تعرَّف على كيفية استخدام أحدث ميزات WebAssembly مع توفير الدعم للمستخدمين على جميع المتصفّحات.
تم إصدار WebAssembly 1.0 قبل أربع سنوات، ولكنّ عملية التطوير لم تتوقف عند هذا الحد. تتم إضافة الميزات الجديدة من خلال عملية توحيد الاقتراحات. كما هو الحال بشكل عام مع الميزات الجديدة على الويب، يمكن أن يختلف ترتيب تنفيذها والمخططات الزمنية لها بشكل كبير بين المحرّكات المختلفة. إذا أردت استخدام هذه الميزات الجديدة، عليك التأكّد من أنّ جميع المستخدمين لديهم إمكانية الوصول إليها. ستتعرّف في هذه المقالة على نهج لتحقيق ذلك.
تعمل بعض الميزات الجديدة على تحسين حجم الرمز البرمجي من خلال إضافة تعليمات جديدة للعمليات الشائعة، وتعمل بعض الميزات الأخرى على إضافة عناصر أساسية فعّالة للأداء، وتعمل ميزات أخرى على تحسين تجربة المطوّر والدمج مع بقية الويب.
يمكنك العثور على القائمة الكاملة للاقتراحات والمراحل المتعلّقة بها في المستودع الرسمي أو تتبُّع حالة تنفيذها في المحرّكات على صفحة خارطة الطريق الرسمية للميزات.
لضمان تمكّن مستخدمي جميع المتصفّحات من استخدام تطبيقك، عليك تحديد الميزات التي تريد استخدامها. بعد ذلك، يمكنك تقسيمها إلى مجموعات استنادًا إلى توافق المتصفّح. بعد ذلك، يمكنك تجميع قاعدة بياناتك بشكل منفصل لكل مجموعة من هذه المجموعات. أخيرًا، على جانب المتصفّح، عليك رصد الميزات المتوافقة وتحميل حِزمة JavaScript وWasm المقابلة.
اختيار الميزات وتجميعها
لنطّلِع على هذه الخطوات من خلال اختيار مجموعة ميزات عشوائية كمثال. لنفترض أنّني حدّدت أنّني أريد استخدام SIMD وخيوط المعالجة ومعالجة الاستثناءات في مكتبتي لأسباب تتعلّق بالحجم والأداء. في ما يلي متصفحات الويب المتوافقة:
يمكنك تقسيم المتصفّحات إلى المجموعات النموذجية التالية للتأكّد من حصول كلّ مستخدِم على أفضل تجربة محسّنة:
- المتصفّحات المستندة إلى 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__
للاختيار بشكل مشروط بين عمليات التنفيذ المتوازي (العمليات المتعدّدة والمخطّط المتسلسل للتعليمات) للوظائف نفسها وعمليات التنفيذ التسلسلي في وقت الترجمة. سيظهر الإجراء على النحو التالي:
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 الجديدة في جميع المتصفّحات.