يؤثّر تحميل موارد JavaScript الكبيرة بشكل كبير في سرعة الصفحة. يمكن أن يؤدي تقسيم JavaScript إلى أجزاء أصغر وتنزيل ما يلزم فقط لتشغيل الصفحة أثناء بدء التشغيل إلى تحسين سرعة استجابة التحميل للصفحة بشكل كبير، ما يؤدي بدوره إلى تحسين مدة التفاعل مع أول عنصر مرئي (INP) للصفحة.
عندما تنزّل الصفحة ملفات JavaScript كبيرة الحجم وتحلّلها وتجمّعها، قد تصبح غير مستجيبة لفترات من الوقت. تكون عناصر الصفحة مرئية لأنّها جزء من ترميز HTML الأولي للصفحة ويتم تنسيقها باستخدام CSS. ومع ذلك، قد يتم تحليل وتنفيذ JavaScript المطلوب لتشغيل هذه العناصر التفاعلية، بالإضافة إلى النصوص البرمجية الأخرى التي يتم تحميلها بواسطة الصفحة، لكي تعمل هذه العناصر. ونتيجة لذلك، قد يشعر المستخدم بأنّ التفاعل تأخّر بشكل كبير أو حتى توقّف تمامًا.
يحدث ذلك غالبًا لأنّه يتم حظر سلسلة التعليمات الرئيسية أثناء تحليل JavaScript وتجميعها على سلسلة التعليمات الرئيسية. إذا استغرقت هذه العملية وقتًا طويلاً جدًا، قد لا تستجيب عناصر الصفحة التفاعلية بسرعة كافية لبيانات المستخدم. أحد الحلول لهذه المشكلة هو تحميل رموز JavaScript اللازمة فقط لتعمل الصفحة، مع تأجيل تحميل رموز JavaScript الأخرى إلى وقت لاحق من خلال أسلوب يُعرف باسم تقسيم الرموز. تركّز هذه الوحدة على الأسلوب الثاني.
تقليل تحليل JavaScript وتنفيذه أثناء بدء التشغيل من خلال تقسيم الرموز
يُصدر Lighthouse تحذيرًا عندما يستغرق تنفيذ JavaScript أكثر من ثانيتَين، ويتعذّر التنفيذ عندما يستغرق أكثر من 3.5 ثانية. يُعدّ التحليل والتنفيذ المفرطَين لرمز JavaScript مشكلة محتملة في أي مرحلة من مراحل نشاط الصفحة، إذ يمكن أن يؤدّي ذلك إلى زيادة تأخير الإدخال في التفاعل إذا تزامن وقت تفاعل المستخدم مع الصفحة مع الوقت الذي يتم فيه تنفيذ مهام سلسلة التعليمات الرئيسية المسؤولة عن معالجة وتنفيذ JavaScript.
بالإضافة إلى ذلك، فإنّ التنفيذ والتحليل المفرطَين لرمز JavaScript يسبّبان مشاكل بشكل خاص أثناء التحميل الأولي للصفحة، لأنّ هذه هي النقطة في دورة حياة الصفحة التي من المرجّح أن يتفاعل فيها المستخدمون مع الصفحة. في الواقع، يرتبط إجمالي وقت الحظر (TBT)، وهو مقياس لمدى استجابة التحميل، ارتباطًا وثيقًا بمقياس INP، ما يشير إلى أنّ المستخدمين يميلون بشكل كبير إلى محاولة التفاعل مع الصفحة أثناء تحميلها الأولي.
تُعدّ عملية تدقيق Lighthouse التي تسجّل الوقت المستغرَق في تنفيذ كل ملف JavaScript تطلبه صفحتك مفيدة لأنّها يمكن أن تساعدك في تحديد النصوص البرمجية التي يمكن تقسيمها إلى أجزاء. يمكنك بعد ذلك الانتقال إلى خطوة أبعد من خلال استخدام أداة "نسبة استخدام رموز الصفحة" في "أدوات مطوّري البرامج في Chrome" لتحديد الأجزاء التي لا يتم استخدامها من JavaScript في الصفحة أثناء تحميلها.
تقسيم الرموز هو أسلوب مفيد يمكن أن يقلّل من أحجام حمولات JavaScript الأولية للصفحة. تتيح لك تقسيم حزمة JavaScript إلى جزأين:
- ملف JavaScript المطلوب عند تحميل الصفحة، وبالتالي لا يمكن تحميله في أي وقت آخر.
- هي ما تبقّى من JavaScript ويمكن تحميلها في وقت لاحق، وغالبًا ما يتم ذلك عندما يتفاعل المستخدم مع عنصر تفاعلي معيّن على الصفحة.
يمكن إجراء تقسيم الرمز البرمجي باستخدام بنية dynamic 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()
في رمز المصدر. تنفّذ معظم هذه الأدوات هذا الإجراء تلقائيًا، ولكن تتطلّب الأداة esbuild منك الموافقة على هذا التحسين.
ملاحظات مفيدة حول تقسيم الرموز
مع أنّ تقسيم الرموز البرمجية هو طريقة فعّالة للحدّ من التنازع على سلسلة التعليمات الرئيسية أثناء التحميل الأولي للصفحة، من المفيد مراعاة بعض الأمور إذا قررت تدقيق رمز المصدر JavaScript بحثًا عن فرص لتقسيم الرموز البرمجية.
استخدام أداة تجميع إذا أمكن
من الممارسات الشائعة أن يستخدم المطوّرون وحدات JavaScript أثناء عملية التطوير. وهي تحسّن تجربة المطوّرين بشكل ممتاز، كما أنّها تحسّن قابلية قراءة الرمز البرمجي وسهولة صيانته. ومع ذلك، هناك بعض خصائص الأداء غير المثالية التي يمكن أن تحدث عند شحن وحدات JavaScript إلى مرحلة الإنتاج.
والأهم من ذلك، يجب استخدام أداة تجميع لمعالجة رمز المصدر وتحسينه، بما في ذلك الوحدات التي تنوي تقسيم الرمز فيها. تتسم أدوات التجميع بفعالية كبيرة، ليس فقط في تطبيق التحسينات على الرمز المصدري لـ JavaScript، بل أيضًا في تحقيق التوازن بين اعتبارات الأداء، مثل حجم الحزمة ونسبة الضغط. تزداد فعالية الضغط مع حجم الحِزمة، ولكن تحاول أدوات تجميع الحِزم أيضًا التأكّد من أنّ الحِزم ليست كبيرة جدًا لدرجة أنّها تؤدي إلى مهام طويلة بسبب تقييم النص البرمجي.
تتجنّب أدوات التجميع أيضًا مشكلة إرسال عدد كبير من الوحدات غير المجمّعة عبر الشبكة. تميل البُنى التي تستخدم وحدات JavaScript إلى أن تتضمّن أشجار وحدات كبيرة ومعقّدة. عندما لا يتم تجميع شجرات الوحدات، تمثّل كل وحدة طلب HTTP منفصلاً، وقد يتأخر التفاعل في تطبيق الويب إذا لم يتم تجميع الوحدات. على الرغم من إمكانية استخدام
تلميح المورد <link rel="modulepreload">
لتحميل بنى الوحدات الكبيرة في أقرب وقت ممكن،
تظل حِزم JavaScript هي الخيار الأفضل من ناحية أداء التحميل.
عدم إيقاف تجميع بيانات البث عن غير قصد
يوفّر محرّك V8 JavaScript من 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
يتم شحن webpack مع إضافة باسم SplitChunksPlugin
، ما يتيح لك ضبط طريقة تقسيم حزمة JavaScript. يتعرّف webpack على كل من العبارات الديناميكية import()
والثابتة import
. يمكن تعديل سلوك SplitChunksPlugin
من خلال تحديد الخيار chunks
في إعداداته:
chunks: async
هي القيمة التلقائية، وتشير إلى طلباتimport()
الديناميكية.- يشير
chunks: initial
إلى مكالماتimport
ثابتة. - يغطي
chunks: all
عمليات الاستيراد الديناميكيةimport()
والثابتة، ما يتيح لك مشاركة أجزاء بين عمليات الاستيرادasync
وinitial
.
بشكل تلقائي، كلما صادف Webpack عبارة 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');
}
يؤدي الإعداد التلقائي لـ webpack لمقتطف الرمز السابق إلى إنشاء جزأين منفصلين:
- الجزء
main.js
الذي يصنّفه webpack على أنّه جزءinitial
ويتضمّن الوحدةmain.js
والوحدة./my-function.js
- الجزء
async
الذي يتضمّنform-validation.js
فقط (الذي يحتوي على تجزئة ملف في اسم المورد إذا تم ضبطه) لا يتم تنزيل هذا الجزء إلا إذا كانت قيمةcondition
صحيحة.
تتيح لك هذه الإعدادات تأجيل تحميل الجزء form-validation.js
إلى أن تكون هناك حاجة إليه. يمكن أن يؤدي ذلك إلى تحسين سرعة الاستجابة للتحميل من خلال تقليل وقت تقييم البرنامج النصي أثناء التحميل الأولي للصفحة. يتم تنزيل البرنامج النصي وتقييمه
للحزمة form-validation.js
عند استيفاء شرط محدّد،
وفي هذه الحالة، يتم تنزيل الوحدة التي تم استيرادها بشكل ديناميكي. أحد الأمثلة على ذلك هو حالة لا يتم فيها تنزيل polyfill إلا لمتصفّح معيّن، أو كما في المثال السابق، يكون النموذج المستورد ضروريًا لتفاعل المستخدم.
من ناحية أخرى، يؤدي تغيير إعدادات SplitChunksPlugin
لتحديد chunks: initial
إلى ضمان تقسيم الرمز على الأجزاء الأولية فقط. وهي عبارة عن أجزاء مثل تلك التي يتم استيرادها بشكل ثابت، أو تلك المُدرَجة في السمة entry
في Webpack. بالنظر إلى المثال السابق، سيكون الجزء الناتج عبارة عن
مزيج من form-validation.js
و main.js
في ملف نصي واحد،
ما يؤدي إلى تدهور محتمل في أداء التحميل الأولي للصفحة.
يمكن أيضًا ضبط خيارات SplitChunksPlugin
لفصل النصوص البرمجية الأكبر حجمًا إلى نصوص برمجية أصغر متعددة، مثلاً باستخدام الخيار maxSize
لتوجيه webpack إلى تقسيم الأجزاء إلى ملفات منفصلة إذا تجاوزت ما تم تحديده بواسطة maxSize
. يمكن أن يؤدي تقسيم ملفات النصوص البرمجية الكبيرة إلى ملفات أصغر إلى
تحسين سرعة الاستجابة عند التحميل، لأنّه في بعض الحالات، يتم تقسيم عملية تقييم النصوص البرمجية التي تستهلك الكثير من وحدة المعالجة المركزية إلى مهام أصغر، ما يقلّل من احتمال حظر سلسلة التعليمات الرئيسية لفترات زمنية أطول.
بالإضافة إلى ذلك، يعني إنشاء ملفات JavaScript أكبر حجمًا أيضًا أنّ النصوص البرمجية أكثر عُرضة لانتهاء صلاحية ذاكرة التخزين المؤقت. على سبيل المثال، إذا أرسلت نصًا برمجيًا كبيرًا جدًا يتضمّن كلاً من إطار العمل ورمز التطبيق التابع للجهة الأولى، يمكن إبطال الحزمة بأكملها إذا تم تعديل إطار العمل فقط، بدون إجراء أي تغييرات أخرى على المورد المجمّع.
من ناحية أخرى، تزيد ملفات النصوص البرمجية الأصغر حجمًا من احتمالية أن يسترد الزائر المتكرر الموارد من ذاكرة التخزين المؤقت، ما يؤدي إلى تحميل الصفحات بشكل أسرع عند الزيارات المتكررة. ومع ذلك، تستفيد الملفات الأصغر حجمًا بشكل أقل من الملفات الأكبر حجمًا من عملية الضغط، وقد تؤدي إلى زيادة وقت نقل البيانات ذهابًا وإيابًا على الشبكة عند تحميل الصفحات باستخدام ذاكرة تخزين مؤقت غير مُعدّة مسبقًا في المتصفح. يجب توخّي الحذر لتحقيق التوازن بين كفاءة التخزين المؤقت وفعالية الضغط ووقت تقييم النص البرمجي.
webpack demo
العرض التوضيحي SplitChunksPlugin
لـ webpack
اختبِر معلوماتك
ما هو نوع عبارة import
المستخدَمة عند تقسيم الرمز البرمجي؟
import()
الديناميكيةimport
ما هو نوع عبارة import
التي يجب أن تكون في أعلى
وحدة JavaScript النمطية، وفي أي مكان آخر؟
import()
الديناميكيةimport
عند استخدام SplitChunksPlugin
في Webpack، ما الفرق بين جزء async
وجزء initial
؟
async
باستخدام import()
الديناميكية، ويتم تحميل أجزاء initial
باستخدام import
الثابتة.
async
باستخدام import
الثابتة، ويتم تحميل أجزاء initial
باستخدام import()
الديناميكية.
الموضوع التالي: صور تظهر بأسلوب "التحميل الكسول" وعناصر <iframe>
على الرغم من أنّ JavaScript يميل إلى أن يكون نوعًا مكلفًا من الموارد، إلا أنّه ليس نوع الموارد الوحيد الذي يمكنك تأجيل تحميله. يمكن أن تكون عناصر الصور و<iframe>
موارد مكلفة بحد ذاتها. كما هو الحال مع JavaScript، يمكنك تأجيل تحميل الصور وعنصر <iframe>
من خلال التحميل الكسول، وهو ما سيتم توضيحه في الوحدة التالية من هذه الدورة التدريبية.