اكتشاف ميزة WebAssembly

تعرّف على كيفية استخدام أحدث ميزات WebAssembly مع توفير الدعم للمستخدمين في جميع المتصفحات.

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

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

يمكنك العثور على القائمة الكاملة للاقتراحات ومراحلها في المستودع الرسمي أو تتبُّع حالة التنفيذ في المحرّكات على صفحة خارطة طريق الميزات الرسمية.

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

انتقاء الميزات وتجميعها

لنطّلِع على هذه الخطوات باختيار مجموعة من الميزات العشوائية كمثال. لنفترض أنّني تعلّمت أنّني أريد استخدام SIMD وسلاسل المحادثات والتعامل مع الاستثناءات في مكتبتي لأسباب تتعلّق بالحجم والأداء. في ما يلي خطوات دعم المتصفِّح:

جدول يعرض توافق المتصفِّح مع الميزات المحدَّدة
يمكنك الاطّلاع على جدول الميزات هذا على webassembly.org/Roadmap.

يمكنك تقسيم المتصفّحات إلى المجموعات النموذجية التالية للتأكّد من حصول كل مستخدِم على أفضل تجربة محسَّنة:

  • المتصفّحات المستندة إلى Chrome: تتوافق جميعها مع سلاسل المحادثات وشريحة SIMD والتعامل مع الاستثناءات.
  • Firefox: يمكن استخدام سلاسل التعليمات وSIMD، بينما لا يُسمح بمعالجة الاستثناءات.
  • Safari: يمكن استخدام سلاسل المحادثات، بينما لا يُسمح باستخدام شريحة SIMD أو معالجة الاستثناءات.
  • في المتصفحات الأخرى: افترض أن تتوافق مع WebAssembly الأساسي فقط.

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

التجميع لمجموعات الخصائص المختلفة

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

تختلف كل سلسلة أدوات ونظام تصميم، وستحتاج إلى الرجوع إلى وثائق برنامج التجميع الخاص بك لمعرفة كيفية تعديل هذه الميزات. ولتبسيط الأمر، سأستخدم مكتبة C++ لملف واحد في المثال التالي وسأوضح كيفية تجميعه باستخدام Emscripten.

سأستخدم SIMD من خلال محاكاة SSE2، وسلاسل المحادثات عبر دعم مكتبة Pthreads، والاختيار بين معالجة استثناءات Wasm وتنفيذ JavaScript الاحتياطي:

# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions

ويمكن لرمز C++ نفسه استخدام #ifdef __EMSCRIPTEN_PTHREADS__ و#ifdef __SSE2__ للاختيار بشكل مشروط بين عمليات تنفيذ الدوال (السلاسل وSIMD) المتوازية نفسها وعمليات التنفيذ التسلسلية في وقت التجميع. ستبدو هكذا:

void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
  // …implementation using threads and SIMD for max speed
#else
  // …implementation using threads but not SIMD
#endif
#else
  // …fallback implementation for browsers without those features
#endif
}

ولا تحتاج معالجة الاستثناء إلى أوامر #ifdef، لأنّه يمكن استخدامه بالطريقة نفسها من C++ بغض النظر عن طريقة التنفيذ الأساسية التي تم اختيارها من خلال علامات التجميع.

جارٍ تحميل الحزمة الصحيحة

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

import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';

let initModule;
if (await threads()) {
  if (await simd()) {
    if (await exceptions()) {
      initModule = import('./main.threads-simd-exceptions.mjs');
    } else {
      initModule = import('./main.threads-simd.mjs');
    }
  } else {
    initModule = import('./main.threads.mjs');
  }
} else {
  initModule = import('./main.basic.mjs');
}

const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would

الكلمات الأخيرة

في هذه المشاركة، وضحت كيفية اختيار الحزم وإنشاءها والتبديل بينها لمجموعات الميزات المختلفة.

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

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