JavaScript แบบแยกโค้ด

การโหลดทรัพยากร JavaScript ขนาดใหญ่ส่งผลต่อความเร็วหน้าเว็บอย่างมาก แยกส่วน JavaScript ให้เล็กลงแล้วดาวน์โหลดเฉพาะสิ่งที่จำเป็นสำหรับ หน้าเว็บที่ทำงานในช่วงเริ่มต้นจะสามารถปรับปรุงการโหลดของหน้าเว็บของคุณได้เป็นอย่างมาก การปรับเปลี่ยนตามอุปกรณ์ ซึ่งก็ช่วยปรับปรุงการโต้ตอบกับรายการถัดไปของหน้าเว็บได้ Paint (INP)

เมื่อหน้าเว็บดาวน์โหลด แยกวิเคราะห์ และคอมไพล์ไฟล์ JavaScript ขนาดใหญ่ อาจกลายเป็น ไม่ตอบสนองเป็นระยะเวลาหนึ่ง องค์ประกอบของหน้าจะมองเห็นได้ เนื่องจากเป็นองค์ประกอบ ของ HTML เริ่มต้นของหน้าเว็บและจัดรูปแบบโดย CSS แต่เนื่องจาก JavaScript ที่จำเป็นต่อการขับเคลื่อนองค์ประกอบแบบอินเทอร์แอกทีฟเหล่านั้น รวมถึงสคริปต์อื่นๆ ที่โหลดโดย หน้าเว็บอาจกำลังแยกวิเคราะห์และเรียกใช้ JavaScript เพื่อให้หน้าเว็บทำงาน ผลลัพธ์คือ ผู้ใช้อาจรู้สึกว่าการโต้ตอบนั้น ล่าช้า หรือแม้กระทั่งเสียหายไปเลย

กรณีนี้มักเกิดขึ้นเนื่องจากเทรดหลักถูกบล็อก เมื่อมีการแยกวิเคราะห์ JavaScript และคอมไพล์ในเทรดหลัก หากกระบวนการนี้ใช้เวลานานเกินไป องค์ประกอบของหน้าเว็บอาจตอบสนองได้ไม่เร็วพอเมื่อได้รับข้อมูลจากผู้ใช้ วิธีแก้ปัญหาหนึ่งเดียว คือการโหลดเฉพาะ JavaScript ที่คุณจำเป็นต้องใช้เพื่อให้หน้าเว็บทำงาน การเลื่อนเวลา JavaScript อื่นๆ ไปให้โหลดในภายหลังผ่านเทคนิคที่เรียกว่าโค้ด การแยก โมดูลนี้จะมุ่งเน้นที่เทคนิค 2 อย่างหลัง

ลดการแยกวิเคราะห์และเรียกใช้ JavaScript ระหว่างการเริ่มต้นใช้งานผ่านการแยกโค้ด

Lighthouse จะแสดงคำเตือนเมื่อการดำเนินการของ JavaScript ใช้เวลานานกว่า 2 แต่จะล้มเหลวเมื่อใช้เวลานานกว่า 3.5 วินาที JavaScript มากเกินไป การแยกวิเคราะห์และการเรียกใช้อาจเป็นปัญหาที่อาจเกิดขึ้นในจุดใดก็ได้ในหน้าเว็บ ตลอดอายุการใช้งาน เนื่องจากอาจเพิ่มความล่าช้าในการป้อนข้อมูลของการโต้ตอบได้ หากเวลาที่ผู้ใช้โต้ตอบกับหน้าเว็บนั้นตรงกับช่วงเวลาดังกล่าว งานเทรดหลักที่รับผิดชอบการประมวลผลและเรียกใช้ JavaScript ได้แก่ วิ่งอยู่

