تقييم النص البرمجي والمهام الطويلة

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

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

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

ما هو تقييم النص؟

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

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

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

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

العلاقة بين النصوص البرمجية والمهام التي تقوم بتقييمها

تعتمد طريقة بدء المهام المسؤولة عن تقييم النصوص البرمجية على ما إذا كان يتم تحميل النص البرمجي الذي تحمِّله من خلال عنصر <script> عادي، أو ما إذا كان النص البرمجي عبارة عن وحدة محمَّلة مع type=module. ونظرًا لأن المتصفحات تميل إلى التعامل مع الأمور بشكل مختلف، سيتم التطرق إلى كيفية تعامل محركات المتصفح الرئيسية مع تقييم النصوص البرمجية عند اختلاف سلوكيات تقييم النصوص البرمجية عبرها.

تحميل نصوص برمجية باستخدام العنصر <script>

بشكل عام، هناك علاقة مباشرة بين عدد المهام المُرسَلة لتقييم النصوص البرمجية وعدد عناصر <script> في الصفحة. يبدأ كل عنصر <script> مهمة لتقييم النص البرمجي المطلوب بحيث يمكن تحليله وتجميعه وتنفيذه. وينطبق ذلك على المتصفّحات المستندة إلى Chromium وSafari وFirefox.

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

يمكنك تقسيم عمليات تقييم النصوص البرمجية من خلال تجنُّب تحميل أجزاء كبيرة من JavaScript، وتحميل المزيد من النصوص البرمجية الفردية والأصغر باستخدام عناصر <script> إضافية.

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

مهام متعددة تتضمّن تقييم نص برمجي كما يظهر في محلّل الأداء في &quot;أدوات مطوري البرامج في Chrome&quot; بسبب تحميل العديد من النصوص البرمجية الأصغر بدلاً من عدد أقل من النصوص البرمجية الأكبر حجمًا، يقل احتمال أن تصبح المهام مهام طويلة، ما يسمح لسلسلة التعليمات الرئيسية بالاستجابة لإدخال المستخدم بسرعة أكبر.
يتم إنشاء مهام متعددة لتقييم النصوص البرمجية كنتيجة لعناصر <script> متعددة في HTML للصفحة. يُفضَّل إرسال حزمة نصوص برمجية كبيرة واحدة إلى المستخدمين، الأمر الذي من المرجح أن يؤدي إلى حظر سلسلة التعليمات الرئيسية.

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

تحميل نصوص برمجية باستخدام العنصر <script> والسمة type=module

يمكن الآن تحميل وحدات ES في المتصفّح باستخدام السمة type=module على العنصر <script>. يحمل هذا الأسلوب في تحميل النص البرمجي بعض المزايا التي يحصل عليها المطوّرون، مثل عدم الحاجة إلى تحويل الرمز البرمجي لاستخدام الإنتاج، خاصةً عند استخدامه مع استيراد الخرائط. ومع ذلك، يؤدي تحميل النصوص البرمجية بهذه الطريقة إلى جدولة المهام التي تختلف من متصفّح إلى آخر.

المتصفّحات المستندة إلى Chromium

في متصفحات مثل Chrome أو تلك المشتقة منه، يؤدي تحميل وحدات ES باستخدام السمة type=module إلى إنشاء أنواع مختلفة من المهام عن تلك التي تظهر عادةً في حال عدم استخدام السمة type=module. على سبيل المثال، سيتم تشغيل مهمة لكل نص برمجي للوحدة تتضمن نشاطًا مصنّفًا على أنه وحدة تجميع.

يعمل تجميع الوحدة النمطية في مهام متعددة كما هو موضّح في &quot;أدوات مطوري البرامج في Chrome&quot;.
سلوك تحميل الوحدة في المتصفّحات المستندة إلى Chromium. سينتج عن كل نص برمجي للوحدة طلب وحدة تجميع لتجميع المحتوى قبل التقييم.

بعد تجميع الوحدات، يبدأ أي رمز يتم تشغيله فيها لاحقًا في نشاط مصنّف على أنّه تقييم الوحدة.

