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

กลยุทธ์การรวมกลุ่มเว็บแพ็กแบบใหม่ใน Next.js และ Gatsby จะลดโค้ดที่ซ้ำกันเพื่อปรับปรุงประสิทธิภาพการโหลดหน้าเว็บ

Chrome กำลังร่วมงานกับเครื่องมือและ ในระบบนิเวศโอเพนซอร์สของ JavaScript เมื่อเร็วๆ นี้มีการเพิ่มประสิทธิภาพใหม่ๆ หลายรายการ เพิ่มเพื่อปรับปรุงประสิทธิภาพการโหลดของ Next.js และ Gatsby บทความนี้กล่าวถึงกลยุทธ์การแบ่งส่วนแบบละเอียดที่ได้รับการปรับปรุง ซึ่งตอนนี้มีการจัดส่งโดยค่าเริ่มต้นในทั้ง 2 เฟรมเวิร์กแล้ว

บทนำ

Next.js และ Gatsby ใช้ webpack เป็นแกนหลัก เช่นเดียวกับเว็บเฟรมเวิร์กอื่นๆ Bundler เปิดตัว Webpack v3 CommonsChunkPlugin เพื่อให้ดำเนินการต่อไปนี้ได้ โมดูลเอาต์พุตที่ใช้ร่วมกันระหว่างจุดแรกเข้าต่างๆ ใน "คอมมอนส์" เดียว (หรือไม่กี่รายการ) กลุ่ม (หรือ ) คุณสามารถดาวน์โหลดโค้ดที่แชร์แยกต่างหากและจัดเก็บไว้ในแคชของเบราว์เซอร์ตั้งแต่เนิ่นๆ ซึ่งอาจ ส่งผลให้ประสิทธิภาพการโหลดดีขึ้น

รูปแบบนี้เริ่มเป็นที่นิยมในเฟรมเวิร์กแอปพลิเคชันหน้าเว็บเดียวจำนวนมากที่นำจุดแรกเข้าและ การกําหนดค่าแพ็กเกจที่มีลักษณะดังนี้

Entrypoint และการกำหนดค่า Bundle ทั่วไป

แม้ว่าจะปฏิบัติได้จริง แต่แนวคิดของการรวมโค้ดโมดูลที่แชร์ไว้ทั้งหมดเป็นชุดเดียว โมดูลที่ไม่ได้แชร์ในทุกจุดแรกเข้าสามารถดาวน์โหลดสำหรับเส้นทางที่ไม่ได้ใช้โมดูลดังกล่าว ส่งผลให้มีการดาวน์โหลดโค้ดมากเกินกว่าที่จำเป็น เช่น เมื่อ page1 โหลดขึ้นมา กลุ่ม common จะโหลดรหัสสำหรับ moduleC แม้ว่า page1 จะไม่ได้ใช้ moduleC ก็ตาม ด้วยเหตุนี้ Webpack v4 จึงควรนำปลั๊กอินออกเพื่อเรียกปลั๊กอินใหม่ 1: SplitChunksPlugin

การแยกส่วนที่ปรับปรุงใหม่

การตั้งค่าเริ่มต้นสำหรับ SplitChunksPlugin เหมาะสำหรับผู้ใช้ส่วนใหญ่ ส่วนย่อยหลายๆ ส่วน ที่สร้างขึ้นโดยขึ้นอยู่กับเงื่อนไขต่างๆ เพื่อป้องกันการดึงข้อมูลโค้ดที่ซ้ำกันในหลายเส้นทาง

อย่างไรก็ตาม หลายเฟรมเวิร์กเว็บที่ใช้ปลั๊กอินนี้ยังคงเป็นไปตาม "single-commons" วิธีการแบ่งชิ้นส่วน การแยก ตัวอย่างเช่น Next.js จะสร้างแพ็กเกจ commons ที่มีโมดูลที่ ใช้ในหน้าเว็บมากกว่า 50% และทรัพยากร Dependency ของเฟรมเวิร์กทั้งหมด (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)[\\/]/,
      },
    },
  },