ยิ่งไปกว่านั้น การดำเนินการและแยกวิเคราะห์ JavaScript ที่มากเกินไป เกิดปัญหาในระหว่างการโหลดหน้าเว็บเริ่มต้น เนื่องจากเป็นประเด็นในหน้าเว็บ ที่ผู้ใช้มีแนวโน้มที่จะโต้ตอบกับหน้าเว็บ ในความเป็นจริงแล้ว เวลาทั้งหมดในการบล็อก (TBT) ซึ่งเป็นเมตริกการตอบกลับในการโหลดมีความสัมพันธ์สูง ด้วย INP ซึ่งแสดงให้เห็นว่าผู้ใช้มีแนวโน้มสูงที่จะพยายามโต้ตอบ ในระหว่างการโหลดหน้าเว็บเริ่มต้น

การตรวจสอบ Lighthouse ที่รายงานเวลาที่ใช้ในการดำเนินการกับไฟล์ JavaScript แต่ละไฟล์ คำขอหน้าเว็บมีประโยชน์เพราะจะช่วยให้คุณระบุ สคริปต์อาจเป็นตัวเลือกที่เหมาะสมสำหรับการแยกโค้ด คุณสามารถดำเนินการต่อได้โดย โดยใช้เครื่องมือการครอบคลุมใน Chrome DevTools เพื่อระบุอย่างเจาะจงว่าส่วนใดของ JavaScript ของหน้าเว็บจะไม่ทำงานระหว่างการโหลดหน้าเว็บ

การแยกโค้ดเป็นเทคนิคที่มีประโยชน์ที่สามารถลด JavaScript เริ่มต้นของหน้าเว็บได้ เพย์โหลด ซึ่งจะช่วยให้คุณแยกกลุ่ม JavaScript ออกเป็น 2 ส่วน ดังนี้

  • JavaScript ที่จำเป็นเมื่อโหลดหน้าเว็บ จึงไม่สามารถโหลดที่อื่นๆ ได้
  • JavaScript ที่เหลืออยู่ซึ่งสามารถโหลดได้ในภายหลัง โดยส่วนใหญ่ ณ จุดที่ผู้ใช้โต้ตอบกับองค์ประกอบแบบอินเทอร์แอกทีฟหนึ่งๆ หน้าเว็บ

การแยกโค้ดทําได้โดยใช้ไวยากรณ์แบบไดนามิก import() ช่วงเวลานี้ ไวยากรณ์ ซึ่งต่างจากองค์ประกอบ <script> ที่ขอทรัพยากร JavaScript ที่ระบุ ระหว่างการเริ่มต้น - ส่งคำขอทรัพยากร JavaScript ในภายหลังระหว่าง วงจรหน้าเว็บ

document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
  // Get the form validation named export from the module through destructuring:
  const { validateForm } = await import('/validate-form.mjs');

  // Validate the form:
  validateForm();
}, { once: true });

ในข้อมูลโค้ด JavaScript ก่อนหน้า โมดูล validate-form.mjs จะ ดาวน์โหลด แยกวิเคราะห์ และดำเนินการเฉพาะเมื่อผู้ใช้เบลอข้อมูลในแบบฟอร์ม <input> ช่อง ในกรณีนี้ ทรัพยากร JavaScript ที่รับผิดชอบ การกำหนดตรรกะการตรวจสอบของฟอร์มจะเกี่ยวข้องกับหน้าเว็บก็ต่อเมื่อ มีโอกาสใช้งานมากที่สุดจริงๆ

Bundler ของ JavaScript เช่น webpack, Parcel, Rollup และ esbuild สามารถเป็นได้ ได้รับการกำหนดค่าให้แยกกลุ่ม JavaScript ออกเป็นกลุ่มเล็กๆ ทุกครั้งที่ พบการเรียกใช้ import() แบบไดนามิกในซอร์สโค้ด เครื่องมือเหล่านี้ส่วนใหญ่ นี้โดยอัตโนมัติ แต่คุณต้องเลือกใช้ การเพิ่มประสิทธิภาพ

