تساعد استراتيجية تقسيم webpack الأحدث في Next.js وGatsby على تقليل الرموز المكرّرة لتحسين أداء تحميل الصفحات.
يتعاون Chrome مع الأدوات وأُطر العمل في منظومة JavaScript المتكاملة المفتوحة المصدر. تمت إضافة عدد من التحسينات الجديدة مؤخرًا لتحسين أداء تحميل Next.js وGatsby. تتناول هذه المقالة استراتيجية محسّنة لتقسيم البيانات إلى أجزاء دقيقة يتم شحنها الآن تلقائيًا في كلا الإطارين.
مقدمة
مثل العديد من أُطر عمل الويب، يستخدم Next.js وGatsby webpack كأداة تجميع أساسية. قدّم الإصدار الثالث من webpack CommonsChunkPlugin لإتاحة إخراج الوحدات المشتركة بين نقاط دخول مختلفة في جزء (أو أجزاء) "مشتركة" واحدة (أو قليلة). يمكن تنزيل الرمز المشترَك بشكل منفصل وتخزينه في ذاكرة التخزين المؤقت للمتصفّح في وقت مبكر، ما قد يؤدي إلى تحسين أداء التحميل.
أصبح هذا النمط شائعًا مع العديد من أُطر عمل التطبيقات من صفحة واحدة التي تعتمد على نقطة دخول وإعدادات حزمة تبدو على النحو التالي:

على الرغم من أنّ مفهوم تجميع كل الرموز البرمجية للوحدات المشتركة في حزمة واحدة هو مفهوم عملي، إلا أنّه ينطوي على بعض القيود. يمكن تنزيل الوحدات غير المشتركة في كل نقطة دخول للمسارات التي لا تستخدمها،
ما يؤدي إلى تنزيل رمز أكثر من اللازم. على سبيل المثال، عندما يحمّل page1 الجزء common، يتم تحميل الرمز لـ moduleC على الرغم من أنّ page1 لا يستخدم moduleC.
لهذا السبب، إلى جانب بعض الأسباب الأخرى، أزال الإصدار 4 من webpack المكوّن الإضافي لصالح مكوّن جديد: 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 بمصدر واحد (6 لمتصفّح Chrome)، لذا فإنّ تقليل عدد الأجزاء التي ينتجها برنامج التجميع يمكن أن يضمن بقاء إجمالي عدد الطلبات أقل من هذا الحدّ. ومع ذلك، ينطبق هذا فقط على HTTP/1.1. يتيح الإرسال المتعدّد في HTTP/2 إمكانية بث طلبات متعددة بالتوازي باستخدام اتصال واحد عبر مصدر واحد. بعبارة أخرى، لا داعي للقلق بشأن الحدّ من عدد الأجزاء التي تصدرها أداة التجميع.
تتوافق جميع المتصفّحات الرئيسية مع بروتوكول HTTP/2. أراد فريقا Chrome وNext.js معرفة ما إذا كانت زيادة عدد الطلبات من خلال تقسيم حزمة "commons" الواحدة في Next.js إلى أجزاء مشتركة متعددة ستؤثر في أداء التحميل بأي شكل من الأشكال. بدأوا بقياس أداء موقع إلكتروني واحد أثناء تعديل الحد الأقصى لعدد الطلبات المتزامنة باستخدام السمة maxInitialRequests.

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

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

كانت هذه التجربة تهدف فقط إلى تعديل عدد الطلبات لمعرفة ما إذا كان هناك أي تأثير سلبي على أداء تحميل الصفحة. تشير النتائج إلى أنّ ضبط maxInitialRequests على
25 في صفحة الاختبار كان مثاليًا لأنّه قلّل حجم حمولة JavaScript بدون إبطاء
الصفحة. ظلّ إجمالي حجم JavaScript المطلوب لتحميل الصفحة كما هو تقريبًا، ما يفسّر سبب عدم تحسُّن أداء تحميل الصفحة بالضرورة مع تقليل حجم الرمز البرمجي.
يستخدم webpack 30 كيلوبايت كحدّ أدنى تلقائي لحجم الجزء الذي سيتم إنشاؤه. ومع ذلك، فإنّ استخدام قيمة 25 لـ maxInitialRequests مع حدّ أدنى للحجم يبلغ 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 خلف علامة، حيث تم اختبارها على عدد من المستخدمين في مرحلة مبكرة. لاحظ الكثيرون انخفاضًا كبيرًا في إجمالي JavaScript المستخدَم في مواقعهم الإلكترونية:
| الموقع الإلكتروني | إجمالي التغيير في JavaScript | نسبة الاختلاف |
|---|---|---|
| https://www.barnebys.com/ | -238 كيلوبايت | -23% |
| https://sumup.com/ | -220 كيلوبايت | -30% |
| https://www.hashicorp.com/ | -11 ميغابايت | -71% |
وتم طرح الإصدار النهائي تلقائيًا في الإصدار 9.2.
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 لاعتماد استراتيجية تقسيم دقيقة مماثلة، لاحظوا أيضًا انخفاضًا كبيرًا في حجم 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% |
يمكنك الاطّلاع على طلب الدمج لفهم كيفية تنفيذ هذا المنطق في إعدادات webpack، والذي يتم شحنه تلقائيًا في الإصدار 2.20.7.
الخاتمة
لا يقتصر مفهوم شحن الأجزاء الدقيقة على Next.js أو Gatsby أو حتى webpack. يجب على الجميع التفكير في تحسين استراتيجية تقسيم التطبيق إذا كان يتّبع نهج حزمة "المشتركات" الكبيرة، بغض النظر عن إطار العمل أو حزمة الوحدات المستخدَمة.
- يمكن أيضًا تطبيق تحسينات التقسيم نفسها على تطبيق React عادي باستخدام نسخة مبسّطة من استراتيجية التقسيم الدقيق في إعدادات webpack، ما يساعدك في البدء في تطبيق نوع المنطق نفسه على موقعك الإلكتروني.
- بالنسبة إلى Rollup، يتم إنشاء الأجزاء بشكل دقيق تلقائيًا. يمكنك الاطّلاع على
manualChunksإذا أردت ضبط السلوك يدويًا.