اكتشاف ميزة WebAssembly

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

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

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

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

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

اختيار الميزات وتجميعها

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

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

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

  • المتصفِّحات المستنِدة إلى Chrome: تتوفّر سلاسل المحادثات وشرائح SIMD والتعامل مع الاستثناءات.
  • Firefox: يمكن استخدام Thread و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 الجديدة على جميع المتصفّحات.