หมายเหตุที่เป็นประโยชน์เกี่ยวกับการแยกโค้ด

ขณะที่การแยกโค้ดเป็นวิธีที่มีประสิทธิภาพในการลดการช่วงชิงเทรดหลัก ในระหว่างการโหลดหน้าเว็บครั้งแรก คุณจะต้องคำนึงถึง 2-3 สิ่งต่อไปนี้หากคุณตัดสินใจ เพื่อตรวจสอบซอร์สโค้ดของ JavaScript เพื่อหาโอกาสในการแยกโค้ด

ใช้ Bundler หากทำได้

เป็นเรื่องปกติที่นักพัฒนาซอฟต์แวร์จะใช้โมดูล JavaScript ในช่วง ขั้นตอนการพัฒนาแอป ซึ่งเป็นการปรับปรุงประสบการณ์ ที่ดีเยี่ยมของนักพัฒนาซอฟต์แวร์ ช่วยให้โค้ดอ่านง่ายและบำรุงรักษาได้ดีขึ้น แต่ก็มีบางส่วน ลักษณะประสิทธิภาพที่ต่ำกว่ามาตรฐานซึ่งอาจส่งผลเมื่อจัดส่ง JavaScript ไปสู่เวอร์ชันที่ใช้งานจริง

สิ่งสำคัญที่สุดคือ คุณควรใช้ Bundler ในการประมวลผลและเพิ่มประสิทธิภาพแหล่งที่มา รวมถึงโมดูลที่คุณต้องการแยกโค้ดด้วย Bundler มีประสิทธิภาพมากในการ ไม่เพียงแค่ปรับใช้การเพิ่มประสิทธิภาพกับซอร์สโค้ด JavaScript แต่ยัง ค่อนข้างมีประสิทธิภาพในการสร้างสมดุลระหว่างการพิจารณาประสิทธิภาพ เช่น ขนาดแพ็กเกจ เทียบกับอัตราส่วนการบีบอัด ประสิทธิภาพการบีบอัดเพิ่มขึ้นด้วยขนาดกลุ่ม แต่ Bundler ยังพยายามตรวจสอบว่าแพ็กเกจไม่ได้มีขนาดใหญ่จน งานที่ใช้เวลานานเพราะการประเมินสคริปต์

นอกจากนี้ Bundler ยังหลีกเลี่ยงปัญหาในการจัดส่งโมดูลที่ยังไม่ได้จัดแพ็กเกจจำนวนมากด้วย ผ่านทางเครือข่าย สถาปัตยกรรมที่ใช้โมดูล JavaScript มักมีแท็ก ต้นไม้โมดูลที่ซับซ้อน เมื่อเลิกรวมกลุ่มต้นไม้โมดูล แต่ละโมดูลจะแสดงถึง คำขอ HTTP แยกต่างหาก และความสามารถในการโต้ตอบในเว็บแอปอาจมีความล่าช้าหากคุณ อย่ารวมกลุ่มโมดูล แม้ว่าคุณจะสามารถใช้ คำแนะนำทรัพยากร <link rel="modulepreload"> เพื่อโหลดต้นไม้โมดูลขนาดใหญ่โดยเร็วที่สุด มากที่สุด แพ็กเกจ JavaScript ยังคงดีกว่าจากประสิทธิภาพการโหลด จุดยืนของเรา

อย่าปิดใช้การคอมไพล์สตรีมมิงโดยไม่ตั้งใจ

เครื่องมือ V8 JavaScript ของ Chromium มีการเพิ่มประสิทธิภาพมากมายที่พร้อมใช้งาน เพื่อให้มั่นใจว่าโค้ด JavaScript การผลิตของคุณโหลดได้อย่างมีประสิทธิภาพที่สุด การเพิ่มประสิทธิภาพอย่างหนึ่งคือการคอมไพล์สตรีมมิง การแยกวิเคราะห์ที่เพิ่มขึ้นของ HTML ที่สตรีมไปยังเบราว์เซอร์ ประกอบด้วยชิ้นส่วนที่สตรีม JavaScript ที่มาจากเครือข่าย

