ปรับปรุงประสิทธิภาพการโหลดหน้าเว็บของ Next.js และ Gatsby ด้วยการแบ่งข้อมูลเป็นกลุ่มแบบละเอียด

กลยุทธ์การแบ่งกลุ่ม 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 กลุ่ม และการแยกกลุ่มอย่างเหมาะสมสําหรับแต่ละจุดแรกเข้าช่วยลดจํานวนโค้ดที่ไม่จําเป็นสําหรับหน้าเดียวกันได้อย่างมาก

การลดเพย์โหลด JavaScript ด้วยการเพิ่มการแบ่งกลุ่ม

การทดสอบนี้เกี่ยวข้องกับการแก้ไขจํานวนคําขอเท่านั้นเพื่อดูว่าจะมีผลกระทบเชิงลบต่อประสิทธิภาพการโหลดหน้าเว็บหรือไม่ ผลลัพธ์ชี้ให้เห็นว่าการตั้งค่า 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

กลยุทธ์การแบ่งกลุ่มแบบละเอียดใหม่นี้เปิดตัวครั้งแรกใน 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%
การลดขนาด JavaScript - ในเส้นทางทั้งหมด (บีบอัด)

ระบบจะจัดส่งเวอร์ชันสุดท้ายโดยค่าเริ่มต้นในเวอร์ชัน 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%
การลดขนาด JavaScript - ในเส้นทางทั้งหมด (บีบอัด)

โปรดดูPR เพื่อทําความเข้าใจว่าทีมได้นําตรรกะนี้ไปใช้กับการกำหนดค่า webpack อย่างไร ซึ่งจะมาพร้อมกับ v2.20.7 โดยค่าเริ่มต้น

บทสรุป

แนวคิดในการส่งข้อมูลแบบละเอียดไม่ได้จำกัดเฉพาะ Next.js, Gatsby หรือแม้แต่ webpack ทุกคนควรพิจารณาปรับปรุงกลยุทธ์การแบ่งกลุ่มของแอปพลิเคชันหากใช้แนวทางการรวมกลุ่ม "คอมมอนส์" ขนาดใหญ่ ไม่ว่าจะใช้เฟรมเวิร์กหรือเครื่องมือรวมโมดูลใดก็ตาม

  • หากต้องการดูการเพิ่มประสิทธิภาพการแบ่งกลุ่มเดียวกันกับที่ใช้กับแอปพลิเคชัน React เวอร์ชันพื้นฐาน ให้ดูตัวอย่างแอป React นี้ ซึ่งใช้กลยุทธ์การแบ่งกลุ่มแบบละเอียดเวอร์ชันที่เข้าใจง่าย และช่วยให้คุณเริ่มใช้ตรรกะแบบเดียวกันกับเว็บไซต์ได้
  • สําหรับการรวม ระบบจะสร้างข้อมูลโค้ดแบบละเอียดโดยค่าเริ่มต้น โปรดดูหัวข้อmanualChunks หากต้องการกําหนดค่าลักษณะการทํางานด้วยตนเอง