لغة JavaScript مقسَّمة للرمز

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

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

وغالبًا ما يحدث ذلك لأنّ سلسلة التعليمات الرئيسية محظورة، بسبب تحليل JavaScript وتجميعها في سلسلة التعليمات الرئيسية. إذا استغرقت هذه العملية وقتًا طويلاً جدًا، فقد لا تستجيب عناصر الصفحة التفاعلية بسرعة كافية لإدخال المستخدم. ومن بين الحلول البديلة تحميل محتوى JavaScript الذي تحتاجه حتى تعمل الصفحة بشكل سليم، مع تأجيل تحميل محتوى JavaScript آخر لاحقًا من خلال أسلوب يُعرف باسم "تقسيم الرموز البرمجية". تركز هذه الوحدة على الأخير من هاتين الطريقتين.

الحدّ من تحليل JavaScript وتنفيذه أثناء بدء التشغيل من خلال تقسيم الرموز البرمجية

تعرض خدمة Lighthouse تحذيرًا عندما تستغرق عملية تنفيذ JavaScript وقتًا أطول من ثانيتين وتتعذّر تنفيذها عندما تستغرق أكثر من 3.5 ثانية. ويشكّل تحليل JavaScript وتنفيذه بشكل مفرط مشكلة محتملة في أي مرحلة من مراحل نشاط الصفحة، لأنّه من المحتمل أن يؤدي إلى زيادة تأخير الإدخال للتفاعل إذا كان الوقت الذي يتفاعل فيه المستخدم مع الصفحة يتزامن مع لحظة تشغيل مهام سلسلة التعليمات الرئيسية المسؤولة عن معالجة JavaScript وتنفيذها.

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

يشير تدقيق Lighthouse إلى أنّ الوقت المستغرق في تنفيذ كل ملف JavaScript تطلبه صفحتك يساعدك في تحديد النصوص البرمجية التي قد تكون مرشحة لتقسيم الرمز بالضبط. ويمكنك بعد ذلك استخدام أداة التغطية في "أدوات مطوري البرامج في Chrome" لتحديد أجزاء JavaScript للصفحة التي لا يتم استخدامها أثناء تحميل الصفحة.

يشكّل تقسيم الرمز طريقة مفيدة يمكنها تقليل حمولات JavaScript الأولية للصفحة. وهي تتيح لك تقسيم حزمة JavaScript إلى جزأين:

  • هناك حاجة إلى رمز JavaScript عند تحميل الصفحة، وبالتالي لا يمكن تحميلها في أي وقت آخر.
  • باقي رموز JavaScript التي يمكن تحميلها في وقت لاحق، وغالبًا ما تكون في الوقت الذي يتفاعل فيه المستخدم مع عنصر تفاعلي معيَّن على الصفحة.

يمكن تقسيم الرمز باستخدام البنية الديناميكية import(). وهذه البنية، على عكس عناصر <script> التي تطلب مورد JavaScript أثناء بدء التشغيل، تطلب مورد JavaScript في مرحلة لاحقة خلال دورة حياة الصفحة.

document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
  // Get the form validation named export from the module through destructuring:
  const { validateForm } = await import('/validate-form.mjs');

  // Validate the form:
  validateForm();
}, { once: true });

في مقتطف JavaScript السابق، لا يتم تنزيل وحدة validate-form.mjs وتحليلها وتنفيذها إلا عندما يتمويه المستخدم أيًا من حقول <input> في النموذج. في هذه الحالة، لا يتم تضمين مورد JavaScript المسؤول عن توجيه منطق التحقق من صحة النموذج إلا في الصفحة عندما يكون من المرجّح استخدامها فعليًا.

يمكن ضبط حِزم JavaScript، مثل webpack وParcel وRollup وesbuild، لتقسيم حِزم JavaScript إلى أجزاء أصغر عندما تصادف استدعاء import() ديناميكي في رمز المصدر. تقوم معظم هذه الأدوات بتنفيذ ذلك تلقائيًا، لكن يتم إنشاؤها على وجه التحديد تتطلب منك تفعيل هذا التحسين.

ملاحظات مفيدة حول تقسيم الرموز

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

استخدام حزمة إذا كان ذلك ممكنًا

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

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