คุณมีวิธีการ 2 วิธีที่จะทำให้การคอมไพล์แบบสตรีมเกิดขึ้นสำหรับ เว็บแอปพลิเคชันใน Chromium

  • เปลี่ยนรูปแบบโค้ดเวอร์ชันที่ใช้งานจริงเพื่อหลีกเลี่ยงการใช้โมดูล JavaScript ที่ดัดผม สามารถแปลงซอร์สโค้ด JavaScript ของคุณตามเป้าหมายการคอมไพล์ และ เป้าหมายมักจะเฉพาะเจาะจงกับสภาพแวดล้อมหนึ่งๆ V8 จะใช้สตรีมมิง เป็นโค้ด JavaScript ที่ไม่ได้ใช้โมดูล และคุณสามารถ กำหนดค่า Bundler เพื่อเปลี่ยนรูปแบบโค้ดโมดูล JavaScript เป็นไวยากรณ์ ที่ไม่ได้ใช้โมดูล JavaScript และฟีเจอร์ของโมดูล
  • หากต้องการส่งโมดูล JavaScript ไปเป็นเวอร์ชันที่ใช้งานจริง โปรดใช้ .mjs ส่วนขยาย ไม่ว่า JavaScript เวอร์ชันที่ใช้งานจริงของคุณจะใช้โมดูลหรือไม่ก็ตาม ไม่มีประเภทเนื้อหาพิเศษสำหรับ JavaScript ที่ใช้โมดูลเทียบกับ JavaScript ที่ไม่เป็นเช่นนั้น หากมีข้อกังวลเกี่ยวกับ V8 คุณจะเลือกไม่ใช้สตรีมมิงได้อย่างมีประสิทธิภาพ เมื่อจัดส่งโมดูล JavaScript ในเวอร์ชันที่ใช้งานจริงโดยใช้ .js ส่วนขยาย หากคุณใช้ส่วนขยาย .mjs สำหรับโมดูล JavaScript V8 สามารถ ตรวจสอบว่าการคอมไพล์สตรีมมิงสำหรับโค้ด JavaScript ที่ใช้โมดูลนั้น เสีย

อย่าให้ข้อควรพิจารณาเหล่านี้ทำให้คุณใช้การแยกโค้ดไม่ได้ โค้ด การแยกเป็นวิธีที่มีประสิทธิภาพในการลดเพย์โหลด JavaScript เริ่มต้นให้แก่ผู้ใช้ แต่ด้วยการใช้ Bundler และการรู้ว่าจะรักษา สตรีมมิงของ V8 ได้อย่างไร ลักษณะการทำงานในการคอมไพล์ คุณสามารถมั่นใจได้ว่าโค้ด JavaScript การผลิตของคุณเป็นไปตาม รวดเร็วที่สุดเท่าที่จะทำได้

การสาธิตการนำเข้าแบบไดนามิก

Webpack

webpack มาพร้อมกับปลั๊กอินชื่อ SplitChunksPlugin ซึ่งช่วยให้คุณ กำหนดค่าวิธีที่ Bundler แยกไฟล์ JavaScript Webpack รับรู้ได้ทั้ง คำสั่ง import() แบบไดนามิกและ import แบบคงที่ ลักษณะการทำงานของ SplitChunksPlugin สามารถแก้ไขได้โดยการระบุตัวเลือก chunks ใน การกำหนดค่า:

  • chunks: async คือค่าเริ่มต้น และอ้างอิงถึงการเรียก import() แบบไดนามิก
  • chunks: initial หมายถึงการเรียก import แบบคงที่
  • chunks: all ครอบคลุมทั้งการนำเข้า import() แบบไดนามิกและการนำเข้าแบบคงที่ ซึ่งช่วยให้คุณ เพื่อแชร์กลุ่มระหว่างการนำเข้า async ถึง initial รายการ

