عند تحميل البرامج النصية، يستغرق المتصفّح بعض الوقت لتقييمها قبل تنفيذها، ما قد يؤدي إلى إطالة مدة المهام. تعرَّف على طريقة تقييم النص البرمجي وما يمكنك فعله لمنعه من التسبب في مهام طويلة أثناء تحميل الصفحة.
عندما يتعلق الأمر بتحسين مدى استجابة الصفحة لتفاعلات المستخدم (INP)، ستتضمّن معظم النصائح التي ستواجهها تحسين التفاعلات نفسها. على سبيل المثال، في دليل تحسين المهام الطويلة، تمت مناقشة أساليب مثل تحقيق الأرباح مع setTimeout
وغيرها. هذه الأساليب مفيدة، لأنها تتيح لسلسلة البيانات الرئيسية مساحة للتنفس من خلال تجنب المهام الطويلة التي يمكن أن تتيح مزيدًا من الفرص للتفاعلات والأنشطة الأخرى في وقت أقرب، بدلاً مما إذا كان عليهم انتظار مهمة واحدة طويلة.
ولكن ماذا عن المهام الطويلة التي تأتي من تحميل النصوص البرمجية نفسها؟ ويمكن أن تتداخل هذه المهام مع تفاعلات المستخدمين وتؤثّر في مقياس INP للصفحة أثناء التحميل. سيوضّح هذا الدليل كيفية تعامل المتصفّحات مع المهام التي تبدأ بتقييم النصوص البرمجية، وسيتحقّق من الإجراءات التي يمكنك اتّخاذها لتقسيم عمل تقييم النصوص البرمجية حتى يكون المسار الرئيسي أكثر استجابةً لإدخالات المستخدم أثناء تحميل الصفحة.
ما هو تقييم النص البرمجي؟
في حال إنشاء تطبيق يوفّر الكثير من JavaScript، قد تكون ظهرت لك مهام طويلة تم وضع علامة على الجاني فيها باسم تقييم النص البرمجي.
يُعدّ تقييم النصوص البرمجية جزءًا ضروريًا من تنفيذ JavaScript في المتصفّح، لأنّه يتم تجميع JavaScript في الوقت المناسب قبل التنفيذ. عند تقييم نص برمجي، يتم تحليله أولاً بحثًا عن الأخطاء. إذا لم يعثر المُحلِّل على أي أخطاء، يتم تجميع النص البرمجي إلى رمز برمجي ثنائي، ويمكن بعد ذلك مواصلة التنفيذ.
على الرغم من أنّ تقييم النصوص البرمجية ضروري، إلا أنّه قد يتسبب في مشاكل، لأنّ المستخدمين قد يحاولون التفاعل مع الصفحة بعد وقت قصير من عرضها في البداية. ومع ذلك، لا يعني عرض الصفحة أنّها قد اكتملت عملية تحميلها. يمكن أن يتأخّر التفاعل الذي يحدث أثناء التحميل لأنّ الصفحة مشغولة بتقييم النصوص البرمجية. مع أنّه ما مِن ضمان بأنّه يمكن أن يحدث تفاعل في هذه المرحلة الزمنية، لأنّ النص البرمجي المسؤول عنه قد لا يكون قد تم تحميله بعد، قد تكون هناك تفاعلات تعتمد على JavaScript جاهزة، أو قد لا يعتمد التفاعل على JavaScript على الإطلاق.
العلاقة بين النصوص البرمجية والمهام التي تقيّمها
تعتمد كيفية بدء المهام المسؤولة عن تقييم النص البرمجي على ما إذا كان النص البرمجي الذي يتم تحميله محمَّلاً بعنصر <script>
عادي، أو ما إذا كان النص البرمجي عبارة عن وحدة تم تحميلها باستخدام type=module
. بما أنّ المتصفّحات تميل إلى معالجة الأمور بشكلٍ مختلف، سيتم التطرق إلى كيفية تعامل محرّكات المتصفّحات الرئيسية مع تقييم النصوص البرمجية حيث تختلف سلوكيات تقييم النصوص البرمجية على مستوى المتصفّحات.
النصوص البرمجية المحمَّلة باستخدام عنصر <script>
إنّ عدد المهام المُرسَلة لتقييم النصوص البرمجية يرتبط بشكل عام بعدد عناصر <script>
على الصفحة. يبدأ كل عنصر <script>
مهمة لتقييم النص البرمجي المطلوب حتى يمكن تحليله وتجميعه وتنفيذه. ينطبق ذلك على المتصفّحات المستندة إلى Chromium وSafari وو Firefox.
ما أهمية ذلك؟ لنفترض أنّك تستخدم أداة تجميع لإدارة النصوص البرمجية الخاصة بالإنتاج، وضبطتها لتجميع كل ما تحتاج إليه صفحتك للعمل في نص برمجي واحد. إذا كان هذا هو الحال بالنسبة إلى موقعك الإلكتروني، يمكنك توقّع إرسال مهمة واحدة لتقييم هذا النص البرمجي. هل هذا أمر سيئ؟ ليس بالضرورة - ما لم يكن النص البرمجي ضخمًا.
يمكنك تقسيم عمل تقييم النصوص البرمجية عن طريق تجنُّب تحميل أجزاء كبيرة من JavaScript، وتحميل المزيد من النصوص البرمجية الفردية الأصغر حجمًا باستخدام عناصر <script>
إضافية.
على الرغم من أنّه عليك دائمًا محاولة تحميل أقل قدر ممكن من JavaScript أثناء تحميل الصفحة، إلا أنّ تقسيم النصوص البرمجية يضمن لك الحصول على عدد أكبر من المهام الأصغر حجمًا التي لن تحظر السلسلة الرئيسية على الإطلاق، بدلاً من مهمة واحدة كبيرة قد تحظر السلسلة الرئيسية، أو على الأقل أقل من عدد المهام التي بدأت بها.
يمكنك اعتبار تقسيم المهام لتقييم النصوص البرمجية مشابهًا إلى حدٍّ ما للتوقّف أثناء عمليات استدعاء الأحداث التي يتم تنفيذها أثناء التفاعل. ومع ذلك، عند تقييم النصوص البرمجية، تقسم آلية التقديم لغة JavaScript التي تحمّلها إلى نصوص برمجية متعددة أصغر حجمًا، بدلاً من عدد أقل من النصوص البرمجية الأكبر حجمًا التي يُرجّح أن تحظر السلسلة الرئيسية.
النصوص البرمجية المحمَّلة باستخدام العنصر <script>
والسمة type=module
يمكن الآن تحميل وحدات ES في المتصفّح باستخدام السمة type=module
في العنصر <script>
. يقدّم هذا النهج لتحميل النصوص البرمجية بعض المزايا لتجربة المطوّر، مثل عدم الحاجة إلى تحويل الرمز البرمجي لاستخدامه في مرحلة الإنتاج، خاصةً عند استخدامه مع خرائط الاستيراد. ومع ذلك، يؤدي تحميل النصوص البرمجية بهذه الطريقة إلى جدولة المهام التي تختلف من متصفح إلى آخر.
المتصفّحات المستندة إلى Chromium
في المتصفّحات، مثل Chrome أو المتصفّحات المشتقة منه، يؤدي تحميل وحدات ES باستخدام السمة type=module
إلى إنشاء أنواع مختلفة من المهام عن تلك التي تظهر عادةً عند عدم استخدام type=module
. على سبيل المثال، سيتم تنفيذ مهمة لكل نص برمجي للوحدة يتضمن نشاطًا مصنّفًا على أنّه تجميع الوحدة.
بعد تجميع الوحدات، سيؤدي أي رمز يتم تشغيله لاحقًا فيها إلى بدء النشاط الذي يحمل التصنيف تقييم الوحدة.
ويتمثل التأثير هنا في Chrome والمتصفّحات ذات الصلة على الأقل في أنّه يتم تقسيم خطوات التجميع عند استخدام وحدات ES. يعد هذا مكسبًا واضحًا فيما يتعلق بإدارة المهام الطويلة؛ ومع ذلك، فإن تقييم الوحدة الناتج الذي ينتج عنه لا يزال يعني أنك تتحمل بعض التكاليف الحتمية. على الرغم من أنّه يجب أن تسعى إلى تضمين أقل قدر ممكن من JavaScript، إلا أنّ استخدام وحدات ES، بغض النظر عن المتصفّح، يوفّر المزايا التالية:
- يتم تشغيل جميع رموز الوحدات تلقائيًا في الوضع المتشدد، ما يسمح بإجراء تحسينات محتملة من خلال محركات JavaScript لا يمكن إجراؤها في سياق غير صارم.
- يتم التعامل مع النصوص البرمجية التي تم تحميلها باستخدام
type=module
كما لو كانت مؤجَّلة تلقائيًا. من الممكن استخدام السمةasync
على النصوص البرمجية التي تم تحميلها باستخدامtype=module
لتغيير هذا السلوك.
Safari وFirefox
عند تحميل الوحدات في Safari وFirefox، يتم تقييم كل وحدة في مهمة منفصلة. وهذا يعني أنّه من الناحية النظرية، يمكنك تحميل وحدة واحدة من المستوى الأعلى تتألف من عبارات import
ثابتة فقط إلى الوحدات الأخرى، وستؤدي كل وحدة يتم تحميلها إلى طلب شبكة ومهمة منفصلة لتقييمها.
النصوص البرمجية المحمَّلة باستخدام import()
ديناميكي
import()
الديناميكي هو طريقة أخرى لتحميل النصوص البرمجية. على عكس عبارات import
الثابتة المطلوب أن تكون أعلى وحدة ES، يمكن أن يظهر طلب import()
الديناميكي في أي مكان في النص البرمجي لتحميل جزء من JavaScript عند الطلب. ويُطلق على هذه التقنية اسم تقسيم الرموز البرمجية.
يتميّز import()
الديناميكي بميزتَين لتحسين INP:
- إنّ الوحدات التي يتم تأجيل تحميلها إلى وقت لاحق تقلّل من تداخل مؤشر التسلسل الرئيسي أثناء بدء التشغيل من خلال تقليل مقدار JavaScript الذي يتم تحميله في ذلك الوقت. ويؤدي ذلك إلى تحرير سلسلة المهام الرئيسية لكي تكون أكثر استجابة لتفاعلات المستخدمين.
- عند إجراء طلبات
import()
ديناميكية، ستفصل كل طلب بشكل فعّال عملية تجميع كل وحدة وتقييمها إلى مهمتها الخاصة. بالطبع، سيؤديimport()
الديناميكي الذي يحمِّل وحدة كبيرة جدًا إلى بدء مهمة تقييم نص برمجي كبيرة إلى حدٍ ما، ويمكن أن يتداخل ذلك مع قدرة السلسلة الرئيسية على الاستجابة لإدخال المستخدم إذا حدث التفاعل في الوقت نفسه مع طلبimport()
الديناميكي. لذلك، لا يزال من المهم جدًا تحميل أقل قدر ممكن من JavaScript.
تتصرف طلبات import()
الديناميكية بشكل مشابه في جميع محرّكات المتصفّحات الرئيسية: ستكون مهام تقييم النصوص البرمجية الناتجة مماثلة لعدد الوحدات التي يتم استيرادها ديناميكيًا.
النصوص البرمجية التي تم تحميلها في عامل تشغيل الويب
عاملو الويب هم حالة استخدام خاصة في JavaScript. ويتم تسجيل العاملين على الويب في سلسلة التعليمات الرئيسية، ويتم تشغيل الرمز داخل العامل في سلسلة التعليمات الخاصة به. ويعود ذلك بالفائدة الكبيرة على الأداء، لأنّه في حين أنّ الرمز الذي يسجّل Web Worker يتم تشغيله في سلسلة المهام الرئيسية، لا يتم تشغيل الرمز ضمن Web Worker. يقلل ذلك من ازدحام سلسلة المحادثات الرئيسية، ويمكن أن يساعد في إبقاء سلسلة المحادثات الرئيسية أكثر استجابةً لتفاعلات المستخدمين.
بالإضافة إلى تقليل عمل السلسلة الرئيسية، يمكن لعمال الويب أنفسهم تحميل نصوص برمجية خارجية لاستخدامها في سياق العامل، إما من خلال عبارات importScripts
أو عبارات import
الثابتة في المتصفّحات التي تتيح عمال الوحدات. وتكون النتيجة أن أي نص برمجي يطلبه عامل ويب يتم تقييمه خارج سلسلة التعليمات الرئيسية.
المفاضلات والاعتبارات
على الرغم من أنّ تقسيم النصوص البرمجية إلى ملفات منفصلة أصغر حجمًا يساعد في الحد من المهام الطويلة مقارنةً بتحميل عدد أقل من الملفات الأكبر حجمًا، من المهم أخذ بعض الأمور في الاعتبار عند تحديد كيفية تقسيم النصوص البرمجية.
كفاءة الضغط
يُعدّ الضغط عاملاً مهمًا عند تقسيم النصوص البرمجية. عندما تكون النصوص البرمجية أصغر حجمًا، يصبح الضغط أقل فعالية إلى حد ما. ستستفيد النصوص البرمجية الأكبر حجمًا من الضغط بشكل أكبر. على الرغم من أن زيادة كفاءة الضغط تساعد في خفض أوقات تحميل النصوص البرمجية لأقصى حد ممكن، إلا أن هناك توازنًا في ذلك لضمان تقسيم النصوص البرمجية إلى أجزاء أصغر بما يكفي لتسهيل تفاعل أفضل أثناء بدء التشغيل.
تعتبر الحِزم أدوات مثالية لإدارة حجم الإخراج للنصوص البرمجية التي يعتمد عليها موقعك الإلكتروني:
- بالنسبة إلى webpack، يمكن أن يساعدك المكوّن الإضافي
SplitChunksPlugin
. يمكنك الرجوع إلى مستنداتSplitChunksPlugin
لمعرفة الخيارات التي يمكنك ضبطها للمساعدة في إدارة أحجام مواد العرض. - بالنسبة إلى أدوات تجميع الحِزم الأخرى، مثل Rollup وesbuild، يمكنك إدارة أحجام ملفات النصوص البرمجية باستخدام طلبات
import()
الديناميكية في رمزك البرمجي. وستعمل برامج الحِزم هذه وحِزمة الويب على تقسيم مواد العرض التي يتم استيرادها ديناميكيًا إلى ملف خاص بها، ما يؤدي إلى تجنُّب أحجام الحِزم الأولية الأكبر.
إيقاف ذاكرة التخزين المؤقت
تلعب عملية إلغاء التخزين المؤقت دورًا كبيرًا في سرعة تحميل الصفحة عند الزيارات المتكررة. عند شحن حِزم نصوص برمجية كبيرة ومتكاملة، تكون في وضع غير مُفيد عندما يتعلق الأمر بميزة التخزين المؤقت للمتصفّح. ويعود السبب في ذلك إلى أنّه عند تعديل رمز الطرف الأول، سواء من خلال تحديث الحزم أو إصلاح أخطاء الشحن، تصبح الحزمة بأكملها غير صالحة ويجب تنزيلها مرة أخرى.
من خلال تقسيم النصوص البرمجية، لا تقسّم فقط عمل تقييم النصوص البرمجية على مستوى مهام أصغر، بل تزيد أيضًا من احتمالية أن يحصل الزوّار المتكرّرون على المزيد من النصوص البرمجية من ذاكرة التخزين المؤقت للمتصفّح بدلاً من الشبكة. ويؤدي ذلك إلى تحميل الصفحة بشكل أسرع بشكل عام.
الوحدات المُدمجة وأداء التحميل
إذا كنت بصدد شحن وحدات ES في مرحلة الإنتاج وتحميلها باستخدام السمة type=module
، عليك معرفة كيفية تأثير تداخل الوحدات في وقت بدء التشغيل. يشير تداخل الوحدات إلى الحالات التي تستورد فيها وحدة ES وحدة ES أخرى بشكل ثابت تستورد بدورها وحدة ES أخرى بشكل ثابت:
// a.js
import {b} from './b.js';
// b.js
import {c} from './c.js';
إذا لم تكن وحدات ES مجمّعة معًا، يؤدّي الرمز السابق إلى سلسلة طلبات شبكة: عند طلب a.js
من عنصر <script>
، يتم إرسال طلب شبكة آخر لـ b.js
، ما يؤدي بعد ذلك إلى طلب آخر لـ c.js
. إحدى الطرق لتجنُّب ذلك هي استخدام أداة تجميع، ولكن تأكَّد من ضبط أداة التجميع لتقسيم النصوص البرمجية لتوزيع عمل تقييم النصوص البرمجية.
إذا كنت لا تريد استخدام أداة تجميع، يمكنك استخدام ملاحظة المورد modulepreload
كطريقة أخرى للتغلب على عمليات استدعاء الوحدات المتداخلة، ما سيؤدي إلى تحميل وحدات ES مسبقًا قبل الأوان لتجنُّب سلاسل طلبات الشبكة.
الخاتمة
لا شكّ أنّ تحسين تقييم النصوص البرمجية في المتصفّح هو مهمة صعبة. يعتمد هذا الأسلوب على متطلبات موقعك الإلكتروني وقيوده. في المقابل، يؤدي تقسيم النصوص البرمجية إلى توسيع نطاق تقييم النصوص البرمجية ليشمل العديد من المهام الأصغر حجمًا، ما يتيح لسلسلة المحادثات الرئيسية التعامل مع تفاعلات المستخدمين بكفاءة أكبر بدلاً من حظر سلسلة التعليمات الرئيسية.
في ما يلي بعض الإجراءات التي يمكنك اتّخاذها لتقسيم مهام تقييم النصوص البرمجية الكبيرة:
- عند تحميل النصوص البرمجية باستخدام عنصر
<script>
بدون السمةtype=module
، تجنَّب تحميل النصوص البرمجية الكبيرة جدًا، لأنّها ستؤدي إلى بدء مهام تقييم النصوص البرمجية التي تستهلك الكثير من الموارد وتوقِف سلسلة التعليمات الرئيسية. انشر نصوصك البرمجية على المزيد من عناصر<script>
لتبسيط هذا العمل. - سيؤدي استخدام السمة
type=module
لتحميل وحدات ES تلقائيًا في المتصفّح إلى بدء مهام فردية لتقييم كل نص برمجي منفصل للوحدة. - يمكنك تقليل حجم حِزمك الأولية باستخدام طلبات
import()
الديناميكية. وينطبق ذلك أيضًا في برامج الحِزم، حيث تتعامل برامج الحِزم مع كل وحدة يتم استيرادها ديناميكيًا باعتبارها "نقطة تقسيم"، ما يؤدي إلى إنشاء نص برمجي منفصل لكل وحدة يتم استيرادها ديناميكيًا. - احرص على موازنة المقايضات، مثل كفاءة الضغط وإبطال ذاكرة التخزين المؤقت. سيتم ضغط النصوص البرمجية الأكبر حجمًا بشكل أفضل، ولكن من المرجّح أن تتطلّب تقييم النصوص البرمجية بشكل أكثر تكلفة في مهام أقل، ما يؤدي إلى إلغاء صلاحية ذاكرة التخزين المؤقت للمتصفّح، ما يؤدي إلى انخفاض كفاءة التخزين المؤقت بشكل عام.
- في حال استخدام وحدات ES بشكلٍ أصلي بدون تجميعها، استخدِم تلميح الموارد
modulepreload
لتحسين تحميلها أثناء بدء التشغيل. - وكما هو الحال دائمًا، يجب استخدام أقل قدر ممكن من JavaScript.
إنّ هذا التوازن أمر حاسم بالتأكيد، ولكن من خلال تقسيم النصوص البرمجية وتقليل الحمولات الأولية باستخدام import()
الديناميكي، يمكنك تحقيق أداء أفضل عند بدء التشغيل والاستجابة بشكل أفضل لتفاعلات المستخدمين خلال فترة بدء التشغيل الحاسمة هذه. من المفترض أن يساعدك ذلك في تحقيق نتيجة أفضل في مقياس INP، وبالتالي تقديم تجربة أفضل للمستخدم.