กลยุทธ์การแบ่งส่วนเว็บแพ็กใหม่ใน Next.js และ Gatsby ช่วยลดโค้ดที่ซ้ำกันเพื่อปรับปรุงประสิทธิภาพการโหลดหน้าเว็บ
Chrome กำลังทำงานร่วมกันกับเครื่องมือและเฟรมเวิร์กในระบบนิเวศโอเพนซอร์สของ JavaScript เมื่อเร็วๆ นี้มีการเพิ่มการเพิ่มประสิทธิภาพใหม่ๆ จำนวนหนึ่งเพื่อปรับปรุงประสิทธิภาพการโหลดของ Next.js และ Gatsby บทความนี้กล่าวถึงกลยุทธ์การแบ่งส่วนแบบละเอียดที่ได้รับการปรับปรุง ซึ่งในขณะนี้จัดส่งไปโดยค่าเริ่มต้นในทั้ง 2 เฟรมเวิร์ก
เกริ่นนำ
เช่นเดียวกับเว็บเฟรมเวิร์กหลายๆ เว็บ Next.js และ Gatsby ใช้ webpack เป็นแพ็กเกจหลัก โดย Webpack v3 ก็ได้เปิดตัว
CommonsChunkPlugin
เพื่อทำให้สามารถแสดงผลโมดูลที่แชร์ระหว่างจุดแรกเข้าที่แตกต่างกันในกลุ่ม "ทั่วไป" กลุ่มเดียว (หรือไม่กี่กลุ่ม) ได้ คุณสามารถดาวน์โหลดโค้ดที่แชร์แยกต่างหากและจัดเก็บไว้ในแคชของเบราว์เซอร์ได้ตั้งแต่เนิ่นๆ ซึ่งอาจทำให้ประสิทธิภาพการโหลดดีขึ้น
รูปแบบนี้ได้รับความนิยมในเฟรมเวิร์กของแอปพลิเคชันหน้าเว็บเดียวจำนวนมากที่ใช้การกำหนดค่าจุดแรกเข้าและการรวมแพ็กเกจที่มีลักษณะดังนี้
ถึงแม้แนวคิดในการรวมโค้ดโมดูลที่แชร์ทั้งหมดไว้ในกลุ่มเดียวก็มีข้อจำกัด โมดูลที่ไม่ได้ใช้ร่วมกันในจุดแรกเข้าทุกจุดสามารถดาวน์โหลดได้สำหรับเส้นทางที่ไม่ได้ใช้ ซึ่งส่งผลให้มีการดาวน์โหลดโค้ดมากเกินจำเป็น ตัวอย่างเช่น เมื่อ page1
โหลดกลุ่ม common
ระบบจะโหลดโค้ดสำหรับ moduleC
แม้ว่า page1
จะไม่ได้ใช้ moduleC
ก็ตาม
ด้วยเหตุนี้ Webpack v4 พร้อมกับคนอื่นๆ อีกจำนวนหนึ่งจึงนำปลั๊กอินออกเพื่อเปลี่ยนไปใช้ปลั๊กอินใหม่ SplitChunksPlugin
ผ่านการปรับปรุงเนื้อหา
การตั้งค่าเริ่มต้นของ SplitChunksPlugin
เหมาะสำหรับผู้ใช้ส่วนใหญ่ ระบบจะสร้างส่วนแยกหลายรายการตามconditionsจำนวนหนึ่งเพื่อป้องกันไม่ให้มีการดึงข้อมูลโค้ดที่ซ้ำกันในหลายเส้นทาง
อย่างไรก็ตาม เว็บเฟรมเวิร์กจำนวนมากที่ใช้ปลั๊กอินนี้ยังคงเป็นไปตามแนวทาง "กลุ่มร่วมเดียว" ในการแยกส่วน เช่น Next.js จะสร้างแพ็กเกจ commons
ที่มีโมดูลที่ใช้ในหน้าเว็บมากกว่า 50% และการอ้างอิงเฟรมเวิร์กทั้งหมด (react
, react-dom
และอื่นๆ)
const splitChunksConfigs = {
…
prod: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
commons: {
name: 'commons',
chunks: 'all',
minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
},
react: {
name: 'commons',
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
},
},
},
แม้ว่าการรวมโค้ดที่ขึ้นอยู่กับเฟรมเวิร์กไว้ในกลุ่มที่แชร์จะทำให้มีการดาวน์โหลดและแคชสำหรับจุดแรกเข้าได้ แต่การเรียนรู้ตามการใช้งานโดยการรวมโมดูลทั่วไปที่ใช้มากกว่าครึ่งหน้านั้นไม่มีประสิทธิภาพมากนัก การแก้ไขอัตราส่วนนี้จะทำให้เกิดผลลัพธ์แบบใดแบบหนึ่งเท่านั้น ดังนี้
- หากคุณลดอัตราส่วน ระบบจะดาวน์โหลดโค้ดที่ไม่จำเป็นมากขึ้น
- หากคุณเพิ่มอัตราส่วน ระบบจะทำซ้ำโค้ดจำนวนมากขึ้นในหลายๆ เส้นทาง
ในการแก้ปัญหานี้ Next.js จึงได้ใช้การกำหนดค่าที่ต่างกันสำหรับ SplitChunksPlugin
ซึ่งช่วยลดโค้ดที่ไม่จำเป็นสำหรับทุกเส้นทาง
- โมดูลของบุคคลที่สามที่มีขนาดใหญ่เพียงพอ (ใหญ่กว่า 160 KB) จะแบ่งออกเป็นส่วนๆ ของตัวเอง
- ระบบจะสร้างกลุ่ม
frameworks
แยกต่างหากสำหรับทรัพยากร Dependency ของเฟรมเวิร์ก (react
,react-dom
และอื่นๆ) - สร้างกลุ่มที่แชร์ได้มากเท่าที่ต้องการ (สูงสุด 25 กลุ่ม)
- ขนาดขั้นต่ำของส่วนที่จะสร้างขึ้นเปลี่ยนเป็น 20 KB
กลยุทธ์การแบ่งเนื้อหาแบบละเอียดนี้ให้ประโยชน์ดังต่อไปนี้
- ปรับปรุงเวลาในการโหลดหน้าเว็บ การปล่อยกลุ่มที่แชร์หลายๆ ส่วน แทนที่จะเป็นรายการเดียว ลดจำนวนโค้ดที่ไม่จำเป็น (หรือซ้ำกัน) สำหรับจุดเข้าถึงใดๆ
- ปรับปรุงการแคชในระหว่างการนำทาง การแยกไลบรารีขนาดใหญ่และทรัพยากร Dependency ของเฟรมเวิร์กออกเป็นกลุ่มๆ จะลดโอกาสที่แคชจะเป็นโมฆะ เนื่องจากทั้งคู่ไม่น่าจะเปลี่ยนแปลงจนกว่าจะทำการอัปเกรด
ดูการกำหนดค่าทั้งหมดที่ Next.js นำมาใช้ใน webpack-config.ts
คำขอ HTTP เพิ่มเติม
SplitChunksPlugin
ได้กำหนดพื้นฐานสำหรับการแบ่งส่วนแบบละเอียด และการใช้แนวทางนี้กับเฟรมเวิร์กอย่าง Next.js ไม่ใช่แนวคิดใหม่ทั้งหมด อย่างไรก็ตาม เฟรมเวิร์กจำนวนมากยังคง
ใช้กลยุทธ์แบบกลุ่มแบบฮิวริสติกและ "ทั่วไป" เพียง 2-3 เหตุผล ซึ่งรวมถึงความกังวลที่ว่าคำขอ HTTP จำนวนมากอาจส่งผลเสียต่อประสิทธิภาพของเว็บไซต์
เบราว์เซอร์จะเปิดการเชื่อมต่อ TCP ไปยังต้นทางเดียวในจำนวนที่จำกัดเท่านั้น (6 สำหรับ Chrome) ดังนั้นการลดจำนวนของกลุ่มที่แสดงผลโดย Bundler จะช่วยให้มั่นใจได้ว่าจำนวนคำขอทั้งหมดจะอยู่ภายใต้เกณฑ์นี้ อย่างไรก็ตาม ค่านี้จะเป็นจริงสำหรับ HTTP/1.1 เท่านั้น การทำ Multiplex ใน HTTP/2 ช่วยให้สตรีมคำขอหลายรายการพร้อมกันได้โดยใช้การเชื่อมต่อเดียวผ่านต้นทางเดียว กล่าวคือ โดยทั่วไปเราไม่ต้องกังวลเกี่ยวกับการจำกัดจำนวนส่วน ที่ Bundler ของเราปล่อยออกมา
เบราว์เซอร์หลักทั้งหมดรองรับ HTTP/2 ทีม Chrome และ Next.js ต้องการทราบว่าการเพิ่มจำนวนคำขอโดยแยกแพ็กเกจ "commons" เดี่ยวของ Next.js ออกเป็นหลายๆ ส่วนที่แชร์จะส่งผลต่อประสิทธิภาพการโหลดหรือไม่ โดยเริ่มต้นด้วยการวัดประสิทธิภาพของเว็บไซต์เดียวในขณะที่แก้ไขจำนวนคำขอพร้อมกันสูงสุดโดยใช้พร็อพเพอร์ตี้ maxInitialRequests
โดยเฉลี่ยในการทดสอบหลายครั้งในหน้าเว็บเดียว เวลาของ load
, start-Render และ First Contentful Paint ยังคงเหมือนเดิมเมื่อเปลี่ยนจำนวนคำขอเริ่มต้นสูงสุด (จาก 5 ถึง 15 รายการ) สิ่งที่น่าสนใจก็คือเราสังเกตเห็นว่าประสิทธิภาพที่เพิ่มขึ้นเล็กน้อย
หลังจากที่แยกคำขอออกเป็นหลายร้อยคำขอเป็นจำนวนมาก
สิ่งนี้แสดงให้เห็นว่าการคงไว้ต่ำกว่าเกณฑ์ที่เชื่อถือได้ (คำขอ 20~25 รายการ) ทำให้เกิดสมดุลที่เหมาะสมระหว่างประสิทธิภาพการโหลดกับประสิทธิภาพการแคช หลังจากการทดสอบพื้นฐาน ระบบเลือก 25 รายการเป็นจํานวน maxInitialRequest
การแก้ไขจำนวนคำขอสูงสุดที่เกิดขึ้นพร้อมกันจะทำให้มีแพ็กเกจที่แชร์มากกว่า 1 กลุ่ม และการแยกคำขออย่างเหมาะสมสำหรับจุดแรกเข้าแต่ละจุดลดจำนวนโค้ดที่ไม่จำเป็นสำหรับหน้าเดียวกันลงได้อย่างมาก
การทดสอบนี้เป็นเพียงการแก้ไขจำนวนคำขอเพื่อดูว่าจะส่งผลในทางลบต่อประสิทธิภาพการโหลดหน้าเว็บไหม ผลลัพธ์ที่ได้ชี้ให้เห็นว่าการตั้งค่า maxInitialRequests
เป็น 25
ในหน้าทดสอบนั้นเหมาะสมที่สุดแล้ว เพราะเป็นการลดขนาดเพย์โหลด JavaScript โดยไม่ทำให้หน้าเว็บช้าลง จำนวน JavaScript ทั้งหมดที่ต้องใช้ในการแสดงโฆษณาให้หน้ายังเท่าเดิมที่เท่าเดิม ซึ่งอธิบายถึงสาเหตุที่ประสิทธิภาพในการโหลดหน้าเว็บไม่จำเป็นต้องดีขึ้นเสมอไปเนื่องจากโค้ดที่ลดลง
Webpack จะใช้ 30 KB เป็นขนาดขั้นต่ำเริ่มต้นในการสร้างกลุ่มขึ้นมา อย่างไรก็ตาม การรวมค่า maxInitialRequests
เป็น 25 กับขนาดขั้นต่ำ 20 KB แทนจะส่งผลให้การแคชดีขึ้น
การลดขนาดด้วยชิ้นส่วนแบบละเอียด
เฟรมเวิร์กจำนวนมาก รวมถึง Next.js อาศัยการกำหนดเส้นทางฝั่งไคลเอ็นต์ (จัดการโดย JavaScript) เพื่อแทรกแท็กสคริปต์ใหม่ๆ สำหรับการเปลี่ยนเส้นทางทุกครั้ง แต่เขาจะสรุปองค์ประกอบต่างๆ ที่เปลี่ยนแปลงอยู่ตลอดเวลาเหล่านี้ได้อย่างไรในเวลาบิลด์
Next.js ใช้ไฟล์ Manifest ของบิลด์ฝั่งเซิร์ฟเวอร์ในการระบุว่าจุดแรกเข้าต่างๆ ใช้กลุ่มเอาต์พุตใด เพื่อให้ข้อมูลนี้แก่ลูกค้าด้วย ระบบจึงสร้างไฟล์ Manifest ที่จัดทำฝั่งไคลเอ็นต์เป็นฉบับย่อเพื่อแมปทรัพยากร Dependency ทั้งหมดสำหรับทุกจุดแรกเข้า
// Returns a promise for the dependencies for a particular route
getDependencies (route) {
return this.promisedBuildManifest.then(
man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
)
}
กลยุทธ์การแบ่งส่วนแบบละเอียดใหม่นี้เปิดตัวครั้งแรกใน Next.js หลัง Flag ซึ่งได้ทดสอบกับผู้ใช้นำร่องจำนวนหนึ่ง หลายคนเห็นว่า JavaScript ทั้งหมดที่ใช้กับทั้งเว็บไซต์ลดลงอย่างมาก
เว็บไซต์ | การเปลี่ยนแปลง JS ทั้งหมด | % ความแตกต่าง |
---|---|---|
https://www.barnebys.com/ | -238 KB | ลดลง 23% |
https://sumup.com/ | -220 KB | ลดลง 30% |
https://www.hashicorp.com/ | 11 MB | ลดลง 71% |
เราจัดส่งเวอร์ชันสุดท้ายในเวอร์ชัน 9.2 โดยค่าเริ่มต้น
แกตส์บี้
Gatsby ใช้แนวทางเดียวกับที่ใช้การเรียนรู้ตามการใช้งานในการกำหนดโมดูลทั่วไป ดังนี้
config.optimization = {
…
splitChunks: {
name: false,
chunks: `all`,
cacheGroups: {
default: false,
vendors: false,
commons: {
name: `commons`,
chunks: `all`,
// if a chunk is used more than half the components count,
// we can assume it's pretty global
minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
},
react: {
name: `commons`,
chunks: `all`,
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
},
เมื่อเพิ่มประสิทธิภาพการกำหนดค่า Webpack ให้มีกลยุทธ์การแบ่งย่อยเนื้อหาที่คล้ายกัน บริษัทยังสังเกตเห็นว่า JavaScript ลดลงอย่างมากในเว็บไซต์ขนาดใหญ่หลายแห่ง ดังนี้
เว็บไซต์ | การเปลี่ยนแปลง JS ทั้งหมด | % ความแตกต่าง |
---|---|---|
https://www.gatsbyjs.org/ | -680 KB | ลดลง 22% |
https://www.thirdandgrove.com/ | -390 KB | -25% |
https://ghost.org/ | 1.1 MB | ลดลง 35% |
https://reactjs.org/ | -80 KB | -8% |
ดู PR เพื่อทำความเข้าใจวิธีที่ทีมนำตรรกะนี้ไปใช้กับการกำหนดค่า Webpack ซึ่งมีการจัดส่งตามค่าเริ่มต้นในเวอร์ชัน 2.20.7
บทสรุป
แนวคิดในการจัดส่งเป็นกลุ่มแบบละเอียดไม่ได้เจาะจงไปที่ Next.js, Gatsby หรือแม้แต่ Webpack ทุกคนควรพิจารณาปรับปรุงกลยุทธ์การแบ่งส่วนแอปพลิเคชันของตนเองหากเป็นไปตามแนวทางแบบกลุ่ม "Commons" ไม่ว่าจะใช้เฟรมเวิร์กหรือ Bundle Bundler แบบใดก็ตาม
- หากคุณต้องการดูการเพิ่มประสิทธิภาพแบบแบ่งส่วนเดียวกับที่ใช้กับแอปพลิเคชัน Vanilla React ลองดูแอป React ตัวอย่างนี้ ซึ่งจะใช้กลยุทธ์การแบ่งส่วนแบบละเอียดเวอร์ชันที่เรียบง่าย และช่วยให้คุณเริ่มใช้ตรรกะประเภทเดียวกันในเว็บไซต์
- สําหรับรายงาน ระบบจะสร้างกลุ่มแบบละเอียดโดยค่าเริ่มต้น ลองดูที่
manualChunks
หากต้องการกำหนดค่าลักษณะการทำงานด้วยตนเอง