โดยค่าเริ่มต้น เมื่อใดก็ตามที่ Webpack พบคำสั่ง import() แบบไดนามิก รายการดังกล่าว ก็จะสร้างส่วนแยกต่างหากสำหรับโมดูลนั้นขึ้นมา

/* main.js */

// An application-specific chunk required during the initial page load:
import myFunction from './my-function.js';

myFunction('Hello world!');

// If a specific condition is met, a separate chunk is downloaded on demand,
// rather than being bundled with the initial chunk:
if (condition) {
  // Assumes top-level await is available. More info:
  // https://v8.dev/features/top-level-await
  await import('/form-validation.js');
}

การกำหนดค่า Webpack เริ่มต้นสำหรับข้อมูลโค้ดก่อนหน้าให้ผลลัพธ์เป็น ชิ้นส่วนต่างๆ:

  • กลุ่ม main.js ซึ่ง Webpack จัดอยู่ในกลุ่ม initial รวมโมดูล main.js และ ./my-function.js
  • กลุ่ม async ซึ่งมีเฉพาะ form-validation.js (ที่มี แฮชไฟล์ในชื่อทรัพยากรหากมีการกำหนดค่าไว้) ดาวน์โหลดส่วนนี้แล้วเท่านั้น ถ้าและเมื่อ condition คือ truthy

การกำหนดค่านี้ช่วยให้คุณเลื่อนการโหลดกลุ่ม form-validation.js ไปได้จนถึง ที่จำเป็นจริงๆ ซึ่งจะช่วยปรับปรุงการตอบสนองของการโหลดโดยการลด สคริปต์ เฉลี่ยระหว่างการโหลดหน้าเว็บเริ่มต้น การดาวน์โหลดและการประเมินสคริปต์ สําหรับกลุ่ม form-validation.js จะเกิดขึ้นเมื่อเป็นไปตามเงื่อนไขที่ระบุไว้ใน ในกรณีนี้ ระบบจะดาวน์โหลดโมดูลที่นำเข้าแบบไดนามิก ตัวอย่างเช่น ที่มีการดาวน์โหลด polyfill สำหรับเบราว์เซอร์หนึ่งๆ เท่านั้น หรือดังเช่น ตัวอย่างก่อนหน้านี้—โมดูลที่นำเข้าเป็นสิ่งจำเป็นสำหรับการโต้ตอบของผู้ใช้

ในทางกลับกัน การเปลี่ยนการกำหนดค่า SplitChunksPlugin เพื่อระบุ chunks: initial ทำให้มีการแยกโค้ดเฉพาะในส่วนเริ่มต้นเท่านั้น สิ่งเหล่านี้คือ ชิ้นส่วนต่างๆ เช่น ที่นำเข้าแบบคงที่ หรือแสดงอยู่ใน entry ของ Webpack พร็อพเพอร์ตี้ เมื่อดูจากตัวอย่างก่อนหน้านี้ กลุ่มที่ได้จะเป็น ชุดค่าผสมของ form-validation.js และ main.js ในไฟล์สคริปต์ไฟล์เดียว ส่งผลให้ประสิทธิภาพการโหลดหน้าเว็บเริ่มต้นแย่ลง

คุณกำหนดค่าตัวเลือกสำหรับ SplitChunksPlugin ให้แยกขนาดใหญ่ขึ้นได้ด้วย เป็นสคริปต์ที่มีขนาดเล็กลงหลายๆ ไฟล์ เช่น การใช้ตัวเลือก maxSize เพื่อสั่งให้ Webpack แยกชิ้นส่วนต่างๆ ออกเป็นไฟล์ย่อย หากไฟล์มีขนาดเกิน ระบุโดย maxSize การแบ่งไฟล์สคริปต์ขนาดใหญ่ออกเป็นไฟล์เล็กๆ สามารถ ปรับปรุงการตอบสนองของการโหลด เนื่องจากในบางกรณี การประเมินสคริปต์ที่ใช้ CPU มาก จะแบ่งงานออกเป็นงานย่อยๆ ซึ่งไม่ค่อยมีแนวโน้มที่จะบล็อก ชุดข้อความเป็นเวลานานขึ้น