يتجنب التجميعون أيضًا مشكلة شحن عدد كبير من الوحدات غير المجمعة عبر الشبكة. غالبًا ما تحتوي البُنى التي تستخدم وحدات JavaScript على أشجار وحدات كبيرة ومعقدة. عندما يتم فصل أشجار الوحدات، تمثل كل وحدة طلب HTTP منفصلاً، وقد يتأخر التفاعل في تطبيق الويب إذا لم تجمع الوحدات. على الرغم من أنّه من الممكن استخدام تلميح الموارد <link rel="modulepreload"> لتحميل أشجار الوحدات الكبيرة في أقرب وقت ممكن، يفضَّل استخدام حِزم JavaScript على مستوى أداء التحميل.

عدم إيقاف تجميع البث المباشر عن غير قصد

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

لديك طريقتان لضمان حدوث تجميع البث لتطبيق الويب في Chromium:

  • يجب تحويل رمز الإنتاج لتجنُّب استخدام وحدات JavaScript. وبإمكان الحِزم تحويل رمز مصدر JavaScript استنادًا إلى هدف تجميع، وغالبًا ما يكون الهدف محدّدًا لبيئة معيّنة. سيطبّق V8 تجميع البث على أي رمز JavaScript لا يستخدم وحدات، ويمكنك ضبط أداة الحزمة لتحويل رمز وحدة JavaScript إلى بنية لا تستخدم وحدات JavaScript وميزاتها.
  • إذا أردت شحن وحدات JavaScript إلى الإنتاج، استخدِم الإضافة .mjs. سواء كانت لغة JavaScript للإنتاج تستخدم وحدات أم لا، ما من نوع محتوى خاص بلغة JavaScript يستخدم وحدات مقارنةً بلغة JavaScript التي لا تستخدم هذه الوحدات. وفي حال استخدام الإصدار V8، يمكنك إيقاف البث المجمّع بشكل فعّال عند شحن وحدات JavaScript في الإنتاج باستخدام الإضافة .js. إذا استخدمت الإضافة .mjs في وحدات JavaScript، يمكن أن يضمن الإصدار V8 عدم تعطُّل تجميع البث لرمز JavaScript المستند إلى الوحدة.

لا تدع هذه الاعتبارات تثنيك عن استخدام تقسيم التعليمات البرمجية. يُشكّل تقسيم الرموز طريقة فعّالة لتقليل حمولات بيانات JavaScript الأولية للمستخدمين، ولكن باستخدام أداة تجميع ومعرفة كيفية الحفاظ على سلوك تجميع البث من الإصدار V8، يمكنك ضمان سرعة تحميل رمز JavaScript للإنتاج بالنسبة إلى المستخدمين على النحو المطلوب.

عرض توضيحي للاستيراد الديناميكي

حزمة ويب

يتم شحن webpack مع مكوّن إضافي اسمه SplitChunksPlugin، يتيح لك ضبط كيفية تقسيم الحزمة لملفات JavaScript. تتعرف حزمة الويب على كلتا عبارتَي import() الديناميكية والثابتة.import يمكن تعديل سلوك SplitChunksPlugin من خلال تحديد خيار chunks في ضبطه:

  • chunks: async هي القيمة التلقائية وتشير إلى طلبات import() الديناميكية.
  • تشير السمة chunks: initial إلى مكالمات import الثابتة.
  • تتناول chunks: all كلاً من عمليات الاستيراد الديناميكية import() والثابتة، ما يسمح لك بمشاركة مقاطع بين عمليات الاستيراد من async إلى initial.

بشكل تلقائي، عندما يصادف حزمة الويب عبارة import() ديناميكية، ينشئ مقطعًا منفصلاً لهذه الوحدة:

/* main.js */

// An application-specific chunk required during the initial page load:
import myFunction from './my-function.js';

myFunction('Hello world!');

// If a specific condition is met, a separate chunk is downloaded on demand,
// rather than being bundled with the initial chunk:
if (condition) {
  // Assumes top-level await is available. More info:
  // https://v8.dev/features/top-level-await
  await import('/form-validation.js');
}