تقييم في الوقت المناسب لوحدة معروضة في لوحة الأداء في &quot;أدوات مطوري البرامج في Chrome&quot;
عند تشغيل الرمز في وحدة ما، سيتم تقييم هذه الوحدة في الوقت المناسب.

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

  • يتم تشغيل جميع رموز الوحدة تلقائيًا في الوضع المتشدد، ما يسمح بإجراء تحسينات محتملة من قِبل محرّكات JavaScript لم يكن من الممكن إجراؤها في سياق غير معقد.
  • يتم التعامل مع النصوص البرمجية التي يتم تحميلها باستخدام type=module كما لو تم تأجيلها تلقائيًا. ويمكن استخدام السمة async في النصوص البرمجية التي تم تحميلها مع type=module لتغيير هذا السلوك.

Safari وFirefox

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

جارٍ تحميل النصوص البرمجية باستخدام import() الديناميكية

import() الديناميكية هي طريقة أخرى لتحميل النصوص البرمجية. على عكس عبارات import الثابتة التي يجب وضعها في الجزء العلوي من وحدة ES، يمكن لاستدعاء import() الديناميكي أن يظهر في أي مكان في النص البرمجي لتحميل مقطع من JavaScript عند الطلب. ويُطلق على هذا الأسلوب اسم تقسيم الرمز.

يتميز import() الديناميكي بميزتين عند تحسين مقياس INP:

  1. إنّ الوحدات التي يتم تأجيل تحميلها لاحقًا تقلّل من تزايد الطلب على سلسلة التعليمات الرئيسية أثناء بدء التشغيل عن طريق تقليل مقدار محتوى JavaScript الذي يتم تحميله في ذلك الوقت. يؤدي هذا الإجراء إلى إخلاء سلسلة التعليمات الرئيسية حتى تكون أكثر استجابة لتفاعلات المستخدمين.
  2. عند إجراء طلبات import() ديناميكية، سيؤدي كل طلب إلى فصل عملية تجميع كل وحدة وتقييمها بشكل فعّال عن مهمتها الخاصة. وبالطبع، إنّ import() الديناميكية التي تُحمِّل وحدة كبيرة جدًا ستبدأ مهمة تقييم نص برمجي كبيرة جدًا، ما قد يتداخل مع قدرة سلسلة التعليمات الرئيسية على الاستجابة لإدخالات المستخدم إذا وقع التفاعل في الوقت نفسه الذي يحدث فيه طلب import() الديناميكي. لذلك، لا يزال من المهم جدًا تحميل أقل قدر ممكن من محتوى JavaScript.

تعمل طلبات import() الديناميكية بشكل مشابه في جميع محرّكات البحث الرئيسية: ستكون مهام تقييم النصوص البرمجية الناتجة مماثلة لعدد الوحدات التي يتم استيرادها ديناميكيًا.

تحميل نصوص برمجية في عامل ويب

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

بالإضافة إلى تقليل مهام سلسلة التعليمات الرئيسية، يمكن للعاملين على الويب بنفسهم تحميل نصوص برمجية خارجية لاستخدامها في سياق العامل، إما من خلال عبارات importScripts أو عبارات import الثابتة في المتصفِّحات التي تتوافق مع عمال الوحدات. والنتيجة هي تقييم أي نص برمجي يطلبه أحد الموظفين على الويب خارج سلسلة التعليمات الرئيسية.

المفاضلات والاعتبارات

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

كفاءة الضغط

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

تعتبر الحِزم أدوات مثالية لإدارة حجم الإخراج للنصوص البرمجية التي يعتمد عليها موقعك الإلكتروني:

  • يمكن للمكوّن الإضافي SplitChunksPlugin مساعدتك في ما يتعلّق بحزمة الويب. راجِع مستندات SplitChunksPlugin للاطّلاع على الخيارات التي يمكنك ضبطها للمساعدة في إدارة أحجام مواد العرض.
  • بالنسبة إلى الحِزم الأخرى مثل البيانات المجمّعة وإنشاء الإنشاء، يمكنك إدارة أحجام ملفات النصوص البرمجية باستخدام استدعاءات 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، ما يؤدي إلى تحسين تجربة المستخدم.

صورة رئيسية من UnLaunch، للفنان ماركوس سبيسك.