تقليل حمولات JavaScript من خلال تقسيم الرمز

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

يعرض هذا الدرس التطبيقي حول الترميز كيفية استخدام تقسيم الرموز لتحسين أداء تطبيق بسيط يفرز ثلاثة أرقام.

تعرض نافذة متصفّح تطبيقًا بعنوان "أداة الترتيب السحرية" يتضمّن ثلاثة حقول لإدخال الأرقام وزرّ ترتيب.

القياس

كما هو الحال دائمًا، من المهم أولاً قياس مستوى أداء الموقع الإلكتروني قبل محاولة إضافة أي تحسينات.

  1. لمعاينة الموقع الإلكتروني، اضغط على عرض التطبيق. ثم اضغط على ملء الشاشة ملء الشاشة.
  2. اضغط على Ctrl ‏+ Shift ‏+ J (أو Command ‏+ Option ‏+ J على نظام التشغيل Mac) لفتح DevTools.
  3. انقر على علامة التبويب الشبكة.
  4. ضَع علامة في مربّع الاختيار إيقاف ذاكرة التخزين المؤقت.
  5. أعِد تحميل التطبيق.

لوحة الشبكة تعرض حِزمة JavaScript بحجم 71.2 كيلوبايت

71.2 كيلوبايت من JavaScript لترتيب بعض الأرقام في تطبيق بسيط What gives?

في رمز المصدر (src/index.js)، يتم استيراد مكتبة lodash واستخدامها في هذا التطبيق. توفّر مكتبة Lodash العديد من الدوالّ المساعدة المُفيدة، ولكن يتم استخدام طريقة واحدة فقط من الحزمة هنا. إنّ تثبيت واستخدام مكتبات تابعة لجهات خارجية بالكامل في حال استخدام جزء صغير فقط منها هو خطأ شائع.

تحسين

هناك بضع طرق لتقليل حجم الحِزمة:

  1. كتابة طريقة ترتيب مخصّصة بدلاً من استيراد مكتبة تابعة لجهة خارجية
  2. استخدام طريقة Array.prototype.sort() المضمّنة للترتيب رقميًا
  3. استيراد طريقة sortBy فقط من lodash وليس المكتبة بأكملها
  4. لا تنزِّل الرمز للترتيب إلا عندما ينقر المستخدم على الزر.

الخياران 1 و2 هما طريقتان مناسبتان تمامًا لتقليل حجم الحِزمة (وهما سيُعدّان الخيارَين الأكثر منطقية لتطبيق حقيقي). ومع ذلك، لن يتم استخدام هذه الأدوات في هذا الدليل التعليمي لأغراض تعليمية 😈.

يساعد الخياران 3 و4 في تحسين أداء هذا التطبيق. تغطي الأقسام القليلة التالية من هذا الدرس التطبيقي حول الترميز هذه الخطوات. مثل أي دورة تدريبية لترميز، حاوِل دائمًا كتابة الرمز بنفسك بدلاً من نسخه ولصقه.

استيراد ما تحتاج إليه فقط

يجب تعديل بعض الملفات لاستيراد الطريقة الوحيدة من lodash فقط. في البداية، استبدِل هذا الاعتماد في package.json:

"lodash": "^4.7.0",

من خلال تنفيذ ما يلي:

"lodash.sortby": "^4.7.0",

الآن في src/index.js، استورِد هذه الوحدة المحدّدة:

import "./style.css";
import _ from "lodash";
import sortBy from "lodash.sortby";

عدِّل طريقة ترتيب القيم:

form.addEventListener("submit", e => {
  e.preventDefault();
  const values = [input1.valueAsNumber, input2.valueAsNumber, input3.valueAsNumber];
  const sortedValues = _.sortBy(values);
  const sortedValues = sortBy(values);

  results.innerHTML = `
    <h2>
      ${sortedValues}
    </h2>
  `
});

أعِد تحميل التطبيق وافتح "أدوات مطوّري البرامج"، ثم اطّلِع على لوحة الشبكة مجددًا.