แม้ว่าการรวมโค้ดที่ขึ้นอยู่กับเฟรมเวิร์กไว้ในกลุ่มที่ใช้ร่วมกัน จะทำให้สามารถดาวน์โหลดและ แคชสำหรับจุดแรกเข้าใดๆ การเรียนรู้ตามการใช้งานของการรวมโมดูลทั่วไปที่ใช้ใน ครึ่งหน้าไม่มีประสิทธิภาพมากนัก การแก้ไขอัตราส่วนนี้จะทำให้เกิดผลลัพธ์ 1 ใน 2 แบบต่อไปนี้

  • หากลดอัตราส่วน ระบบจะดาวน์โหลดโค้ดที่ไม่จำเป็นมากขึ้น
  • หากคุณเพิ่มอัตราส่วน จะมีการทำรหัสซ้ำในหลายเส้นทาง

เพื่อแก้ปัญหานี้ Next.js จึงได้ใช้ การกำหนดค่าสำหรับSplitChunksPluginที่ลด โค้ดที่ไม่จำเป็นสำหรับเส้นทาง

  • โมดูลของบุคคลที่สามที่มีขนาดใหญ่พอ (มากกว่า 160 KB) จะแยกออกเป็นของตนเอง กลุ่ม
  • ระบบได้สร้างกลุ่ม frameworks แยกต่างหากสำหรับทรัพยากร Dependency ของเฟรมเวิร์ก (react, react-dom และ เป็นต้น)
  • สร้างกลุ่มที่แชร์ได้มากเท่าที่ต้องการ (สูงสุด 25 รายการ)
  • เปลี่ยนขนาดต่ำสุดของกลุ่มที่จะสร้างเปลี่ยนเป็น 20 KB

กลยุทธ์การแยกส่วนแบบละเอียดนี้มีประโยชน์ดังต่อไปนี้

  • เวลาในการโหลดหน้าเว็บดีขึ้น การปล่อยกลุ่มที่แชร์หลายกลุ่ม ไม่ใช่กลุ่มเดียว ลดจำนวนโค้ดที่ไม่จำเป็น (หรือซ้ำกัน) สำหรับจุดแรกเข้า
  • การแคชที่ดียิ่งขึ้นระหว่างการนำทาง การแยกไลบรารีขนาดใหญ่และทรัพยากร Dependency ของเฟรมเวิร์ก แยกเป็นส่วนๆ จะช่วยลดโอกาสที่จะทำให้แคชใช้งานไม่ได้ เนื่องจากทั้ง 2 อย่าง เปลี่ยนแปลงจนกว่าจะทำการอัปเกรด

คุณดูการกำหนดค่าทั้งหมดที่ 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

ประสิทธิภาพการโหลดหน้าเว็บด้วยจำนวนคำขอที่เพิ่มขึ้น

โดยเฉลี่ยจากการทดลองหลายๆ ครั้ง โดยเฉลี่ย 3 ครั้งบนหน้าเว็บ 1 หน้า 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 ของบิลด์เพื่อจับคู่ทรัพยากร 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

กลยุทธ์แบ่งส่วนแบบละเอียดใหม่นี้เปิดตัวเป็นครั้งแรกใน Next.js หลังแฟล็ก โดยมีการทดสอบใน จำนวนของผู้ใช้รายแรกๆ หลายคนพบว่า JavaScript ที่ใช้สำหรับ 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 เคยทำตามแนวทางเดียวกับการใช้แอปตามการใช้งาน การเรียนรู้สำหรับการกำหนดโมดูลทั่วไป:

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 กิโลไบต์ ลดลง 22%
https://www.thirdandgrove.com/ -390 กิโลไบต์ -25%
https://ghost.org/ -1.1 MB ลดลง 35%
https://reactjs.org/ -80 KB -8%
การลดขนาด JavaScript - ในทุกเส้นทาง (บีบอัด)

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

บทสรุป

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

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