عملکرد بارگذاری صفحه Next.js و گتسبی با تکه تکه شدن دانه بهبود یافته است

یک استراتژی جدیدتر انفصال بسته وب در Next.js و Gatsby کدهای تکراری را برای بهبود عملکرد بارگذاری صفحه به حداقل می رساند.

Chrome با ابزارها و چارچوب‌ها در اکوسیستم منبع باز جاوا اسکریپت همکاری می‌کند. اخیراً تعدادی بهینه سازی جدیدتر برای بهبود عملکرد بارگیری Next.js و Gatsby اضافه شده است. این مقاله یک استراتژی تقسیم دانه ای بهبود یافته را پوشش می دهد که اکنون به طور پیش فرض در هر دو چارچوب ارسال شده است.

مانند بسیاری از چارچوب‌های وب، Next.js و Gatsby از وب‌پک به عنوان بسته‌کننده اصلی خود استفاده می‌کنند. webpack v3 CommonsChunkPlugin معرفی کرد تا خروجی ماژول های مشترک بین نقاط ورودی مختلف را در یک تکه (یا چند قطعه) مشترک (یا تکه) ممکن کند. کد به اشتراک گذاشته شده را می توان به طور جداگانه دانلود کرد و در حافظه پنهان مرورگر ذخیره کرد که می تواند منجر به عملکرد بارگیری بهتر شود.

این الگو با بسیاری از فریم ورک‌های کاربردی تک صفحه‌ای که از یک نقطه ورودی و پیکربندی بسته‌ای استفاده می‌کنند که به شکل زیر است، محبوب شد:

نقطه ورودی مشترک و پیکربندی بسته نرم افزاری

اگرچه عملی است، مفهوم بسته‌بندی همه کدهای ماژول مشترک در یک تکه دارای محدودیت‌هایی است. ماژول‌هایی که در هر نقطه ورودی به اشتراک گذاشته نمی‌شوند را می‌توان برای مسیرهایی که از آن استفاده نمی‌کنند دانلود کرد و در نتیجه کد بیشتری نسبت به نیاز دانلود می‌شود. برای مثال، وقتی page1 قطعه common را بارگیری می‌کند، کد moduleC را بارگیری می‌کند، حتی اگر page1 از moduleC استفاده نمی‌کند. به همین دلیل، همراه با چند مورد دیگر، 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 کیلوبایت) به تکه های جداگانه خود تقسیم می شود
  • یک تکه frameworks جداگانه برای وابستگی های فریم ورک ایجاد می شود ( react ، react-dom ، و غیره)
  • تعداد تکه های مشترک به تعداد مورد نیاز ایجاد می شود (تا 25)
  • حداقل اندازه برای یک قطعه تولید شده به 20 کیلوبایت تغییر می کند

این استراتژی تکه تکه شدن دانه ای مزایای زیر را ارائه می دهد:

  • زمان بارگذاری صفحه بهبود یافته است . انتشار چند تکه مشترک، به جای یک تکه، مقدار کدهای غیر ضروری (یا تکراری) را برای هر نقطه ورودی به حداقل می رساند.
  • بهبود حافظه پنهان در حین پیمایش . تقسیم کتابخانه‌های بزرگ و وابستگی‌های فریمورک به تکه‌های جداگانه، احتمال عدم اعتبار کش را کاهش می‌دهد، زیرا بعید است که هر دو تا زمانی که ارتقاء داده شود، تغییر کنند.

می توانید کل پیکربندی را که Next.js در webpack-config.ts اتخاذ کرده است ببینید.

درخواست های HTTP بیشتر

SplitChunksPlugin اساس تکه تکه شدن دانه ای را تعریف کرد و استفاده از این رویکرد در چارچوبی مانند Next.js مفهوم کاملا جدیدی نبود. با این حال، بسیاری از چارچوب‌ها به چند دلیل همچنان به استفاده از یک استراتژی اکتشافی و دسته‌ای «مشترک» ادامه دادند. این شامل این نگرانی است که بسیاری از درخواست های HTTP بیشتر می توانند بر عملکرد سایت تأثیر منفی بگذارند.

مرورگرها فقط می‌توانند تعداد محدودی اتصال TCP را به یک مبدا باز کنند (۶ مورد برای Chrome)، بنابراین به حداقل رساندن تعداد تکه‌های خروجی توسط یک بسته‌کننده می‌تواند تضمین کند که تعداد کل درخواست‌ها زیر این آستانه باقی می‌ماند. با این حال، این فقط برای HTTP/1.1 صادق است. Multiplexing در HTTP/2 اجازه می دهد تا چندین درخواست به صورت موازی با استفاده از یک اتصال واحد در یک مبدا پخش شوند. به عبارت دیگر، ما به طور کلی نیازی به نگرانی در مورد محدود کردن تعداد تکه های منتشر شده توسط باندلر خود نداریم.

همه مرورگرهای اصلی از HTTP/2 پشتیبانی می کنند. تیم‌های Chrome و Next.js می‌خواستند ببینند که آیا افزایش تعداد درخواست‌ها با تقسیم کردن دسته «مشترک» Next.js به چند تکه مشترک، بر عملکرد بارگیری تأثیر می‌گذارد یا خیر. آنها با اندازه گیری عملکرد یک سایت واحد شروع کردند و در عین حال حداکثر تعداد درخواست های موازی را با استفاده از ویژگی maxInitialRequests تغییر دادند.

عملکرد بارگذاری صفحه با افزایش تعداد درخواست ها

