تحسين أداء تحميل الصفحات في Next.js وGatsby باستخدام ميزة التقسيم الدقيق

تعمل استراتيجية جديدة لتقسيم حزمة الويب في Next.js وGatsby على تقليل الرمز المكرر لتحسين أداء تحميل الصفحة.

يعمل Chrome على التعاون من خلال الأدوات في منظومة JavaScript المتكاملة المفتوحة المصدر. تم مؤخرًا إدخال عدد من التحسينات الجديدة لتحسين أداء تحميل Next.js "غاتسبي" تتناول هذه المقالة استراتيجية تقسيم دقيقة محسَّنة. التي يتم شحنها الآن بشكل افتراضي في كلا إطاري العمل.

مقدمة

على غرار العديد من أُطر عمل الويب، يستخدم Next.js وGatsby webpack كحزمة أساسية. بِرْنَامِجْ. تم طرح الإصدار 3 من webpack. CommonsChunkPlugin لإتاحة وحدات إخراج مشتركة بين نقاط الإدخال المختلفة في "شائع" واحد (أو عدد قليل) مقطع (أو الأجزاء). يمكن تنزيل الرمز المشترك بشكل منفصل وتخزينه في ذاكرة التخزين المؤقت للمتصفح مبكرًا، تؤدي إلى تحسين أداء التحميل.

وأصبح هذا النمط شائعًا في العديد من أطر تطبيقات الصفحة الواحدة التي استخدمت نقطة دخول بدا الإعداد التالي على النحو التالي:

إعدادات الحزمة ونقطة الدخول الشائعة

على الرغم من أن مفهوم تجميع جميع التعليمات البرمجية للوحدة المشتركة في مقطع واحد له دلالات عملية والقيود. يمكن تنزيل الوحدات غير المشتركة في كل نقطة دخول للمسارات التي لا تستخدمها يؤدي إلى تنزيل عدد أكبر من اللازم من الرمز. على سبيل المثال، عند تحميل page1 مقطع common، فإنه يحمّل رمز moduleC على الرغم من أن page1 لا يستخدم moduleC. لهذا السبب، إلى جانب عدد قليل من الأسباب الأخرى، أزال الإصدار 4 من webpack المكوّن الإضافي لصالح علامة أولاً: SplitChunksPlugin

تقسيم محسّن

تكون الإعدادات التلقائية لـ SplitChunksPlugin مناسبة لمعظم المستخدمين. تُعد المقاطع المقسمة المتعددة يتم إنشاؤها استنادًا إلى عدد من الشروط لمنع جلب رمز مكرّر في مسارات متعدّدة.

ومع ذلك، لا تزال العديد من أطر عمل الويب التي تستخدم هذا المكون الإضافي تتبع "single-commons" طريقة معالجة المقطع التقسيم. على سبيل المثال، سينشئ 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 لمصدر واحد (6 فقط بالنسبة إلى Chrome)، لذا ومن خلال خفض عدد المقاطع التي تنتجها أداة التجميع، يمكن أن يضمن ذلك إجمالي عدد الطلبات يبقى أقل من هذا الحدّ. ومع ذلك، ينطبق هذا فقط على HTTP/1.1. تعدد الإرسال في HTTP/2 تسمح ببث طلبات متعددة بالتوازي باستخدام اتصال واحد على المصدر. بعبارة أخرى، لا داعي للقلق بشكل عام بشأن الحدّ من عدد المجموعات الصادرة عن أداة التجميع.

تتوافق جميع المتصفحات الرئيسية مع HTTP/2. فريقا Chrome وNext.js معرفة ما إذا كانت زيادة عدد الطلبات عن طريق تقسيم عنصر "commons" المفرد في Next.js مجموعة إلى أجزاء مشتركة متعددة على أداء التحميل بأي شكل من الأشكال. بدأوا بقياس أداء موقع واحد مع تعديل الحد الأقصى لعدد الطلبات المتوازية باستخدام maxInitialRequests الموقع.

أداء تحميل الصفحة مع زيادة عدد الطلبات

وفي متوسط ثلاث عمليات لتجارب متعددة على صفحة ويب واحدة، load، start-render وظل مقياس سرعة عرض المحتوى على الصفحة كما هو عند تغيير الحد الأقصى عدد الطلبات (من 5 إلى 15). ومن المثير للاهتمام أننا لاحظنا انخفاضًا طفيفًا في النفقات العامة للأداء فقط بعد تقسيمها بشدة على مئات الطلبات.

أداء تحميل الصفحة مع مئات الطلبات

