بهبود عملکرد بار کردن صفحه Next.js و Gatsby با تکه‌تکه کردن دانه‌ای

راهبرد جدیدتر تکه‌تکه کردن webpack در Next.js و Gatsby کد تکراری را به‌حداقل می‌رساند تا عملکرد بار کردن صفحه را بهبود بخشد.

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

مقدمه

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

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

پیکربندی نقطه ورودی و بسته متداول

اگرچه مفهوم بسته‌بندی کردن همه کدهای ماژول هم‌رسانی‌شده در یک تکه واحد کاربردی است، اما محدودیت‌هایی دارد. واحدهایی که در همه نقاط ورودی هم‌رسانی نشده‌اند می‌توانند برای مسیرهایی که از آن‌ها استفاده نمی‌کنند بارگیری شوند که منجر به بارگیری کد بیشتر از حد لازم می‌شود. برای مثال، وقتی page1 تکه common را بار می‌کند، کد moduleC را بار می‌کند، هرچند page1 از moduleC استفاده نمی‌کند. به این دلیل، همراه با چند دلیل دیگر، webpack v4 این افزونه را به‌نفع افزونه جدیدی برداشت: SplitChunksPlugin.

تکه‌تکه کردن بهبودیافته

تنظیمات پیش‌فرض برای SplitChunksPlugin برای اکثر کاربران به‌خوبی کار می‌کند. چندین تکه تقسیم‌شده براساس تعدادی شرایط ایجاد می‌شود تا از واکشی کد تکراری در چندین مسیر جلوگیری شود.

بااین‌حال، بسیاری از چارچوب‌های وب که از این افزونه استفاده می‌کنند همچنان از رویکرد «تک‌مشترک» برای تقسیم‌بندی تکه‌ای پیروی می‌کنند. برای مثال، Next.js یک بسته commons تولید می‌کند که شامل هر واحدی است که در بیش‌از ۵۰٪ صفحه‌ها و همه وابستگی‌های چارچوب (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 اتخاذ کرد که کد غیرضروری را برای هر مسیر کاهش می‌دهد.

  • هر واحد شخص ثالث به‌اندازه کافی بزرگ (بزرگ‌تر از ۱۶۰ کیلوبایت) به تکه مجزای خودش تقسیم می‌شود
  • قطعه frameworks جداگانه‌ای برای وابستگی‌های چارچوب (react، react-dom، و غیره) ایجاد می‌شود
  • هر تعداد بخش هم‌رسانی‌شده که لازم باشد ایجاد می‌شود (تا ۲۵ بخش)
  • حداقل اندازه برای تولید یک قطعه به ۲۰ کیلوبایت تغییر کرده است

این استراتژی قطعه‌بندی دانه‌ای مزایای زیر را ارائه می‌دهد:

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

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

درخواست‌های اچ‌تی‌تی‌پی بیشتر

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

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

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

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

در میانگین سه اجرا از چندین آزمایش در یک صفحه وب، load، شروع پرداز و زمان‌های اولین پرداز محتوا همگی با تغییر حداکثر تعداد درخواست اولیه (از ۵ به ۱۵) تقریباً یکسان باقی ماندند. جالب است که فقط پس‌از تقسیم تهاجمی به صدها درخواست، متوجه سربار عملکردی جزئی شدیم.

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

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

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

کاهش بار داده جاوا اسکریپت با افزایش تکه‌تکه کردن

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

‫webpack از ۳۰ کیلوبایت به‌عنوان حداقل اندازه پیش‌فرض برای تولید تکه استفاده می‌کند. اما، جفت کردن maxInitialRequests مقدار ۲۵ با حداقل اندازه ۲۰ کیلوبایت منجر به ذخیره‌سازی بهتر در حافظه نهان شد.

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

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

وب‌سایت مجموع تغییرات جاوااسکریپت ٪ تفاوت
https://www.barnebys.com/ ‫-۲۳۸ کیلوبایت ‪-۲۳٪
https://sumup.com/ -۲۲۰ کیلوبایت ‫-٪۳۰
https://www.hashicorp.com/ ‫-۱۱ مگابایت ‫-۷۱٪
کاهش اندازه جاوا اسکریپت - در همه مسیرها (فشرده‌شده)

نسخه نهایی به‌طور پیش‌فرض در نسخه ۹.۲ عرضه شد.

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 برای اتخاذ یک استراتژی مشابه تکه‌تکه کردن دانه‌ای، آن‌ها همچنین متوجه کاهش قابل توجه جاوا اسکریپت در بسیاری از سایت‌های بزرگ شدند:

وب‌سایت مجموع تغییرات جاوااسکریپت ٪ تفاوت
https://www.gatsbyjs.org/ -۶۸۰ کیلوبایت ‫-۲۲٪
https://www.thirdandgrove.com/ ‫-۳۹۰ کیلوبایت ‫-٪۲۵
https://ghost.org/ ‫-۱٫۱ مگابایت ‫-۳۵٪
https://reactjs.org/ -۸۰ کیلوبایت ‪-۸٪
کاهش اندازه جاوا اسکریپت - در همه مسیرها (فشرده‌شده)

به درخواست ارسال نگاهی بیندازید تا متوجه شوید که آن‌ها چگونه این منطق را در پیکربندی webpack خود پیاده‌سازی کرده‌اند که به‌طور پیش‌فرض در نسخه ۲.۲۰.۷ ارائه شده است.

نتیجه‌گیری

مفهوم ارسال تکه‌های دانه‌ای مختص Next.js،‏ Gatsby، یا حتی webpack نیست. همه باید بهبود استراتژی تکه‌تکه کردن برنامه خود را در نظر بگیرند اگر از رویکرد بسته بزرگ «مشترک» پیروی می‌کند، صرف‌نظر از چارچوب یا بسته‌بندی ماژول استفاده‌شده.

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