ظلّت JavaScript لأكثر من عقدين لغة الويب بلا منازع، غير أن بيئة التشغيل في المتصفح لم تعد بيئة لغة واحدة. فقد صار WebAssembly في هدوء بيئة التشغيل الثانية في كل متصفح حديث، وهو يتيح قدرات لا تستطيع JavaScript وحدها تقديمها بكفاءة. محرّرات الصور، ومرمّزات الفيديو، ومحرّكات قواعد البيانات، ومحرّكات العرض ثلاثي الأبعاد، وبيئات تشغيل اللغات: كلها تعمل اليوم داخل ألسنة المتصفح، مُصرَّفة إلى صيغة ثنائية منخفضة المستوى تعمل بسرعة تقارب السرعة الأصلية.
ما WebAssembly ولماذا يهمّ
WebAssembly صيغة تعليمات ثنائية منخفضة المستوى تعمل في آلة افتراضية قائمة على المكدّس. صُمّمت لتكون هدف تصريف محمولًا للغات البرمجة، بما يتيح النشر على الويب لتطبيقات العميل والخادم معًا. تعاون في تصميمها صانعو المتصفحات الأربعة — Google وMozilla وApple وMicrosoft — وصارت معيارًا لدى W3C عام 2019. وتدعمها جميع المتصفحات الكبرى منذ 2017.
لكن WebAssembly ليس بديلًا عن JavaScript، بل مكمّلًا لها. تبقى JavaScript لغة التعامل مع DOM ومعالجة الأحداث ومنطق الواجهة. ويتولّى WebAssembly العمل كثيف الحساب الذي لم تُصمَّم له JavaScript أصلًا. تتعايش بيئتا التشغيل في اللسان نفسه: تستورد الوحدات دوالّ JavaScript، وتستدعي JavaScript صادرات Wasm عبر واجهة محدّدة بوضوح.
لا يسعى WebAssembly إلى استبدال JavaScript، بل إلى حلّ المشكلات التي لا تحلّها JavaScript بكفاءة. ومنصة الويب أقوى لأن المطوّر يستطيع اختيار الأداة المناسبة لكل مهمة داخل التطبيق الواحد.
الأساسيات: الصيغة الثنائية والذاكرة الخطّية
تستخدم وحدات WebAssembly صيغة ثنائية (ملفات .wasm) مصمّمة للتحليل السريع وصغر حجم النقل. الصيغة مضغوطة: الوحدة النمطية أصغر بكثير من JavaScript المكافئ، حتى بعد التصغير. وهي تُرمّز الأنواع والدوالّ والواردات والصادرات وإعلانات الذاكرة والتعليمات في تمثيل كثيف يستطيع المتصفح التحقّق منه وتصريفه في مرور خطّي واحد.
تعمل كل وحدة WebAssembly على آلة مكدّس مفاهيمية. تدفع التعليمات القيم إلى مكدّس التقييم وتسحبها لتنفيذ العمليات. وأنواع القيم المدعومة هي i32 وi64 وf32 وf64: أعداد صحيحة وعشرية بطول 32 و64 بتًا. ولا توجد على مستوى Wasm سلاسل نصية ولا كائنات ولا مصفوفات ولا أي نوع رفيع آخر. وكل بنية بيانات مركّبة يجب تمثيلها في الذاكرة الخطّية بصورة بايتات خام.
الذاكرة الخطّية أهم مفهوم في WebAssembly. تستطيع كل وحدة إعلان كتلة أو أكثر من الذاكرة المتّصلة القابلة لتغيير الحجم. وهذه الذاكرة مصفوفة بايتات مسطّحة تقرأ منها الوحدة وتكتب فيها بتعليمات التحميل والتخزين. يتحكّم المضيف — بيئة تشغيل JavaScript أو بيئة تشغيل Wasm — في الحجم الابتدائي والحجم الأقصى، ويستطيع توسيعها بطلب صفحات إضافية سعة كل منها 65٬536 بايتًا. ومن جهة المضيف تكون هذه الذاكرة متاحة بصورة ArrayBuffer، أي إن JavaScript تقرأ وتكتب الذاكرة نفسها التي تستخدمها الوحدة.
هذا النموذج القائم على الذاكرة المشتركة هو ما يجعل WebAssembly كفؤًا. يتبادل المضيف والوحدة البيانات دون تسلسل ودون نسخ — إذ يكتبان ببساطة في المخزن نفسه. فحين تُعيد دالة بلغة Rust سلسلة نصية إلى JavaScript، تكتب بايتاتها في الذاكرة الخطّية وتُعيد مؤشّرًا (إزاحة عددية) وطولًا. وتقرأ JavaScript تلك البايتات مباشرة من ArrayBuffer. لا تحليل JSON ولا تمرير رسائل ولا كلفة تتجاوز الاستدعاء نفسه.
بنية الوحدة والتحقّق منها
تتألّف وحدة WebAssembly من أقسام: قسم الأنواع يعرّف تواقيع الدوالّ، وقسم الدوالّ يُعلنها، وقسم الشيفرة يحوي الشيفرة الثنائية الفعلية، وقسم الذاكرة يعرّف الذاكرة الخطّية، وقسم الصادرات يتيح الدوالّ والذاكرة للمضيف. وقبل تنفيذ أي وحدة، يتحقّق المتصفح من بنيتها ويفرض سلامة الأنواع. ويضمن هذا التحقّق أن معاملات كل تعليمة تطابق أنواعها المتوقّعة، وأن كل استدعاء يشير إلى توقيع صالح، وأن كل وصول إلى الذاكرة يظلّ ضمن الحدود المعلنة. يجري ذلك في أجزاء من الثانية ويضمن ألّا تستطيع وحدة مشوّهة استغلال بيئة التشغيل.
نموذج الأمان صريح ومقيَّد. لا تستطيع وحدة WebAssembly الوصول إلى DOM ولا إجراء طلبات شبكية ولا قراءة الملفات ولا أي تفاعل مع النظام، ما لم يمنحها المضيف تلك القدرات صراحة عبر دوالّ مستوردة. تعمل الوحدة في صندوق رملي بلا وصول إلى أي شيء خارج ذاكرتها الخطّية وجدول دوالّها المستوردة. وهذا يجعل تنفيذ الوحدات القادمة من مصادر غير موثوقة آمنًا — وهي خاصية حاسمة في المتصفح وعلى الخادم معًا.
التصريف إلى Wasm: مقارنة بين Rust وC وGo وZig
تتفاوت جودة التصريف إلى WebAssembly تفاوتًا كبيرًا بحسب اللغة. فبعض اللغات تنتج وحدات محكمة كفؤة بكلفة زائدة ضئيلة. وبعضها يحتاج إلى بيئة تشغيل تضيف ميغابايتات إلى حجم الملف الثنائي. واختيار لغة المصدر يتوقّف على متطلّبات الأداء، وخبرة فريقك، ودرجة التكامل التي تحتاج إليها.
Rust: المعيار الذهبي لـ Wasm
تتمتّع Rust بأفضل دعم لـ WebAssembly بين اللغات جميعًا. فسلسلة أدوات wasm-pack تتولّى التصريف والتحسين وتوليد روابط JavaScript بسلاسة. وتنتج Rust ملفات ثنائية صغيرة لأنها بلا جامع مهملات وبلا بيئة تشغيل ثقيلة، ولأن نموذج الملكية فيها ينطبق طبيعيًا على إدارة الذاكرة الخطّية. والوحدة النمطية بلغة Rust، ومعها بضع دوالّ مُصدَّرة، تتراوح بين 10 و50 كيلوبايتًا بعد LTO والتحسين، وهو ما ينافس Wasm المكتوب يدويًا.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}! Wasm says hello.", name)
}تولّد حزمة wasm_bindgen شيفرة وصل بلغة JavaScript تتكفّل تلقائيًا برقصة الذاكرة الخطّية. فاستدعاء fibonacci من JavaScript يبدو ويُحسّ كاستدعاء أي دالة JavaScript أخرى. وخلف الكواليس تحوّل wasm_bindgen معاملات السلاسل النصية إلى أزواج من مؤشّر وطول، وتستدعي دالة Wasm، ثم تعيد تحويل النتيجة إلى سلسلة JavaScript. وهذه الطبقة تضيف كلفة ضئيلة وتجعل الجمع بين Rust وWasm عمليًا في الاستعمال اليومي.
C وC++ عبر Emscripten
Emscripten هي سلسلة أدوات التصريف الأصلية إلى Wasm. تصرّف شيفرة C وC++ باستخدام LLVM خلفيةً لها، وتوفّر طبقة توافق شاملة مع POSIX تتيح نقل التطبيقات الأصلية القائمة إلى الويب بأدنى قدر من تغيير المصدر. وتتضمّن نظام ملفات افتراضيًا، وترجمة من OpenGL إلى WebGL، ومحاكاة لـ pthread، وبيئة تشغيل بلغة JavaScript تدير دورة حياة الوحدة.
Go والخلفية الرسمية لـ Wasm
أضافت Go دعم WebAssembly في الإصدار 1.11، ولا يزال الدعم يتحسّن مع كل إصدار. وتصريف برنامج Go إلى Wasm مباشر: تضبط GOOS=js GOARCH=wasm وتشغّل go build. غير أن دعم Go تعتريه حدود واضحة قياسًا إلى Rust. إذ تُضمّن Go بيئة تشغيلها (مجدول الـ goroutine، وجامع المهملات، ومكدّس defer) في كل ملف ثنائي، فتبدأ الوحدة الدنيا عند نحو 2 ميغابايت. وهذا مقبول في الخادم حيث يقلّ أثر الحجم، لكنه مانع في المتصفح حيث زمن التنزيل والتحليل حاسم.
Zig: المنافس الجديد
تقدّم Zig نفسها بديلًا حديثًا للغة C بدعم من الدرجة الأولى لـ WebAssembly. ويستطيع مصرّف Zig استهداف Wasm مباشرة دون أغلفة مثل Emscripten. وتنتج وحدات تنافس Rust حجمًا وأداءً، وتتيح خاصية comptime برمجة وصفية قادرة على إزالة الكلفة أثناء التشغيل كليًا. ولا تحمل Zig بيئة تشغيل خفية — لا جامع مهملات ولا مُخصِّص ذاكرة ولا شيفرة إقلاع — ما لم تربطها أنت صراحة.
- تنتج Rust أصغر ملفات Wasm الثنائية (10-50 كيلوبايتًا للوحدات النمطية) ولديها أفضل منظومة أدوات مع wasm-pack وwasm-bindgen.
- C وC++ عبر Emscripten أفضل خيار لنقل قواعد شيفرة أصلية قائمة إلى Wasm، بأعلى توافق وإن كبرت الملفات الثنائية.
- تُصرَّف Go إلى Wasm بسهولة لكنها تنتج ملفات كبيرة (أكثر من 2 ميغابايت) بسبب بيئة التشغيل المضمّنة، ما يجعلها أنسب لـ Wasm على الخادم.
- تنتج Zig وحدات محكمة بلا كلفة بيئة تشغيل، وهي مثالية للاستعمالات المضمّنة والدنيا.
استعمال Wasm في المتصفح
يتبع تحميل وحدة WebAssembly وتشغيلها في المتصفح واجهة قياسية متطابقة في كل المتصفحات الحديثة. فواجهة JavaScript تتيح في فضاء الأسماء WebAssembly الدوالّ compile وinstantiate وinstantiateStreaming. والنسخة المتدفّقة مفضّلة لأنها تبدأ التصريف بينما تُنزَّل البايتات، فتتداخل عمليات الشبكة مع التصريف.
async function loadWasm(url: string) {
const response = await fetch(url);
const results = await WebAssembly.instantiateStreaming(response);
return results.instance.exports;
}
async function main() {
const wasm = await loadWasm("/fibonacci.wasm");
console.log(wasm.fibonacci(40));
}
main().catch(console.error);تُعيد instantiateStreaming كائنًا يحوي الخاصية instance (نسخة الوحدة المصرَّفة) وكائن exports بكل الدوالّ والذاكرة المعلنة صادراتٍ في الوحدة. ويمكنك أيضًا تمرير كائن imports وسيطًا ثانيًا، فيمنح الوحدة الوصول إلى دوالّ JavaScript وقيمها التي صُرِّفت وهي تتوقّعها.
في الإنتاج، تُصرَّف الوحدة عادة مسبقًا في خطوة البناء، أو تُستعمل إضافة للمُجمِّع. يدعم Webpack 5 واردات Wasm أصلًا، ولدى Vite دعم تجريبي. وتتولّى هذه المُجمِّعات الجلب والتهيئة وإدارة دورة الحياة تلقائيًا، فتستورد وحدات Wasm كأنها وحدات JavaScript. ومع wasm-pack تبدو صيغة الاستيراد هكذا: import init from './pkg/fibonacci.js'، وهو ما يمنحك دالة تهيئة قائمة على الوعود تتكفّل بكل شيفرة الوصل.
من الاعتبارات الحاسمة في المتصفح التصريف الأول. فوحدات Wasm تُصرَّف من صيغتها الثنائية إلى شيفرة أصلية عند أول تحميل. مع الوحدات الصغيرة يستغرق ذلك أجزاء من الثانية. ومع الوحدات الكبيرة — التي تتجاوز 10 ميغابايتات — قد يستغرق مئات الأجزاء من الثانية ويحجب الخيط الرئيسي. والحل استعمال WebAssembly.compileStreaming داخل Web Worker، فيجري التصريف خارج الخيط الرئيسي ويُعيد كائن وحدة مصرَّفة يستطيع الخيط الرئيسي تهيئته فورًا. وهذا النمط لا غنى عنه في التطبيقات التي تحمّل وحدات كبيرة.
WASI وWasm على الخادم
صُمّم WebAssembly أصلًا للمتصفح وحده، غير أن نموذج أمانه وخصائص أدائه يجعلانه جذّابًا على الخادم بالقدر نفسه. والتحدّي أن المتصفح يوفّر واجهات مضيف بعينها — DOM وfetch وWebSocket — تفتقر إليها بيئات الخادم. وتحلّ WebAssembly System Interface ذلك بتعريف مجموعة قياسية من نداءات النظام على نسق POSIX تستطيع وحدات Wasm استعمالها خارج المتصفح.
توفّر WASI تجريدات لإدخال الملفات وإخراجها، وللشبكة، والوصول إلى الساعة، وتوليد الأعداد العشوائية، ومتغيّرات البيئة، ومعاملات سطر الأوامر. والوحدة المصرَّفة بدعم WASI تستطيع قراءة الملفات وفتح مقابس الشبكة والتفاعل مع نظام التشغيل عبر واجهة موحّدة تعمل في أي بيئة تشغيل متوافقة. وهذا أساس تشغيل Wasm تطبيقًا خادميًا.
يتطوّر معيار WASI عبر لقطات متعاقبة. فـ WASI preview 1 هو الأساس المستقرّ الذي تدعمه اليوم معظم سلاسل الأدوات. أما WASI preview 2 فيقدّم تصميمًا منسجمًا مع نموذج المكوّنات وأمانًا قائمًا على القدرات، حيث تُعلن كل وحدة صراحة موارد النظام التي تحتاج إليها. وpreview 2 تقدّم معماري مهمّ لأنه يتيح نماذج أذونات دقيقة: فالوحدة التي تحتاج إلى قراءة ملف بعينه لا تستطيع الوصول إلى أي ملف آخر في النظام.
تحوّل WASI WebAssembly من تقنية متصفح إلى بيئة تشغيل معزولة عامّة. فالوحدة نفسها التي تعمل في لسان متصفح تستطيع العمل في بيئة تشغيل Wasm على الخادم، أو في دالة دون خادم، أو على عقدة طرفية — بلا تعديل. و«اكتب مرة، شغّل في أي مكان» تكتسب معنى جديدًا حين يشمل «أي مكان» بيئات لا تبلغها JavaScript.
بيئات تشغيل Wasm والنشر
يستلزم تشغيل WebAssembly خارج المتصفح بيئة تشغيل خادمية. وهناك عدة بيئات ناضجة، لكلٍّ فلسفتها في التصميم ومجال استعمالها. وأهمّها ثلاث: Wasmtime وWasmer وWAMR (WebAssembly Micro Runtime) للأنظمة المضمّنة.
Wasmtime
Wasmtime بيئة تشغيل مستقلّة طوّرتها Bytecode Alliance، وهي المنظّمة نفسها التي تقود معيار WASI. مكتوبة بلغة Rust، وتصرّف الوحدات باستعمال Cranelift (مولّد شيفرة صُمّم خصيصًا لـ Wasm)، وتوفّر واجهات بلغات Rust وC وPython وغيرها. وهي أكثر بيئات التشغيل استعمالًا لـ Wasm على الخادم، وتقف خلف Cloudflare Workers وFastly Compute@Edge ومنصات حوسبة طرفية أخرى.
use wasmtime::*;
fn main() -> Result<()> {
let engine = Engine::default();
let module = Module::from_file(&engine, "hello.wasm")?;
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let hello = instance
.get_typed_func::<(), ()>(&mut store, "hello")?;
hello.call(&mut store, ())?;
Ok(())
}واجهة التضمين في Wasmtime نظيفة ومتّسقة. تُنشئ Engine (بيئة التصريف)، وتحمّل Module من ملف أو من بايتات، وتُنشئ Store (سياق التنفيذ الذي يحمل ذاكرة Wasm الخطّية)، ثم تهيّئ الوحدة للوصول إلى دوالّها المُصدَّرة. وتتولّى بيئة التشغيل إدارة الذاكرة ومعالجة المصائد وتمرير نداءات نظام WASI تلقائيًا.
Wasmer
Wasmer بيئة تشغيل شائعة أخرى تدعم عدّة خلفيات تصريف — Cranelift وLLVM وSinglepass (تصريف JIT سريع بأدنى تحسين). وتوفّر Wasmer حزم تطوير للغات Rust وC وC++ وPython وGo وPHP وRuby وJava وغيرها، فتصير متاحة من أي بيئة برمجية تقريبًا. كما تقدّم سجلّ حزم (WAPM) لتوزيع وحدات Wasm، على غرار npm.
الفرق الجوهري أن Wasmer يركّز على تجربة المطوّر وسهولة التضمين، بينما يركّز Wasmtime على الالتزام بالمعيار والأمان. وكلاهما خيار ممتاز، والاختيار يتوقّف على ما تقدّره أكثر: اتّساع المنظومة (Wasmer) أم صرامة مطابقة المواصفة وسجلّ الإنتاج (Wasmtime).
- Wasmtime: الأفضل لـ Wasm الخادمي في الإنتاج، والتزام صارم بـ WASI، ومصرّف JIT هو Cranelift، وتستعمله Cloudflare وFastly.
- Wasmer: الأفضل لتضمين Wasm في تطبيقات غير مكتوبة بـ Rust، ويدعم التصريف بـ LLVM لسرعة تقارب الأصلية، وله منظومة حزم WAPM.
- WAMR: الأفضل لإنترنت الأشياء والأجهزة المضمّنة، بأثر ضئيل جدًا، وبنمطي المفسّر وJIT، ومعه أداة سطر الأوامر iwasm.
خصائص الأداء ومتى يتفوّق Wasm
يتوقّف تفوّق WebAssembly على JavaScript في الأداء على طبيعة العمل. ففي العمليات المقيّدة بالمعالج والكثيفة عدديًا، يعمل Wasm عادة أسرع بمقدار 1.5 إلى 3 أضعاف. وفي العمليات الكثيفة على الذاكرة مثل معالجة الصور أو ضغط البيانات قد يتّسع الفارق، لأن Wasm يتحكّم مباشرة في تخطيط الذاكرة وأنماط التخصيص. أما في الأعمال التي يغلب عليها الإدخال والإخراج أو التعامل مع DOM فلا يقدّم Wasm تفوّقًا، وربما أضاف كلفة بسبب الجسر بين الوحدة ومضيف JavaScript.
ونموذج التصريف عامل آخر. تستعمل JavaScript تصريف JIT: تبدأ التنفيذ فورًا ثم تحسّن المسارات الساخنة مع الوقت. أما Wasm فيُصرَّف مسبقًا أو عند تحميل الوحدة، فيبلغ ذروة أدائه من فوره — بلا فترة إحماء. وفي الدوالّ قصيرة العمر أو التنفيذات المفردة، يزيل ذلك تأخير إحماء JIT الذي قد يجعل JavaScript أبطأ عند أول استدعاء.
لكن تفوّق Wasm الحقيقي في قابلية التنبّؤ. فتحسين JIT في JavaScript غير حتمي: قد تُحسَّن الدالة نفسها أو يُتراجع عن تحسينها تبعًا للأنواع التي تتلقّاها وعدد مرات استدعائها وحالة قواعد المصرّف الاستدلالية. أما تنفيذ Wasm فحتمي. ولكل تعليمة كلفة ثابتة. لا توقّف بسبب جمع المهملات، ولا التباس أنواع، ولا تراجع عن التحسين. وهذا يجعل Wasm الخيار الصحيح حيث يكون ثبات زمن الاستجابة أهمّ من ذروة الإنتاجية النظرية: معالجة الصوت، ومحاكاة الفيزياء، والحسابات المالية.
- يتفوّق Wasm في: معالجة الصور والفيديو، وتركيب الصوت وتحليله، وضغط البيانات وفكّها، والعمليات التعموية، ومحرّكات الألعاب، ومحاكاة الفيزياء، وتنفيذ استعلامات قواعد البيانات.
- تتفوّق JavaScript في: التعامل مع DOM، ومعالجة الأحداث، وتنسيق طلبات الشبكة، وعرض الواجهات بأطر العمل، ومعالجة السلاسل والنصوص، والأدوات الصغيرة أو نادرة الاستدعاء.
- الحدّ يتحرّك: فكلما نال Wasm وصولًا إلى واجهات الويب عبر WASI ونموذج المكوّنات، انتقلت إليه أعمال أكثر، وإن ظلّت JavaScript طبقة الواجهة الأساسية.
نموذج المكوّنات ومستقبل Wasm
نموذج مكوّنات WebAssembly أهم تطوّر منذ الإصدار الأول. وهو يعالج القيد الجوهري في Wasm المبكّر: أن الوحدات كانت صوامع منعزلة بلا طريقة موحّدة للاتصال ببعضها. ويقدّم نموذج المكوّنات نظام واجهات رفيع المستوى مستقلًّا عن اللغة يتيح تركيب وحدات Wasm معًا كلبنات بناء.
في النموذج الحالي، تُصدِّر الوحدة دوالّ لا تقبل ولا تعيد إلا أعدادًا صحيحة وعشرية. وإذا احتاجت وحدة إلى تمرير سلسلة نصية أو مصفوفة أو بنية بيانات مركّبة إلى أخرى، وجب على الاثنتين الاتفاق على عرف لتخطيط الذاكرة — أي تقاسم منطقة من الذاكرة الخطّية وتنسيق التخصيص والتحرير. وهذا هشّ ومرتبط باللغة. ويحلّ نموذج المكوّنات ذلك بتعريف نظام موحّد لأنواع الواجهات يعالج السلاسل والسجلّات والمتغايرات والقوائم والبنى المتداخلة عبر حدود الوحدات.
تصف أنواع الواجهات البيانات المتدفّقة بين المكوّنات على نحو مستقلّ عن اللغة. فيستطيع مكوّن تصدير دالة تأخذ سلسلة نصية وتعيد سجلًّا بحقلين عدديين، ويستطيع أي مكوّن آخر — مكتوبًا بـ Rust أو C أو Go — استدعاءها دون أن يعرف شيئًا عن تخطيط الذاكرة الداخلي للطرف الآخر. وتتكفّل بيئة التشغيل بمنطق المواءمة تلقائيًا، فتترجم بين تمثيل الطرف المستدعِي وتمثيل الطرف المستدعَى.
دراسات حالة من الواقع
WebAssembly ليس تقنية نظرية. فهو يشغّل بعضًا من أكثر تطبيقات الويب تطلّبًا في الإنتاج اليوم، ويتسارع تبنّيه كلما نضجت الأدوات وصارت مكاسب الأداء لا تُنكَر.
Figma: رائد Wasm
Figma أشهر قصص النجاح. فمحرّك العرض الأساسي فيه مكتوب بـ C++ ومُصرَّف إلى Wasm عبر Emscripten. وتصرّف Figma قاعدة شيفرة C++ كاملة — بما فيها مكتبة الرسوميات Skia، ومُشكِّل النصوص HarfBuzz، ومحرّك التخطيط الخاص بها — في وحدة Wasm واحدة. وتتولّى طبقة JavaScript أحداث الإدخال وإدارة DOM، بينما يتولّى Wasm العرض كلّه واختبار الإصابة وحسابات التخطيط. والنتيجة أداة تصميم تعمل في لسان متصفح بأداء ينافس تطبيقات سطح المكتب الأصلية.
SQLite: قاعدة بيانات في المتصفح
SQLite على الأرجح أوسع وحدات Wasm انتشارًا في العالم. فالمشروع يوزّع بناءً رسميًا بـ Wasm يشغّل محرّك قاعدة البيانات كاملًا في المتصفح. ويستطيع المستخدمون الاستعلام من قاعدة SQLite على جانب العميل وحده، بلا أي مكوّن خادمي. ويُنتَج هذا البناء بسلسلة أدوات Emscripten ويتضمّن دعم نظام ملفات افتراضي لحفظ القواعد في IndexedDB.
والآثار كبيرة. فالتطبيقات التي تحتاج إلى قدرات استعلام على جانب العميل — أدوات تحليل البيانات، ولوحات التقارير، ودفاتر الحوسبة العلمية — تستطيع استعمال SQLite بـ Wasm لتنفيذ تجميعات معقّدة وعمليات ضمّ ودوالّ نوافذ داخل المتصفح مباشرة، دون إرسال البيانات إلى خادم. وتغلّف مكتبات مثل sql.js وbetter-sqlite3-wasm وحدة SQLite بواجهات JavaScript أنيقة، فتصير بسهولة أي حزمة npm أخرى.
Google Earth: Wasm للعرض ثلاثي الأبعاد
أُعيد بناء Google Earth للمتصفحات باستعمال Wasm في مساري معالجة التضاريس ثلاثية الأبعاد وفكّ ضغط البيانات. فبينما يتنقّل المستخدم، تتلقّى وحدة Wasm بيانات ثنائية مضغوطة، وتفكّ ضغطها بخوارزميات C++ محسّنة مُصرَّفة إلى Wasm، وتحوّل الهندسة إلى شبكات قابلة للعرض، ثم تسلّم النتائج إلى WebGL — كل ذلك ضمن ميزانية إطار حركة واحد ودون أي كلفة نسخ للبيانات.
- تستعمل Figma Wasm لمحرّك العرض المكتوب بـ C++، فتبلغ أداءً من طبقة الأداء الأصلي في تحرير الرسوميات المتّجهة داخل المتصفح.
- توزّع SQLite بناءً رسميًا بـ Wasm يشغّل محرّك قاعدة البيانات كاملًا على جانب العميل، فيتيح الاستعلام دون اتصال وبلا خادم.
- يعتمد Google Earth على Wasm لفكّ الضغط في الزمن الحقيقي ومعالجة هندسة البيانات الجغرافية المتدفّقة بستّين إطارًا في الثانية.
- تستعمل Adobe Wasm في Photoshop للويب (عبر شيفرة C++ مُصرَّفة بـ Emscripten) لمرشّحات الصور وتحويلات فضاء الألوان.
- يستعمل Zoom Wasm في عميله الوِبّي لفكّ ترميز الفيديو وتمويه الخلفية.
Wasm على الطرف وفي البيئات دون خوادم
تبنّت المنصات دون خوادم WebAssembly بيئةَ تشغيل للحوسبة الطرفية بسبب تفوّقه على الحاويات في البدء البارد. فوحدة Wasm تبدأ خلال أجزاء من المليون من الثانية — لا خلال أجزاء من الألف — لأنه لا نظام تشغيل يُقلَع، ولا عملية تُستنسَخ، ولا بيئة تشغيل تُهيّأ. وحيث يمسّ زمن البدء البارد تجربة المستخدم مباشرة، يكون ذلك تحوّلًا في الطبيعة لا في الدرجة.
كانت Cloudflare Workers أول منصة كبرى تعتنق Wasm. فالـ Workers تعمل داخل عوازل V8 قادرة على تنفيذ JavaScript ووحدات Wasm معًا. والـ Worker الذي يضع منطق الحساب في وحدة Wasm يستطيع البدء بخدمة الطلبات في أقل من 5 أجزاء من الألف من الثانية بعد البدء البارد. وتوفّر بيئة الـ Worker مجموعة محدودة من الواجهات المبنية على مواصفة Service Worker — fetch وCache وتخزين KV وDurable Objects — وتستطيع وحدات Wasm المصرَّفة بدعم WASI استعمالها عبر آلية الاستيراد في بيئة المضيف.
أما Compute@Edge من Fastly فيستعمل Wasmtime بيئةَ تشغيل أساسية ويشترط تصريف كل الحسابات إلى Wasm. ويكتب المطوّرون منطقهم الطرفي بـ Rust أو Go أو JavaScript (التي تصرّفها Fastly مسبقًا إلى Wasm)، ثم تعمل الوحدة الناتجة على عقد Fastly الطرفية. ويمنح هذا النموذج عزلًا أمنيًا متينًا — إذ يعمل كل طلب في نسخة Wasm جديدة بلا حالة مشتركة — إلى جانب سرعة البدء التي تجعل الحوسبة الطرفية عملية.
منظومة Wasm على الطرف ما تزال فتيّة لكنها تنمو بسرعة. فمنصات مثل Fermyon Spin وDeislabs تقدّم أطرًا مصمّمة خصيصًا لبناء تطبيقات Wasm دون خوادم. وهي تخفي تفاصيل بيئة WASI وتوفّر أنماطًا مألوفة — معالجات HTTP، وتخزين مفتاح-قيمة، ومهامّ مجدولة — بينما تنفّذ كل شيء عبر Wasm. والوعد أن تكتب منطق عملك مرة واحدة بأي لغة تُصرَّف إلى Wasm، ثم تنشره بلا تعديل على أي منصة متوافقة مع WASI.
كيف تبدأ مع Wasm اليوم
تتوقّف أفضل طريقة للبدء على هدفك. فإن أردت استعمال Wasm في المتصفح، فأسرع الطرق هي Rust مع wasm-pack. ثبّت Rust، وأضف الهدف wasm32-unknown-unknown، وأنشئ مشروعًا بـ wasm-pack new. ويتضمّن المشروع المولَّد مثالًا عاملًا بدالة Wasm قابلة للاستدعاء من JavaScript، ونصّ بناء ينتج حزمة متوافقة مع npm، وإطار اختبار يشغّل وحدتك في متصفح بلا واجهة.
وإن أردت استكشاف Wasm على الخادم، فابدأ بـ Wasmtime وأداة سطر الأوامر الخاصة به. ثبّت wasmtime، وصرّف أي برنامج بـ C أو Rust بدعم WASI، وشغّله بـ wasmtime program.wasm. سترى أن برنامج سطر أوامر مُصرَّفًا إلى Wasm يتصرّف تمامًا كملف ثنائي أصلي: يقرأ من الدخل القياسي، ويكتب إلى الخرج القياسي، ويصل إلى الملفات، وينتهي برمز حالة. وتجربة تشغيل برنامج Wasm في الطرفية سلسة على نحو مفاجئ.
وللتجريب على الطرف ودون خوادم، تقدّم Cloudflare Workers طبقة مجانية تدعم Wasm. اكتب دالة بـ Rust، وصرّفها إلى Wasm، وانشرها على شبكة Cloudflare العالمية. سترى أزمنة استجابة دون 10 أجزاء من الألف من الثانية من معظم المواقع، وستدرك بنفسك كيف يزيل Wasm البدء البارد من قائمة همومك.
أكثر الخطوات الأولى عملية مع Wasm ليست إعادة كتابة تطبيقك كلّه. ابحث عن دالة واحدة مكلفة حسابيًا — مرشّح، أو تحويل، أو تحقّق — وأعد كتابتها بـ Rust، وصرّفها إلى Wasm، وادمجها في قاعدة شيفرتك القائمة. ثم قِس الفرق. وحين ترى تسارعًا ثلاثيًا مقابل خمسين سطرًا من Wasm، ستفهم لماذا تهمّ هذه التقنية.
ومشهد الأدوات يتحسّن بسرعة. فـ wasm-pack وcargo-generate وwasm-tools توفّر سير عمل ناضجًا. وتقدّم سلسلة Binaryen أداة wasm-opt لتحسين الوحدات وخفض حجمها بنسبة 20 إلى 30 في المئة. وتواصل Wasmtime وWasmer تضييق الفجوة مع التنفيذ الأصلي. وسيجعل نموذج المكوّنات قريبًا وحدات Wasm متعاملة فيما بينها بطرق كانت مستحيلة حين ظهر Wasm أول مرة.
WebAssembly ليس بديلًا عن الويب كما نعرفه، بل إثراءً للمنصة — بيئة تشغيل ثانية تفعل ما لا تستطيعه JavaScript. وفي المرة القادمة التي تواجه فيها مشكلة أداء تشعر معها بأنك تصارع الطبيعة الديناميكية لـ JavaScript، فكّر فيما إذا كان الجزء الثقيل حسابيًا من شيفرتك قابلًا للاستخراج إلى وحدة Wasm. فالأدوات جاهزة للإنتاج، والمكسب حقيقي، ومستقبل Wasm — بنموذج المكوّنات وWASI preview 2 ودعم أوسع للّغات — لا يزداد إلا تحسّنًا.
صُمّمت منصة الويب لتكون قابلة للتوسيع. و WebAssembly أهمّ توسيع لها منذ عقد. وفهمه ليس اختياريًا لمن يريد بناء تطبيقات تدفع حدود ما يستطيع الويب فعله.