وقد أظهر ذلك أن الالتزام بالحد الأدنى الموثوق به (من 20 إلى 25 طلبًا) قد حقق التوازن الصحيح بين أداء التحميل وكفاءة التخزين المؤقت بعد إجراء بعض الاختبارات المرجعية، تم تحديد 25 باعتباره عدد maxInitialRequest.

أدى تعديل الحد الأقصى لعدد الطلبات التي تحدث بالتوازي إلى حدوث أكثر من طلب وفصلها بشكل مناسب لكل نقطة دخول إلى حد كبير مقدار التعليمات البرمجية غير اللازمة للصفحة نفسها.

تقليل حمولة البيانات في JavaScript مع زيادة التقسيم

كانت هذه التجربة تتعلق فقط بتعديل عدد الطلبات لمعرفة ما إذا كان هناك أي تأثير سلبي في أداء تحميل الصفحة تشير النتائج إلى أنّ ضبط maxInitialRequests على كانت الإضافة 25 في الصفحة التجريبية مثالية لأنّها قلّلت حجم حمولة JavaScript بدون إبطاء. أسفل الصفحة. لا يزال إجمالي مقدار JavaScript المطلوب لترطيب الصفحة قائمًا نفس الشيء، وهو ما يفسر سبب عدم تحسّن أداء تحميل الصفحة بالضرورة مع مقدار التعليمات البرمجية.

يستخدم webpack 30 كيلوبايت كحد أدنى افتراضي لحجم المقطع المطلوب إنشاؤه. ومع ذلك، يمكن أن يساعد إقران بدلاً من ذلك، أدّى استخدام قيمة maxInitialRequests التي تبلغ 25 مع حد أدنى للحجم يبلغ 20 كيلوبايت إلى تحسين في التخزين المؤقت.

تقليل الحجم باستخدام المقاطع الدقيقة

تعتمد العديد من أطر العمل، بما في ذلك Next.js، على التوجيه من جهة العميل (الذي تتم معالجته بواسطة JavaScript) لإدخال علامات نص برمجي أحدث لكل انتقال للمسار. ولكن كيف يحددون هذه المجموعات الديناميكية مسبقًا في وقت الإنشاء؟

يستخدم 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 وراء علامة، حيث تم اختبارها على عدد المستخدمين في مرحلة مبكرة. رأى العديد منهم انخفاضًا كبيرًا في إجمالي عدد رموز JavaScript المستخدَمة في بأكمله:

الموقع الإلكتروني التغيير في إجمالي JavaScript نسبة الاختلاف
https://www.barnebys.com/ -238 كيلوبايت -23%
https://sumup.com/ -220 كيلوبايت -30%
https://www.hashicorp.com/ -11 ميغابايت -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)[\\/]/,
      },

ومن خلال تحسين إعدادات حزمة الويب لديها لاعتماد استراتيجية تقسيم دقيقة مماثلة، استطاعت الشركة أيضًا لاحظت انخفاضًا كبيرًا في JavaScript في العديد من المواقع الإلكترونية الكبيرة:

الموقع الإلكتروني التغيير في إجمالي JavaScript نسبة الاختلاف
https://www.gatsbyjs.org/ -680 كيلوبايت -22%
https://www.thirdandgrove.com/ -390 كيلوبايت -25%
https://ghost.org/ -1.1 ميغابايت -35%
https://reactjs.org/ -80 كيلوبايت -8‏%
تقليل حجم JavaScript في جميع المسارات (مضغوطة)

ألقِ نظرة على PR لفهم كيفية تنفيذ هذا المنطق في تهيئة حزمة الويب، والذي يتم شحنه تلقائيًا في الإصدار 2.20.7.

الخاتمة

لا يقتصر مفهوم شحن الأجزاء الدقيقة على Next.js أو Gatsby أو حتى webpack. كل الأقسام تحسين استراتيجية تقسيم التطبيق إذا كان يتبع مجموعة كبيرة من "الشائعة" نهج الحزمة، بصرف النظر عن أداة تجميع الوحدات المستخدمة أو إطار العمل أو الوحدة.

  • إذا كنت ترغب في رؤية تحسينات التقسيم نفسها المطبقة على تطبيق vanilla React، يمكنك الاطّلاع على نموذج React هذا التطبيق. تستخدم نسخة مبسّطة من استراتيجية التقسيم الدقيق ويمكن أن تساعدك في بدء تطبيق نوعًا من المنطق لموقعك.
  • بالنسبة إلى البيانات المجمّعة، يتم إنشاء المقاطع بشكل دقيق بشكل تلقائي. إلقاء نظرة على manualChunks إذا كنت تريد تنفيذ الإجراء يدويًا تكوين السلوك.