นอกจากนี้ การสร้างไฟล์ JavaScript ที่มีขนาดใหญ่ขึ้นยังหมายความว่าสคริปต์ มักจะได้รับผลกระทบจากการทำให้แคชใช้งานไม่ได้ ตัวอย่างเช่น หากคุณจัดส่ง สคริปต์ขนาดใหญ่ซึ่งมีทั้งเฟรมเวิร์กและโค้ดแอปพลิเคชันของบุคคลที่หนึ่ง กลุ่มอาจเป็นโมฆะได้หากมีการอัปเดตเพียงเฟรมเวิร์ก แต่ไม่มีการอัปเดตอื่นใน ทรัพยากรที่รวมกลุ่มไว้

ในทางกลับกัน ไฟล์สคริปต์ขนาดเล็กจะเพิ่มแนวโน้มที่การส่งกลับ ผู้เข้าชมดึงแหล่งข้อมูลจากแคช ส่งผลให้โหลดหน้าเว็บได้เร็วขึ้นใน การเข้าชมซ้ำ แต่ไฟล์ขนาดเล็กกว่าจะได้รับประโยชน์จากการบีบอัดน้อยกว่า และอาจเพิ่มระยะเวลารับส่งข้อมูลของเครือข่ายเมื่อโหลดหน้าเว็บด้วย แคชของเบราว์เซอร์ ต้องมีความระมัดระวังเพื่อสร้างสมดุลระหว่างการแคช ประสิทธิภาพ ประสิทธิผลของการบีบอัด และเวลาในการประเมินสคริปต์

การสาธิต Webpack

การสาธิต Webpack SplitChunksPlugin

ทดสอบความรู้ของคุณ

ประเภทของคำสั่ง import ที่ใช้เมื่อทำการโค้ด แยกหรือไม่

import() แบบไดนามิก
ถูกต้อง
คงที่ import
โปรดลองอีกครั้ง

ประเภทของคำสั่ง import ต้องอยู่ด้านบนสุด ของโมดูล JavaScript ในตำแหน่งอื่นหรือไม่

import() แบบไดนามิก
โปรดลองอีกครั้ง
คงที่ import
ถูกต้อง

เมื่อใช้ SplitChunksPlugin ใน Webpack ความแตกต่างระหว่างกลุ่ม async กับ initialกลุ่มไหม

โหลดกลุ่ม async โดยใช้ import() แบบไดนามิกแล้ว และมีการโหลดส่วน initial โดยใช้แบบคงที่ import
ถูกต้อง
โหลดกลุ่ม async โดยใช้ import แบบคงที่ และมีการโหลดส่วน initial โดยใช้ไดนามิก import()
โปรดลองอีกครั้ง

รายการถัดไป: โหลดรูปภาพแบบ Lazy Loading และองค์ประกอบ <iframe>

แม้ว่าจะเป็นทรัพยากรประเภทที่ราคาค่อนข้างแพง แต่ JavaScript ไม่ใช่ เฉพาะประเภททรัพยากรที่คุณเลื่อนการโหลดได้ รูปภาพและองค์ประกอบ <iframe> เป็นทรัพยากรที่มีต้นทุนสูงในตัวเอง เช่นเดียวกับ JavaScript คุณสามารถ สามารถเลื่อนการโหลดรูปภาพและองค์ประกอบ <iframe> ได้โดยการโหลดแบบ Lazy Loading ซึ่งอธิบายไว้ในโมดูลถัดไปของหลักสูตรนี้