กลยุทธ์การแบ่งกลุ่ม webpack ที่ใหม่กว่าใน Next.js และ Gatsby จะลดโค้ดที่ซ้ำกันเพื่อปรับปรุงประสิทธิภาพการโหลดหน้าเว็บ
Chrome ทำงานร่วมกันกับเครื่องมือและ เฟรมเวิร์กในระบบนิเวศโอเพนซอร์สของ JavaScript เมื่อเร็วๆ นี้เราได้เพิ่มการเพิ่มประสิทธิภาพใหม่ๆ หลายรายการ เพื่อปรับปรุงประสิทธิภาพการโหลดของ Next.js และ Gatsby บทความนี้ครอบคลุมกลยุทธ์การแบ่งกลุ่มแบบละเอียดที่ได้รับการปรับปรุง ซึ่งตอนนี้มาพร้อมกับเฟรมเวิร์กทั้ง 2 รายการโดยค่าเริ่มต้น
บทนำ
เช่นเดียวกับเฟรมเวิร์กเว็บหลายๆ ตัว Next.js และ Gatsby ใช้ webpack เป็นตัวจัดกลุ่มหลัก
webpack v3 ได้เปิดตัว CommonsChunkPlugin
เพื่อให้สามารถ
ส่งออกโมดูลที่แชร์ระหว่างจุดแรกเข้าต่างๆ ในก้อน "commons" เดียว (หรือหลายก้อน) คุณสามารถดาวน์โหลดโค้ดที่ใช้ร่วมกันแยกต่างหากและจัดเก็บไว้ในแคชของเบราว์เซอร์ตั้งแต่เนิ่นๆ ซึ่งจะช่วยให้ประสิทธิภาพการโหลดดีขึ้น
รูปแบบนี้ได้รับความนิยมในเฟรมเวิร์กแอปพลิเคชันหน้าเว็บเดียวหลายรายการที่ใช้การกำหนดค่าจุดแรกเข้าและ แพ็กเกจที่มีลักษณะดังนี้
แม้ว่าแนวคิดในการรวมโค้ดโมดูลที่แชร์ทั้งหมดไว้ในก้อนเดียวจะใช้งานได้จริง แต่ก็มีข้อจำกัด
โมดูลที่ไม่ได้แชร์ในทุกจุดแรกเข้าจะดาวน์โหลดได้สำหรับเส้นทางที่ไม่ได้ใช้
ซึ่งจะทำให้ดาวน์โหลดโค้ดมากกว่าที่จำเป็น เช่น เมื่อ page1
โหลดcommon
Chunk ระบบจะโหลดโค้ดสำหรับ moduleC
แม้ว่า page1
จะไม่ได้ใช้ moduleC
ก็ตาม
ด้วยเหตุนี้และเหตุผลอื่นๆ อีกเล็กน้อย 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)[\\/]/,
},
},
},
แม้ว่าการรวมโค้ดที่ขึ้นอยู่กับเฟรมเวิร์กลงในก้อนข้อมูลที่แชร์จะหมายความว่าสามารถดาวน์โหลดและแคชสำหรับจุดแรกเข้าใดก็ได้ แต่ฮิวริสติกตามการใช้งานของการรวมโมดูลทั่วไปที่ใช้ในครึ่งหนึ่งของหน้าเว็บก็ไม่ได้มีประสิทธิภาพมากนัก การแก้ไขอัตราส่วนนี้จะส่งผลให้เกิดผลลัพธ์ 2 อย่างต่อไปนี้เท่านั้น
- หากลดอัตราส่วน ระบบจะดาวน์โหลดโค้ดที่ไม่จำเป็นมากขึ้น
- หากเพิ่มอัตราส่วน ระบบจะทำซ้ำโค้ดมากขึ้นในหลายเส้นทาง
Next.js จึงใช้การกำหนดค่า
ที่แตกต่างสำหรับSplitChunksPlugin
เพื่อลด
โค้ดที่ไม่จำเป็นสำหรับเส้นทางใดก็ตาม
- โมดูลของบุคคลที่สามที่มีขนาดใหญ่เพียงพอ (มากกว่า 160 KB) จะแยกออกเป็น ก้อนข้อมูลของตัวเอง
- ระบบจะสร้าง
frameworks
Chunk แยกต่างหากสำหรับทรัพยากร Dependency ของเฟรมเวิร์ก (react
,react-dom
และ อื่นๆ) - ระบบจะสร้างก้อนข้อมูลที่แชร์มากเท่าที่จำเป็น (สูงสุด 25 รายการ)
- เปลี่ยนขนาดขั้นต่ำของก้อนข้อมูลที่จะสร้างเป็น 20 KB
กลยุทธ์การแบ่งกลุ่มแบบละเอียดนี้มีประโยชน์ดังนี้
- เวลาในการโหลดหน้าเว็บดีขึ้น การปล่อยหลายๆ ชิ้นที่แชร์แทนที่จะปล่อยชิ้นเดียว จะลดปริมาณโค้ดที่ไม่จำเป็น (หรือโค้ดที่ซ้ำกัน) สำหรับจุดแรกเข้า
- ปรับปรุงการแคชระหว่างการนำทาง การแยกไลบรารีขนาดใหญ่และการขึ้นต่อกันของเฟรมเวิร์ก ออกเป็นก้อนๆ แยกกันจะช่วยลดโอกาสที่แคชจะใช้ไม่ได้ เนื่องจากทั้ง 2 อย่างไม่น่าจะ เปลี่ยนแปลงจนกว่าจะมีการอัปเกรด
คุณดูการกำหนดค่าทั้งหมดที่ Next.js ใช้ได้ใน webpack-config.ts
คำขอ HTTP เพิ่มเติม
SplitChunksPlugin
ได้กำหนดพื้นฐานสำหรับการแบ่งกลุ่มแบบละเอียด และการนำแนวทางนี้ไปใช้กับเฟรมเวิร์กอย่าง Next.js ก็ไม่ใช่แนวคิดใหม่ทั้งหมด อย่างไรก็ตาม เฟรมเวิร์กจำนวนมากยังคงใช้ฮิวริสติกเดียวและกลยุทธ์การจัดกลุ่ม "คอมมอนส์" ด้วยเหตุผลบางประการ ซึ่งรวมถึงข้อกังวลที่ว่าคำขอ HTTP จำนวนมากอาจส่งผลเสียต่อประสิทธิภาพของเว็บไซต์
เบราว์เซอร์สามารถเปิดการเชื่อมต่อ TCP กับต้นทางเดียวได้ในจำนวนจำกัด (6 รายการสำหรับ Chrome) ดังนั้นการลดจำนวนก้อนที่ Bundler ส่งออกจะช่วยให้มั่นใจได้ว่าจำนวนคำขอทั้งหมดจะต่ำกว่าเกณฑ์นี้ อย่างไรก็ตาม การดำเนินการนี้ใช้ได้กับ HTTP/1.1 เท่านั้น การทำมัลติเพล็กซิงใน HTTP/2 ช่วยให้สตรีมคำขอหลายรายการแบบคู่ขนานได้โดยใช้การเชื่อมต่อเดียวผ่านต้นทางเดียว กล่าวคือ โดยทั่วไปแล้วเราไม่ต้องกังวลเกี่ยวกับการจำกัดจำนวนก้อนข้อมูลที่ Bundler ปล่อยออกมา
เบราว์เซอร์หลักทั้งหมดรองรับ 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 การบิลด์ฝั่งเซิร์ฟเวอร์เพื่อพิจารณาว่า Chunk เอาต์พุตใดที่ใช้โดย จุดแรกเข้าต่างๆ เราจึงสร้างไฟล์ 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 เคยใช้วิธีเดียวกันในการใช้ฮิวริสติกตามการใช้งาน เพื่อกำหนดโมดูลทั่วไป
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 ทุกคน ควรพิจารณาปรับปรุงกลยุทธ์การแบ่งกลุ่มของแอปพลิเคชันหากใช้แนวทาง Bundle "ทั่วไป" ขนาดใหญ่ ไม่ว่าจะเป็นเฟรมเวิร์กหรือเครื่องมือรวมโมดูลใดก็ตาม
- หากต้องการดูการเพิ่มประสิทธิภาพการแบ่งกลุ่มแบบเดียวกันที่ใช้กับแอปพลิเคชัน React แบบดั้งเดิม โปรดดูแอป React ตัวอย่างนี้ ซึ่งใช้ กลยุทธ์การแบ่งกลุ่มแบบละเอียดเวอร์ชันที่เรียบง่ายและช่วยให้คุณเริ่มใช้ตรรกะแบบเดียวกัน กับเว็บไซต์ได้
- สำหรับ Rollup ระบบจะสร้างก้อนข้อมูลแบบละเอียดโดยค่าเริ่มต้น ดู
manualChunks
หากต้องการกำหนดค่าลักษณะการทำงานด้วยตนเอง