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

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

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

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

صورة رئيسية من Unسباش، بقلم ماركوس سبسكي.