استخدام عاملي الويب لتشغيل JavaScript من سلسلة التعليمات الرئيسية للمتصفِّح

يمكن أن تؤدي البنية غير الأساسية لسلسلة المحادثات إلى تحسين موثوقية تطبيقك وتجربة المستخدم بشكل كبير.

سورما
سورما

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

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

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

ما أهمية مستخدمي الويب؟

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

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

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

إنشاء سلاسل محادثات مع موظفي الويب

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

في JavaScript، يمكننا الحصول على وظائف مشابهة تقريبًا من عمال الويب، وتتوفر هذه الوظائف منذ عام 2007 ويتم دعمها عبر جميع المتصفحات الرئيسية منذ عام 2012. يعمل عاملو الويب بشكل متوازٍ مع سلسلة التعليمات الرئيسية، ولكن على عكس سلاسل أنظمة التشغيل، لا يمكنهم مشاركة المتغيّرات.

لإنشاء عامل ويب، مرر ملفًا إلى الدالة الإنشائية للعامل، والتي تبدأ تشغيل هذا الملف في سلسلة رسائل منفصلة:

const worker = new Worker("./worker.js");

يمكنك التواصل مع العامل على الويب من خلال إرسال الرسائل عبر postMessage API. مرِّر قيمة الرسالة كمَعلمة في استدعاء postMessage، ثمّ أضِف أداة معالجة حدث الرسائل إلى العامل:

main.js

const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);

worker.js

addEventListener('message', event => {
  const [a, b] = event.data;

  // Do stuff with the message
  // ...
});

لإرسال رسالة إلى سلسلة المحادثات الرئيسية، استخدِم واجهة برمجة التطبيقات postMessage نفسها في مشغّل الويب واضبط أداة معالجة الأحداث في سلسلة التعليمات الرئيسية:

main.js

const worker = new Worker('./worker.js');

worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
  console.log(event.data);
});

worker.js

addEventListener('message', event => {
  const [a, b] = event.data;

  // Do stuff with the message
  postMessage(a + b);
});

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

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

Comlink هي مكتبة تهدف إلى السماح لك باستخدام مستخدمي الويب بدون الحاجة إلى التفكير في تفاصيل postMessage. يتيح لك Comlink مشاركة المتغيرات بين عاملي الويب وسلسلة التعليمات الرئيسية تقريبًا مثل لغات البرمجة الأخرى التي تدعم سلاسل المحادثات.

يمكنك إعداد Comlink من خلال استيراده في عامل ويب وتحديد مجموعة من الدوال التي سيتم عرضها لسلسلة التعليمات الرئيسية. يمكنك بعد ذلك استيراد Comlink في سلسلة التعليمات الرئيسية، والتفاف العامل، والحصول على إمكانية الوصول إلى الدوال المعروضة:

worker.js

import {expose} from 'comlink';

const api = {
  someMethod() {
    // ...
  }
}

expose(api);

main.js

import {wrap} from 'comlink';

const worker = new Worker('./worker.js');
const api = wrap(worker);

يعمل المتغيّر api في سلسلة التعليمات الرئيسية بالطريقة نفسها التي يعمل بها المتغيّر في عامل الويب، باستثناء أنّ كل دالة تعرض وعدًا لقيمة بدلاً من القيمة نفسها.

ما هو الرمز الذي يجب نقله إلى مشغّل الويب؟

ولا يمكن للعاملين على الويب الوصول إلى نموذج العناصر في المستند (DOM) والعديد من واجهات برمجة التطبيقات، مثل WebUSB أو WebRTC أو Web Audio، وبالتالي لا يمكنك إضافة أجزاء من تطبيقك تعتمد على هذا الوصول في العامل. ومع ذلك، يشتري كل جزء صغير من الرمز - يتم نقله إلى العامل - مزيدًا من هامش النمو في سلسلة التعليمات الرئيسية للعناصر التي يجب أن تتوفر هناك، مثل تحديث واجهة المستخدم.

تتمثّل إحدى المشاكل التي يواجهها مطوّرو برامج الويب في أنّ معظم تطبيقات الويب تعتمد على إطار عمل واجهة المستخدِم، مثل Vue أو React (التفاعل) أو تنظيم كل شيء في التطبيق، فكل شيء جزء من إطار العمل ويرتبط بطبيعتها بنموذج العناصر في المستند (DOM). يبدو أن ذلك يصعّب الانتقال إلى بنية OMT.

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

PROXX: دراسة حالة حول OMT

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

قرر الفريق استخدام عمال الويب لفصل الحالة المرئية للعبة من منطقها:

  • تتناول سلسلة التعليمات الرئيسية عرض الرسوم المتحركة والانتقالات.
  • لنفترض أن هناك موظفًا على الويب يتعامل مع منطق اللعبة، وهو حسابيّ بحت.

كان لـ OMT تأثيرات مثيرة للاهتمام على أداء الهواتف العادية لشركة PROXX. أمّا في الإصدار الذي لا يتضمّن OMT، يتم تجميد واجهة المستخدم لمدة ست ثوانٍ بعد تفاعل المستخدم معها. لا توجد أي ملاحظات، ويجب على المستخدم الانتظار لمدة ست ثوانٍ كاملة قبل أن يتمكن من فعل شيء آخر.

وقت استجابة واجهة المستخدم في الإصدار غير التابع لأداة إدارة الطلبات من PROXX.

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

وقت استجابة واجهة المستخدم في إصدار OMT من PROXX.

هذه مقايضة واعية: نمنح مستخدمي الأجهزة المقيَّدة تجربة يشعر أنّها أفضل بدون التعرّض للعقوبة على مستخدمي الأجهزة المتطورة.

الآثار المترتبة على بنية OMT

كما يبيّن مثال PROXX، يجعل نظام التشغيل الآلي (OMT) تطبيقك يعمل بشكل موثوق على مجموعة أكبر من الأجهزة، ولكنه لا يجعل تطبيقك أسرع:

  • أنت بصدد نقل العمل من سلسلة التعليمات الرئيسية فقط، وليس تقليل العمل.
  • قد تؤدي أحيانًا أعباء الاتصال الإضافية بين العامل على الويب والسلسلة الرئيسية إلى جعل الأمور أبطأ بشكل طفيف.

التفكير في المقايضات

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

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

ملاحظة حول الأدوات

فبالنسبة إلى مستخدمي الويب، لم يصبحوا انتشارًا واسعًا إلى الآن، لذلك لا تتيح معظم أدوات الوحدات، مثل webpack وRollup، استخدام هذه الأدوات بشكل غير تقليدي. (ومع ذلك، يمكنك استخدام Parcel؟) لحسن الحظ، هناك مكوّنات إضافية لجعل مستخدمي الويب يعملون باستخدام حزمة الويب ودمج تجميع البيانات:

التلخيص

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

بالإضافة إلى ذلك، تتميز OMT بمزايا ثانوية:

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

ليس من الضروري أن يكون عمال الويب مخيفين. حيث تعمل أدوات مثل Comlink على تفريغ مهام العمل من الموظفين وتجعلهم خيارًا قابلاً للتطبيق لمجموعة واسعة من تطبيقات الويب.

صورة رئيسية من Unsplash، من تأليف جيمس بيكوك