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

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

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

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

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

إذا كنت قد حلّلت تطبيقًا يتضمّن الكثير من JavaScript، ربما لاحظت مهامًا طويلة تم تصنيف السبب فيها على أنّه Evaluate Script.

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

عملية تجميع الوحدات في مهام متعددة كما هو موضّح في أدوات مطوري البرامج في Chrome
سلوك تحميل الوحدات في المتصفّحات المستندة إلى 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() الديناميكية بشكل مشابه في جميع محركات المتصفحات الرئيسية: ستكون مهام تقييم النصوص البرمجية الناتجة هي نفسها عدد الوحدات التي يتم استيرادها بشكل ديناميكي.

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

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

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

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

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

كفاءة الضغط

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

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

  • في ما يتعلّق بـ webpack، يمكن أن تساعدك إضافة SplitChunksPlugin. راجِع مستندات SplitChunksPlugin لمعرفة الخيارات التي يمكنك ضبطها للمساعدة في إدارة أحجام مواد العرض.
  • بالنسبة إلى أدوات تجميع الحِزم الأخرى، مثل Rollup وesbuild، يمكنك إدارة أحجام ملفات النصوص البرمجية باستخدام طلبات import() الديناميكية في الرمز البرمجي. ستعمل أدوات التجميع هذه، بالإضافة إلى Webpack، تلقائيًا على تقسيم مواد العرض التي يتم استيرادها ديناميكيًا إلى ملف خاص بها، ما يؤدي إلى تجنُّب أحجام الحِزم الأولية الأكبر.

إيقاف ذاكرة التخزين المؤقت

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

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

الوحدات المتداخلة وأداء التحميل

إذا كنت تشحن وحدات 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، وبالتالي تقديم تجربة أفضل للمستخدم.