การโหลดแหล่งข้อมูล JavaScript ขนาดใหญ่ส่งผลต่อความเร็วหน้าเว็บอย่างมาก การแยก JavaScript ออกเป็นส่วนเล็กๆ และดาวน์โหลดเฉพาะสิ่งที่จำเป็นเพื่อให้หน้าเว็บทำงานได้ในระหว่างการเริ่มต้นจะช่วยปรับปรุงการตอบสนองต่อการโหลดของหน้าเว็บได้อย่างมาก ซึ่งจะช่วยปรับปรุงInteraction to Next Paint (INP) ของหน้าเว็บได้ด้วย
ขณะที่หน้าเว็บดาวน์โหลด แยกวิเคราะห์ และคอมไพล์ไฟล์ JavaScript ขนาดใหญ่ หน้าเว็บอาจ ไม่ตอบสนองเป็นระยะเวลาหนึ่ง องค์ประกอบของหน้าเว็บจะปรากฏให้เห็นเนื่องจากเป็นส่วนหนึ่งของ HTML เริ่มต้นของหน้าเว็บและจัดรูปแบบโดย CSS อย่างไรก็ตาม เนื่องจาก JavaScript ที่จำเป็นต่อการขับเคลื่อนองค์ประกอบแบบอินเทอร์แอกทีฟเหล่านั้น รวมถึงสคริปต์อื่นๆ ที่หน้าเว็บโหลด อาจกำลังแยกวิเคราะห์และเรียกใช้ JavaScript เพื่อให้องค์ประกอบเหล่านั้นทำงานได้ ผลลัพธ์คือผู้ใช้อาจรู้สึกว่าการโต้ตอบล่าช้าอย่างมาก หรือแม้กระทั่งใช้งานไม่ได้เลย
ซึ่งมักเกิดขึ้นเนื่องจากเทรดหลักถูกบล็อกเมื่อมีการแยกวิเคราะห์และคอมไพล์ JavaScript ในเทรดหลัก หากกระบวนการนี้นานเกินไป องค์ประกอบแบบอินเทอร์แอกทีฟ ของหน้าเว็บอาจตอบสนองต่ออินพุตของผู้ใช้ได้ไม่เร็วพอ วิธีแก้ไขปัญหานี้อย่างหนึ่ง คือการโหลดเฉพาะ JavaScript ที่คุณต้องการให้หน้าเว็บทํางานได้ ขณะที่ เลื่อนการโหลด JavaScript อื่นๆ ออกไปเป็นภายหลังผ่านเทคนิคที่เรียกว่าการแยกโค้ด โมดูลนี้มุ่งเน้นที่เทคนิคหลังใน 2 เทคนิคนี้
ลดการแยกวิเคราะห์และการดำเนินการ JavaScript ระหว่างการเริ่มต้นผ่านการแยกโค้ด
Lighthouse จะแสดงคำเตือนเมื่อการดำเนินการ JavaScript ใช้เวลานานกว่า 2 วินาที และจะล้มเหลวเมื่อใช้เวลานานกว่า 3.5 วินาที การแยกวิเคราะห์และการเรียกใช้ JavaScript มากเกินไปอาจเป็นปัญหาได้ทุกจุดในวงจร ของหน้าเว็บ เนื่องจากอาจเพิ่มความล่าช้าในการป้อนข้อมูลของการโต้ตอบ หากเวลาที่ผู้ใช้โต้ตอบกับหน้าเว็บตรงกับช่วงเวลาที่ งานในเทรดหลักซึ่งรับผิดชอบในการประมวลผลและเรียกใช้ JavaScript กำลังทำงานอยู่
นอกจากนี้ การเรียกใช้และการแยกวิเคราะห์ JavaScript มากเกินไปยังเป็นปัญหาอย่างยิ่ง ในระหว่างการโหลดหน้าเว็บครั้งแรก เนื่องจากเป็นจุดในวงจร ของหน้าเว็บที่ผู้ใช้มีแนวโน้มที่จะโต้ตอบกับหน้าเว็บ ในความเป็นจริงแล้ว Total Blocking Time (TBT) ซึ่งเป็นเมตริกการตอบสนองต่อการโหลดมีความสัมพันธ์กันอย่างมากกับ INP ซึ่งแสดงให้เห็นว่าผู้ใช้มีแนวโน้มสูงที่จะพยายามโต้ตอบระหว่างการโหลดหน้าเว็บครั้งแรก
การตรวจสอบ Lighthouse ที่รายงานเวลาที่ใช้ในการเรียกใช้ไฟล์ JavaScript แต่ละไฟล์ ที่หน้าเว็บของคุณขอมีประโยชน์เนื่องจากช่วยให้คุณระบุสคริปต์ที่อาจเป็นตัวเลือกสำหรับการแยกโค้ดได้อย่างแม่นยำ จากนั้นคุณสามารถดำเนินการต่อได้โดยใช้เครื่องมือครอบคลุมในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เพื่อระบุส่วนของ 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 Bundle ออกเป็น Chunk ขนาดเล็กได้ทุกครั้งที่พบการเรียกใช้ import()
แบบไดนามิกในซอร์สโค้ด เครื่องมือเหล่านี้ส่วนใหญ่จะดำเนินการนี้โดยอัตโนมัติ แต่ esbuild โดยเฉพาะกำหนดให้คุณเลือกใช้การเพิ่มประสิทธิภาพนี้
หมายเหตุที่เป็นประโยชน์เกี่ยวกับการแยกโค้ด
แม้ว่าการแยกโค้ดจะเป็นวิธีที่มีประสิทธิภาพในการลดการแย่งชิงเธรดหลัก ในระหว่างการโหลดหน้าเว็บครั้งแรก แต่คุณควรคำนึงถึงสิ่งต่อไปนี้หากตัดสินใจ ตรวจสอบซอร์สโค้ด JavaScript เพื่อหาโอกาสในการแยกโค้ด
ใช้ Bundler หากทำได้
แนวทางปฏิบัติทั่วไปสำหรับนักพัฒนาซอฟต์แวร์คือการใช้โมดูล JavaScript ในระหว่างกระบวนการพัฒนา ซึ่งเป็นการปรับปรุงประสบการณ์ของนักพัฒนาซอฟต์แวร์ที่ยอดเยี่ยม เพราะช่วยปรับปรุงความสามารถในการอ่านและการบำรุงรักษาโค้ด อย่างไรก็ตาม การจัดส่งโมดูล JavaScript ไปยังเวอร์ชันที่ใช้งานจริงอาจทำให้เกิดลักษณะประสิทธิภาพที่ต่ำกว่าที่ควรจะเป็น
ที่สำคัญที่สุดคือคุณควรใช้ Bundler เพื่อประมวลผลและเพิ่มประสิทธิภาพซอร์สโค้ด รวมถึงโมดูลที่คุณต้องการแยกโค้ด Bundler มีประสิทธิภาพมากในการ ไม่เพียงแค่ใช้การเพิ่มประสิทธิภาพกับซอร์สโค้ด JavaScript แต่ยัง มีประสิทธิภาพมากในการปรับสมดุลข้อควรพิจารณาด้านประสิทธิภาพ เช่น ขนาด Bundle เทียบกับอัตราการบีบอัด ประสิทธิภาพการบีบอัดจะเพิ่มขึ้นตามขนาดของแพ็กเกจ แต่ Bundler ก็พยายามทำให้แพ็กเกจมีขนาดไม่ใหญ่จนเกินไปจนทำให้เกิด งานที่ใช้เวลานานเนื่องจากการประเมินสคริปต์
Bundler ยังช่วยหลีกเลี่ยงปัญหาการจัดส่งโมดูลที่ไม่ได้รวมกลุ่มจำนวนมาก
ผ่านเครือข่ายด้วย สถาปัตยกรรมที่ใช้โมดูล JavaScript มักจะมีโครงสร้างโมดูลที่ซับซ้อนและมีขนาดใหญ่
เมื่อแยกโครงสร้างโมดูลออก โมดูลแต่ละโมดูลจะแสดงคำขอ HTTP แยกต่างหาก และการโต้ตอบในเว็บแอปอาจล่าช้าหากคุณไม่รวมโมดูล แม้ว่าคุณจะใช้<link rel="modulepreload">
คำแนะนำเกี่ยวกับทรัพยากรเพื่อโหลดโครงสร้างโมดูลขนาดใหญ่ให้เร็วที่สุดเท่าที่จะทำได้ แต่แพ็กเกจ JavaScript ก็ยังคงเป็นตัวเลือกที่แนะนำในแง่ของประสิทธิภาพการโหลด
อย่าปิดใช้การคอมไพล์สตรีมโดยไม่ตั้งใจ
เครื่องมือ JavaScript V8 ของ Chromium มีการเพิ่มประสิทธิภาพหลายอย่างพร้อมใช้งาน เพื่อให้มั่นใจว่าโค้ด JavaScript ที่ใช้งานจริงจะโหลดได้อย่างมีประสิทธิภาพมากที่สุด การเพิ่มประสิทธิภาพอย่างหนึ่งเรียกว่าการรวบรวมสตรีม ซึ่งจะรวบรวมส่วน JavaScript ที่สตรีมเมื่อมาถึงจากเครือข่าย เช่นเดียวกับการแยกวิเคราะห์ HTML ที่สตรีมไปยังเบราว์เซอร์ทีละส่วน
คุณมี 2 วิธีในการตรวจสอบว่าการคอมไพล์สตรีมมิงเกิดขึ้นกับเว็บแอปพลิเคชันใน Chromium
- แปลงโค้ดที่ใช้งานจริงเพื่อหลีกเลี่ยงการใช้โมดูล JavaScript Bundler สามารถแปลงซอร์สโค้ด 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 เริ่มต้นสำหรับข้อมูลโค้ดก่อนหน้าจะส่งผลให้มี 2 ก้อนแยกกัน ดังนี้
main.js
chunk ซึ่ง webpack จัดประเภทเป็นinitial
chunk ที่มีโมดูล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
การสาธิต SplitChunksPlugin
webpack
ทดสอบความรู้ของคุณ
import
คำสั่งประเภทใดที่ใช้เมื่อทำการแยกโค้ด
import()
import
import
คำสั่งใดที่ต้องอยู่ด้านบน
ของโมดูล JavaScript และไม่อยู่ในตำแหน่งอื่น
import()
import
เมื่อใช้ SplitChunksPlugin
ใน webpack async
chunk กับ initial
chunk แตกต่างกันอย่างไร
async
จะโหลดโดยใช้ import()
แบบไดนามิก และ initial
จะโหลดโดยใช้ import
แบบคงที่
async
จะโหลดก้อนข้อมูลโดยใช้ import
แบบคงที่ และ initial
จะโหลดก้อนข้อมูลโดยใช้ import()
แบบไดนามิก
ถัดไป: โหลดรูปภาพและองค์ประกอบ <iframe>
แบบ Lazy Loading
แม้ว่า JavaScript จะเป็นทรัพยากรประเภทหนึ่งที่มีราคาค่อนข้างสูง แต่ก็ไม่ใช่ทรัพยากรประเภทเดียวที่คุณเลื่อนการโหลดได้ รูปภาพและ<iframe>
องค์ประกอบ
อาจเป็นทรัพยากรที่มีค่าใช้จ่ายสูงในตัวของมันเอง เช่นเดียวกับ JavaScript คุณสามารถเลื่อนการโหลดรูปภาพและองค์ประกอบ <iframe>
ได้โดยเลื่อนการโหลด ซึ่งจะอธิบายไว้ในโมดูลถัดไปของหลักสูตรนี้