ينتج عن التكوين الافتراضي لحزمة الويب لمقتطف التعليمة البرمجية السابق مقطعين منفصلين:

  • مقطع main.js، الذي يصنّفه حزمة الويب كمقطع initial، يتضمّن الوحدة main.js و./my-function.js.
  • المقطع async الذي يحتوي على form-validation.js فقط (يحتوي على تجزئة ملف في اسم المورد عند إعداده). لا يتم تنزيل هذا المقطع إلا إذا كان condition معًا.

تتيح لك هذه الإعدادات تأجيل تحميل مقطع form-validation.js إلى أن تكون هناك حاجة إليه. ويمكن أن يؤدي ذلك إلى تحسين سرعة استجابة التحميل عن طريق تقليل وقت تقييم النصوص البرمجية أثناء التحميل الأولي للصفحة. يتم تنزيل النص البرمجي وتقييمه للمقطع form-validation.js عند استيفاء شرط محدد، وفي هذه الحالة، يتم تنزيل الوحدة التي تم استيرادها ديناميكيًا. وقد يتمثل أحد الأمثلة في شرط يتم فيه تنزيل رمز polyfill لمتصفح معيّن فقط، أو تكون الوحدة النمطية المستوردة ضرورية لتفاعل المستخدم، كما في المثال السابق.

وفي المقابل، إنّ تغيير إعدادات SplitChunksPlugin لتحديد chunks: initial يضمن تقسيم الرمز البرمجي في المقاطع الأولية فقط. وهي عبارة عن أجزاء من المحتوى، مثل تلك التي تم استيرادها بشكل ثابت أو المدرَجة في سمة entry ضمن حزمة الويب. بالنظر إلى المثال السابق، سيكون المقطع الناتج عبارة عن تركيبة من form-validation.js و main.js في ملف نص برمجي واحد، ما قد يؤدي إلى سوء أداء التحميل الأولي للصفحة.

يمكن أيضًا ضبط خيارات SplitChunksPlugin لفصل النصوص البرمجية الأكبر إلى نصوص أصغر متعددة، مثلاً باستخدام الخيار maxSize لتوجيه حزمة الويب إلى تقسيم المقاطع إلى ملفات منفصلة إذا تجاوزت ما تم تحديده في maxSize. يمكن أن يؤدي تقسيم ملفات النصوص البرمجية الكبيرة إلى ملفات أصغر إلى تحسين سرعة استجابة التحميل، لأنه في بعض الحالات، يتم تقسيم عمل تقييم النص البرمجي الذي يستهلك وحدة المعالجة المركزية (CPU) إلى مهام أصغر، حيث تقل احتمالية حظر سلسلة التعليمات الرئيسية لفترات زمنية أطول.

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

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

عرض توضيحي لحزمة الويب

عرض توضيحي لحزمة الويب SplitChunksPlugin.

اختبِر معلوماتك

أي نوع من عبارة import يُستخدم عند تقسيم الرمز؟

import() ديناميكية.
إجابة صحيحة
قيمة import الثابتة:
يُرجى إعادة المحاولة.

أي نوع من عبارات import يجب أن يكون أعلى وحدة JavaScript، وليس في أي مكان آخر؟

import() ديناميكية.
يُرجى إعادة المحاولة.
قيمة import الثابتة:
إجابة صحيحة

عند استخدام SplitChunksPlugin في حزمة الويب، ما هو الفرق بين المقطع async والمقطع initial؟

يتم تحميل مقاطع async باستخدام العناصر الديناميكية import() ويتم تحميل المقاطع initial باستخدام السمة import الثابتة.
إجابة صحيحة
يتم تحميل مقاطع async باستخدام المقاطع الثابتة import وinitial باستخدام import() الديناميكي.
يُرجى إعادة المحاولة.

التالي: التحميل الكسول للصور وعناصر <iframe>

على الرغم من أنّه قد يكون نوعًا من الموارد مكلفًا إلى حد ما، إلا أنّ JavaScript ليس نوع الموارد الوحيد الذي يمكنك تأجيل تحميله. قد تكون عناصر الصور و<iframe> موارد مكلفة في حد ذاتها. كما هي الحال في لغة JavaScript، يمكنك تأجيل تحميل الصور وعنصر <iframe> من خلال التحميل الكسول لهما، كما هو موضّح في الوحدة التالية من هذه الدورة التدريبية.