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

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

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

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

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

ما هي أسباب اختيار الموظفين على الويب؟

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

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

يمكن أن يؤدي تنفيذ سلسلة التعليمات الرئيسية الأقل إلى استخدام ميزة سرعة عرض أكبر محتوى مرئي (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. في الإصدار الذي لا يستند إلى بروتوكول OT، يتم تجميد واجهة المستخدم لمدة ست ثوانٍ بعد تفاعل المستخدم معها. ليست هناك ملاحظات، ويجب على المستخدم الانتظار لمدة ست ثوانٍ كاملة قبل أن يتمكن من تنفيذ شيء آخر.

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

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

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

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

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

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

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

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

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

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

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

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

الملخص

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

أيضًا، يتميّز OMT بمزايا ثانوية:

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

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

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