กลยุทธ์การรวมกลุ่มเว็บแพ็กแบบใหม่ใน Next.js และ Gatsby จะลดโค้ดที่ซ้ำกันเพื่อปรับปรุงประสิทธิภาพการโหลดหน้าเว็บ
Chrome ทำงานร่วมกับเครื่องมือและเฟรมเวิร์กในระบบนิเวศโอเพนซอร์สของ JavaScript เมื่อเร็วๆ นี้เราได้เพิ่มการเพิ่มประสิทธิภาพใหม่ๆ หลายรายการเพื่อปรับปรุงประสิทธิภาพการโหลดของ Next.js และ Gatsby บทความนี้กล่าวถึงกลยุทธ์การแบ่งส่วนแบบละเอียดที่ได้รับการปรับปรุง ซึ่งตอนนี้จัดส่งโดยค่าเริ่มต้นในทั้ง 2 เฟรมเวิร์กแล้ว
บทนำ
Next.js และ Gatsby ใช้ webpack เป็นบักเบรลหลักเช่นเดียวกับเฟรมเวิร์กเว็บอื่นๆ หลายเฟรมเวิร์ก webpack v3 ได้เปิดตัว CommonsChunkPlugin
เพื่อให้สามารถแสดงผลโมดูลที่แชร์กันระหว่างจุดแรกเข้าต่างๆ ในข้อมูลโค้ด "คอมมอน" รายการเดียว (หรือ 2-3 รายการ) โค้ดที่แชร์จะดาวน์โหลดแยกต่างหากและจัดเก็บไว้ในแคชของเบราว์เซอร์ตั้งแต่เนิ่นๆ ซึ่งส่งผลให้การโหลดมีประสิทธิภาพดีขึ้น
รูปแบบนี้ได้รับความนิยมในเฟรมเวิร์กแอปพลิเคชันหน้าเว็บเดียวหลายเฟรมเวิร์กที่ใช้การกำหนดค่าจุดแรกเข้าและกลุ่มที่มีลักษณะดังนี้
แม้ว่าจะปฏิบัติได้จริง แต่แนวคิดของการรวมโค้ดโมดูลที่แชร์ทั้งหมดไว้ในกลุ่มเดียวก็มีข้อจำกัดอยู่ ระบบจะดาวน์โหลดโมดูลที่ไม่ได้แชร์ในทุกจุดแรกเข้าสําหรับเส้นทางที่ไม่ได้ใช้ ซึ่งส่งผลให้มีการดาวน์โหลดโค้ดมากกว่าที่จําเป็น เช่น เมื่อ page1
โหลดกลุ่ม common
ก็จะโหลดโค้ดสําหรับ moduleC
แม้ว่า page1
จะไม่ได้ใช้ moduleC
ก็ตาม
ด้วยเหตุนี้และเหตุผลอื่นๆ อีก 2-3 ข้อ webpack v4 จึงนำปลั๊กอินนี้ออกเพื่อใช้ปลั๊กอินใหม่อย่าง SplitChunksPlugin
การแบ่งออกเป็นกลุ่มที่ปรับปรุงแล้ว
การตั้งค่าเริ่มต้นของ SplitChunksPlugin
เหมาะกับผู้ใช้ส่วนใหญ่ ระบบจะสร้างกลุ่มที่แยกหลายกลุ่มโดยขึ้นอยู่กับเงื่อนไขหลายรายการเพื่อป้องกันการดึงข้อมูลโค้ดที่ซ้ำกันหลายเส้นทาง
อย่างไรก็ตาม หลายเฟรมเวิร์กเว็บที่ใช้ปลั๊กอินนี้ยังคงใช้แนวทางแบบ "Single-commons" ในการแยกกลุ่ม ตัวอย่างเช่น 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
แยกต่างหากสําหรับข้อกําหนดของเฟรมเวิร์ก (react
,react-dom
และอื่นๆ) - สร้างกลุ่มที่แชร์ได้มากเท่าที่ต้องการ (สูงสุด 25 รายการ)
- เปลี่ยนขนาดต่ำสุดของกลุ่มที่จะสร้างเปลี่ยนเป็น 20 KB
กลยุทธ์การแยกส่วนแบบละเอียดนี้มีประโยชน์ดังต่อไปนี้
- เวลาในการโหลดหน้าเว็บดีขึ้น การปล่อยกลุ่มที่แชร์หลายกลุ่มแทนรายการเดียวจะช่วยลดจำนวนโค้ดที่ไม่จำเป็น (หรือซ้ำกัน) สำหรับจุดแรกเข้า
- การแคชที่ดียิ่งขึ้นระหว่างการนำทาง การแยกไลบรารีขนาดใหญ่และไลบรารีที่ต้องอาศัยเฟรมเวิร์กออกเป็นส่วนๆ จะช่วยลดความเป็นไปได้ที่แคชจะใช้งานไม่ได้ เนื่องจากทั้ง 2 รายการมีแนวโน้มที่จะไม่เปลี่ยนแปลงจนกว่าจะทำการอัปเกรด
คุณดูการกําหนดค่าทั้งหมดที่ Next.js นำมาใช้ได้ใน webpack-config.ts
คำขอ HTTP เพิ่มเติม
SplitChunksPlugin
ได้กำหนดพื้นฐานสำหรับการแบ่งกลุ่มแบบละเอียด และการใช้แนวทางนี้กับเฟรมเวิร์กอย่าง Next.js ไม่ใช่แนวคิดใหม่ทั้งหมด อย่างไรก็ตาม เฟรมเวิร์กหลายรายการยังคงใช้กลยุทธ์การแยกกลุ่ม "คอมมอนส์" และการเรียนรู้เชิง heuristics แบบเดียวต่อไปด้วยเหตุผลบางประการ ซึ่งรวมถึงข้อกังวลที่ว่าคำขอ HTTP จำนวนมากอาจส่งผลเสียต่อประสิทธิภาพของเว็บไซต์
เนื่องจากเบราว์เซอร์สามารถเปิดการเชื่อมต่อ TCP ไปยังต้นทางแห่งเดียวได้เพียงจำนวนจำกัด (6 สำหรับ Chrome) การลดจำนวนชิ้นงานที่เครื่องมือรวมออกจึงช่วยให้มั่นใจได้ว่าจำนวนคำขอทั้งหมดจะไม่เกินเกณฑ์นี้ แต่จะใช้ได้กับ HTTP/1.1 เท่านั้น การทำมัลติเพล็กซิงใน HTTP/2 ช่วยให้คุณสตรีมคำขอหลายรายการพร้อมกันได้โดยใช้การเชื่อมต่อเดียวจากแหล่งที่มาเดียว กล่าวคือ โดยทั่วไปแล้วเราไม่จำเป็นต้องกังวลเกี่ยวกับการจำกัดจำนวนกลุ่มที่เครื่องมือรวมของเราสร้างขึ้น
เบราว์เซอร์หลักทั้งหมดรองรับ HTTP/2 ทีม Chrome และ Next.js ต้องการทราบว่าการเพิ่มจำนวนคำขอด้วยการแยกกลุ่ม "commons" เดี่ยวของ Next.js เป็นกลุ่มที่แชร์หลายๆ กลุ่มจะส่งผลต่อประสิทธิภาพการโหลดหรือไม่ โดยเริ่มจากการวัดประสิทธิภาพของเว็บไซต์เดียวขณะแก้ไขจํานวนคําขอสูงสุดแบบขนานโดยใช้พร็อพเพอร์ตี้ maxInitialRequests
ในการทดสอบหลายครั้งในหน้าเว็บเดียวโดยเฉลี่ย 3 ครั้ง พบว่าเวลา 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 ของบิลด์ฝั่งไคลเอ็นต์แบบย่อเพื่อแมปทรัพยากรทั้งหมดของทุกจุดเข้าใช้งานและส่งข้อมูลนี้ไปยังไคลเอ็นต์ด้วย
// 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
Gatsby เคยใช้แนวทางเดียวกันในการใช้ heuristics ตามการใช้งานเพื่อกำหนดโมดูลทั่วไป ดังนี้
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 อย่างไร ซึ่งจะมาพร้อมกับ v2.20.7 โดยค่าเริ่มต้น
บทสรุป
แนวคิดในการส่งข้อมูลแบบเป็นกลุ่มๆ ไม่ได้จำกัดเฉพาะ Next.js, Gatsby หรือแม้แต่ webpack ทุกคนควรพิจารณาปรับปรุงกลยุทธ์การแบ่งกลุ่มของแอปพลิเคชันหากใช้แนวทางการรวมกลุ่ม "คอมมอนส์" ขนาดใหญ่ ไม่ว่าจะใช้เฟรมเวิร์กหรือเครื่องมือรวมโมดูลใดก็ตาม
- หากคุณต้องการดูการเพิ่มประสิทธิภาพแบบแบ่งส่วนเดียวกันที่ใช้กับแอปพลิเคชัน vanilla React ลองดูแอป React ตัวอย่าง แอปนี้ใช้กลยุทธ์การแบ่งย่อยข้อมูลแบบละเอียด และจะช่วยให้คุณเริ่มใช้ตรรกะประเภทเดียวกันในเว็บไซต์ได้
- สําหรับภาพรวม ระบบจะสร้างกลุ่มแบบละเอียดโดยค่าเริ่มต้น โปรดดูหัวข้อ
manualChunks
หากต้องการกําหนดค่าลักษณะการทํางานด้วยตนเอง