กลยุทธ์การแยกไฟล์ webpack เวอร์ชันใหม่ใน 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
เหมาะกับผู้ใช้ส่วนใหญ่ ระบบจะสร้างกลุ่มที่แยกหลายกลุ่มโดยขึ้นอยู่กับเงื่อนไขหลายรายการเพื่อป้องกันการดึงข้อมูลโค้ดที่ซ้ำกันหลายเส้นทาง
อย่างไรก็ตาม เฟรมเวิร์กเว็บหลายรายการที่ใช้ปลั๊กอินนี้ยังคงใช้แนวทาง "คอมมอนส์แบบเดี่ยว" ในการแยกข้อมูลออกเป็นกลุ่ม ตัวอย่างเช่น 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 ไม่ใช่แนวคิดใหม่ทั้งหมด อย่างไรก็ตาม เฟรมเวิร์กหลายรายการยังคงใช้กลยุทธ์การเชื่อมโยงแบบเฮิวริสติกและ "คอมมอนส์" รายการเดียวต่อไปด้วยเหตุผลบางประการ ซึ่งรวมถึงข้อกังวลที่ว่าคำขอ HTTP จำนวนมากอาจส่งผลเสียต่อประสิทธิภาพของเว็บไซต์
เนื่องจากเบราว์เซอร์สามารถเปิดการเชื่อมต่อ TCP ไปยังต้นทางแห่งเดียวได้เพียงจำนวนจำกัด (6 รายการสำหรับ Chrome) การลดจำนวนชิ้นงานที่เครื่องมือรวมออกจึงช่วยให้มั่นใจได้ว่าจำนวนคำขอทั้งหมดจะไม่เกินเกณฑ์นี้ แต่จะใช้ได้กับ HTTP/1.1 เท่านั้น การทำมัลติเพล็กซิงใน HTTP/2 ช่วยให้คุณสตรีมคำขอหลายรายการพร้อมกันได้โดยใช้การเชื่อมต่อเดียวจากแหล่งที่มาเดียว กล่าวคือ โดยทั่วไปแล้วเราไม่จำเป็นต้องกังวลเกี่ยวกับการจำกัดจำนวนกลุ่มที่เครื่องมือรวมของเราสร้างขึ้น
เบราว์เซอร์หลักทั้งหมดรองรับ HTTP/2 ทีม Chrome และ Next.js ต้องการดูว่าการเพิ่มขึ้นของคำขอโดยการแยกกลุ่ม "คอมมอนส์" รายการเดียวของ 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 ทุกคนควรพิจารณาปรับปรุงกลยุทธ์การแบ่งกลุ่มของแอปพลิเคชันหากใช้แนวทางการรวมกลุ่ม "คอมมอนส์" ขนาดใหญ่ ไม่ว่าจะใช้เฟรมเวิร์กหรือเครื่องมือรวมโมดูลใดก็ตาม
- หากต้องการดูการเพิ่มประสิทธิภาพการแบ่งกลุ่มเดียวกันกับที่ใช้กับแอปพลิเคชัน React เวอร์ชันพื้นฐาน ให้ดูตัวอย่างแอป React นี้ ซึ่งใช้กลยุทธ์การแบ่งกลุ่มแบบละเอียดเวอร์ชันที่เข้าใจง่าย และช่วยให้คุณเริ่มใช้ตรรกะแบบเดียวกันกับเว็บไซต์ได้
- สําหรับการรวม ระบบจะสร้างข้อมูลโค้ดแบบละเอียดโดยค่าเริ่มต้น โปรดดูหัวข้อ
manualChunks
หากต้องการกําหนดค่าลักษณะการทํางานด้วยตนเอง