لوحة الشبكة تعرض حِزمة JavaScript بحجم 15.2 كيلوبايت

بالنسبة إلى هذا التطبيق، تم تقليل حجم الحِزمة بأكثر من 4 مرات من خلال جهد بسيط جدًا، ولكن لا يزال هناك مجال للتحسين.

تقسيم الرموز البرمجية

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

يمكن تقسيم الحزمة الواحدة المستخدَمة في هذا التطبيق إلى مقطعَين مختلفَين:

  • الشخص المسؤول عن الرمز الذي يشكل مسارنا الأولي
  • مقطع ثانوي يحتوي على رمز التصنيف

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

ابدأ بإزالة استيراد المستوى الأعلى لطريقة الترتيب في src/index.js:

import sortBy from "lodash.sortby";

واستورِده ضمن أداة معالجة الحدث التي يتم تفعيلها عند الضغط على الزر:

form.addEventListener("submit", e => {
  e.preventDefault();
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

تشكّل ميزة import() جزءًا من اقتراح (في المرحلة 3 حاليًا من عملية TC39) لتضمين إمكانية استيراد وحدة ديناميكيًا. سبق أن ضمّن webpack ميزة تتيح ذلك وتتّبع البنية النحوية نفسها التي وضعها المقترح.

تعرض import() وعدًا، وعند حلّه، يتم توفير العبارة المحدّدة التي يتم تقسيمها إلى جزء منفصل. بعد عرض الوحدات، يتم استخدام module.default للإشارة إلى عملية التصدير الافتراضية التي يوفّرها lodash. يتم ربط الوعد بسلسلة أخرى من .then التي تستدعي طريقة sortInput لترتيب قيم الإدخال الثلاث. في نهاية سلسلة الوعد،يتم استخدام catch() للتعامل مع الحالات التي يتم فيها رفض الوعد بسبب خطأ.

آخر إجراء يجب اتّخاذه هو كتابة طريقة sortInput في نهاية الملف. يجب أن تكون هذه دالة تعرِض دالة تستخدِم الطريقة المستورَدة من lodash.sortBy. يمكن بعد ذلك للدالة المتداخلة ترتيب قيم الإدخال الثلاث وتعديل نموذج DOM.

const sortInput = () => {
  return (sortBy) => {
    const values = [
      input1.valueAsNumber,
      input2.valueAsNumber,
      input3.valueAsNumber
    ];
    const sortedValues = sortBy(values);

    results.innerHTML = `
      <h2>
        ${sortedValues}
      </h2>
    `
  };
}

مراقب

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

لوحة الشبكة تعرض حِزمة JavaScript بحجم 2.7 كيلوبايت

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

لوحة الشبكة تعرض حِزمة JavaScript بحجم 2.7 كيلوبايت متبوعة بحِزمة JavaScript بحجم 13.9 كيلوبايت.

لاحظ كيف يتم ترتيب الأرقام.

الخاتمة

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

واجهة مستخدم التحميل الكسول

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

وحدات العُقد التابعة لجهات خارجية ذات التحميل الكسول

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

التحميل الكسول باستخدام إطار عمل JavaScript

إنّ العديد من أطر العمل والمكتبات الشائعة التي تستخدم حِزمة الويب توفّر تجريدات لتسهيل "التحميل الكسول" مقارنةً باستخدام عمليات الاستيراد الديناميكية في منتصف التطبيق.

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

التحميل المسبق والجلب المسبق

استفِد قدر الإمكان من تلميحات المتصفِّح، مثل <link rel="preload"> أو <link rel="prefetch">، لتجربة تحميل الوحدات المُهمة في وقت أسرع، لأنّ webpack يتيح استخدام كلا التلميحين من خلال استخدام ميزة التعليقات المفيدة في عبارات الاستيراد. يتوفر المزيد من التفاصيل في دليل التحميل المُسبق للأجزاء المهمة.

التحميل الكسول أكثر من مجرد رمز

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