عند تحميل البرامج النصية، يستغرق المتصفّح بعض الوقت لتقييمها قبل تنفيذها، ما قد يؤدي إلى إطالة مدة المهام. تعرَّف على آلية عمل تقييم النصوص البرمجية، وما يمكنك فعله لمنع حدوث مهام طويلة أثناء تحميل الصفحة.
عندما يتعلق الأمر بتحسين مدى استجابة الصفحة لتفاعلات المستخدم (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()
الديناميكية بشكل مشابه في جميع محرّكات المتصفّحات الرئيسية: ستكون مهام تقييم النصوص البرمجية الناتجة مماثلة لعدد الوحدات التي يتم استيرادها ديناميكيًا.
النصوص البرمجية المحمَّلة في Web Worker
Web workers هي حالة استخدام خاصة لـ JavaScript. يتم تسجيل عمال الويب في سلسلة التعليمات الرئيسية، ويتم بعد ذلك تشغيل الرمز البرمجي داخل العامل في سلسلة التعليمات الخاصة به. ويعود ذلك بالفائدة الكبيرة على الأداء، لأنّه في حين أنّ الرمز الذي يسجّل Web Worker يتم تشغيله في سلسلة المهام الرئيسية، لا يتم تشغيل الرمز ضمن Web Worker. ويؤدي ذلك إلى تقليل ازدحام سلسلة المحادثات الرئيسية، ويمكن أن يساعد في إبقاء سلسلة المحادثات الرئيسية أكثر استجابةً لتفاعلات المستخدمين.
بالإضافة إلى تقليل عمل السلسلة الرئيسية، يمكن لعمال الويب أنفسهم تحميل نصوص برمجية خارجية لاستخدامها في سياق العامل، إما من خلال عبارات 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، وبالتالي تقديم تجربة أفضل للمستخدم.