در میانگین سه اجرای آزمایشی چندگانه در یک صفحه وب، زمان load ، شروع رندر و First Contentful Paint همگی در هنگام تغییر حداکثر تعداد درخواست اولیه (از 5 تا 15) تقریباً یکسان باقی می مانند. به اندازه کافی جالب توجه است که ما تنها پس از تقسیم تهاجمی به صدها درخواست متوجه افزایش عملکرد جزئی شدیم.

عملکرد بارگیری صفحه با صدها درخواست

این نشان داد که ماندن در یک آستانه قابل اعتماد (20 تا 25 درخواست) تعادل مناسبی بین عملکرد بارگذاری و کارایی ذخیره سازی ایجاد کرد. پس از چند آزمایش پایه، 25 به عنوان maxInitialRequest انتخاب شد.

تغییر حداکثر تعداد درخواست‌هایی که به صورت موازی اتفاق می‌افتند منجر به بیش از یک بسته مشترک منفرد شد و جداسازی مناسب آن‌ها برای هر نقطه ورودی به طور قابل‌توجهی میزان کدهای غیرضروری را برای همان صفحه کاهش داد.

کاهش حجم بار جاوا اسکریپت با افزایش انقباض

این آزمایش فقط در مورد تغییر تعداد درخواست‌ها بود تا ببینیم آیا تأثیر منفی بر عملکرد بارگذاری صفحه وجود دارد یا خیر. نتایج نشان می‌دهد که تنظیم maxInitialRequests روی 25 در صفحه آزمایشی بهینه بود، زیرا حجم بار بار جاوا اسکریپت را بدون کاهش سرعت صفحه کاهش می‌داد. مقدار کل جاوا اسکریپت مورد نیاز برای هیدراته کردن صفحه همچنان تقریباً ثابت مانده است، که توضیح می دهد که چرا عملکرد بارگذاری صفحه لزوماً با کاهش مقدار کد بهبود نمی یابد.

وب پک از 30 کیلوبایت به عنوان حداقل اندازه پیش فرض برای تولید یک قطعه استفاده می کند. با این حال، جفت کردن مقدار maxInitialRequests 25 با حداقل اندازه 20 کیلوبایت در عوض منجر به ذخیره سازی بهتر می شود.

کاهش اندازه با تکه های دانه ای

بسیاری از چارچوب‌ها، از جمله Next.js، برای تزریق برچسب‌های اسکریپت جدیدتر برای هر انتقال مسیر، به مسیریابی سمت مشتری (که توسط جاوا اسکریپت مدیریت می‌شود) متکی هستند. اما چگونه آنها این تکه های پویا را در زمان ساخت از پیش تعیین می کنند؟

Next.js از یک فایل مانیفست ساخت سمت سرور استفاده می کند تا تعیین کند کدام تکه های خروجی توسط نقاط ورودی مختلف استفاده می شود. برای ارائه این اطلاعات به کلاینت نیز، یک فایل مانیفست ساخت در سمت کلاینت خلاصه شده ایجاد شد تا تمام وابستگی ها را برای هر نقطه ورودی ترسیم کند.

// 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 پشت پرچم منتشر شد، جایی که روی تعدادی از کاربران اولیه آزمایش شد. بسیاری شاهد کاهش قابل توجهی در کل جاوا اسکریپت مورد استفاده برای کل سایت خود بودند:

وب سایت کل تغییر JS درصد تفاوت
https://www.barnebys.com/ -238 کیلوبایت -23٪
https://sumup.com/ -220 کیلوبایت -30٪
https://www.hashicorp.com/ -11 مگابایت -71٪
کاهش اندازه جاوا اسکریپت - در همه مسیرها (فشرده شده)

نسخه نهایی به صورت پیش فرض در نسخه 9.2 ارسال شد.

گتسبی

گتسبی از همان رویکرد استفاده از اکتشافی مبتنی بر استفاده برای تعریف ماژول های رایج استفاده می کرد:

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)[\\/]/,
      },

با بهینه سازی پیکربندی بسته وب خود برای اتخاذ یک استراتژی تقسیم دانه ای مشابه، آنها همچنین متوجه کاهش قابل توجه جاوا اسکریپت در بسیاری از سایت های بزرگ شدند:

وب سایت کل تغییر JS درصد تفاوت
https://www.gatsbyjs.org/ -680 کیلوبایت -22٪
https://www.thirdandgrove.com/ -390 کیلوبایت -25٪
https://ghost.org/ -1.1 مگابایت -35٪
https://reactjs.org/ -80 کیلوبایت -8٪
کاهش اندازه جاوا اسکریپت - در همه مسیرها (فشرده شده)

نگاهی به روابط عمومی بیندازید تا بفهمید چگونه آنها این منطق را در پیکربندی بسته وب خود پیاده سازی کردند، که به طور پیش فرض در نسخه 2.20.7 ارسال شده است.

نتیجه گیری

مفهوم حمل و نقل قطعات دانه ای مختص Next.js، Gatsby یا حتی وب پک نیست. همه باید در نظر داشته باشند که استراتژی انفصال برنامه خود را در صورتی که از یک رویکرد بسته بزرگ "مشترک" پیروی می کند، بدون توجه به چارچوب یا بسته ماژول مورد استفاده، در نظر بگیرند.

  • اگر می‌خواهید همان بهینه‌سازی‌های تکه تکه‌ای را در یک برنامه وانیلی React اعمال کنید، به این نمونه برنامه React نگاهی بیندازید. از یک نسخه ساده شده از استراتژی تکه تکه سازی دانه ای استفاده می کند و می تواند به شما کمک کند تا منطق مشابهی را در سایت خود اعمال کنید.
  • برای Rollup، تکه ها به طور پیش فرض به صورت دانه ای ایجاد می شوند. اگر می خواهید رفتار را به صورت دستی پیکربندی کنید، به manualChunks نگاهی بیندازید.