📌 الدليل الرئيسي: WebAssembly في الإنتاج
تجميع Rust إلى WebAssembly صار الطريق المرجعي لإضافة حساب عالي الأداء في تطبيق JavaScript قائم. الثنائي wasm-pack + wasm-bindgen يُوَفِّر سلسلة قابلة للإعادة: نكتب Rust اصطلاحي، نُؤَشِّر الدوالّ المعروضة، نُشَغِّل أمرًا، ونحصل على حزمة npm جاهزة للاستيراد في Vite أو Webpack أو مباشرة في <script type="module">.
يتبع هذا الدليل السلسلة الكاملة بمشروع مُختزَل (حساب Fibonacci ثم parseur JSON مُبَسَّط) يُظهر كيف نُمَرِّر سلاسل، مصفوفات، وكائنات بين Rust وJavaScript دون glue يدوي. الأوامر نُفِّذت مع Rust 1.83، wasm-pack 0.14.0، وwasm-bindgen 0.2.120.
الخطوة 1 — تحضير بيئة الترجمة
rustup --version
rustup show
rustup target add wasm32-unknown-unknown
إن ظهر الهدف في خرج rustup show، كلّ شيء جاهز جانب Rust. ميزة wasm32-unknown-unknown للتشغيل البيني في المتصفّح أنّها لا تُضَمِّن أيّ ABI POSIX: الـ binary الناتج مُختزَل.
cargo install wasm-pack
wasm-pack --version
الخرج المتوقّع wasm-pack 0.14.0 أو أحدث. wasm-pack يُنَسِّق الترجمة، استدعاء wasm-bindgen لتوليد binding JavaScript، وإنتاج حزمة npm-ready.
الخطوة 2 — إنشاء مشروع Rust وضبط Cargo
cargo new --lib wasm-demo
cd wasm-demo
[package]
name = "wasm-demo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2.120"
الإعلان المزدوج ["cdylib", "rlib"] يُتيح لـ cargo test استعمال الكود كمكتبة Rust قياسية، مع كشف export C-compatible الضروري لـ WebAssembly.
الخطوة 3 — كتابة أوّل دالّة مكشوفة لـ JavaScript
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
if n < 2 {
return n as u64;
}
let mut a: u64 = 0;
let mut b: u64 = 1;
for _ in 2..=n {
let next = a + b;
a = b;
b = next;
}
b
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Bonjour, {} ! Bienvenue dans WebAssembly.", name)
}
التعليقات #[wasm_bindgen] تُوَجِّه المُولِّد لإنتاج wrappers JavaScript. الـ binary .wasm سيُصَدِّر fibonacci وgreet، وملفّ JS المُوَلَّد سيكشف دوالّ مماثلة الأسماء مع توقيعها TypeScript.
الخطوة 4 — الترجمة بـ wasm-pack
ثلاثة أهداف متاحة: --target web لاستيراد مباشر في HTML، --target bundler لـ Vite/Webpack/Rollup، --target nodejs للاستعمال في Node. لأوّل تجربة، web.
wasm-pack build --target web --release
في النهاية، مجلّد pkg/ يحوي 5 ملفّات: wasm_demo.js (loader JS)، wasm_demo_bg.wasm (الـ binary)، wasm_demo.d.ts (الأنواع TypeScript)، wasm_demo_bg.wasm.d.ts، وpackage.json جاهز للنشر على npm.
--release يُفَعِّل opt-level=3 لـ LLVM، ثم wasm-opt من Binaryen يُطَبَّق افتراضيًّا في -Os. على دالّة Fibonacci، الـ binary أقلّ من 20 ko؛ على parseur كامل بـ serde، يصل بسرعة إلى 200-400 ko.
الخطوة 5 — تحميل module من صفحة HTML ساكنة
<!doctype html>
<html lang="fr">
<head><meta charset="utf-8"><title>WASM demo</title></head>
<body>
<pre id="out">chargement...</pre>
<script type="module">
import init, { fibonacci, greet } from './pkg/wasm_demo.js';
await init();
const out = document.getElementById('out');
out.textContent =
greet('Aminata') + '\n' +
'fib(30) = ' + fibonacci(30);
</script>
</body></html>
python3 -m http.server 8080
افتح http://localhost:8080/: الصفحة تعرض التحيّة وقيمة fib(30) = 832040. إن رأيت « Failed to fetch wasm_demo_bg.wasm »، المسار النسبي مكسور.
الخطوة 6 — تمرير بُنى معقّدة عبر serde
[dependencies]
wasm-bindgen = "0.2.120"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6"
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Order { amount: f64, currency: String }
#[derive(Serialize)]
struct OrderSummary { amount_usd: f64, label: String }
#[wasm_bindgen]
pub fn summarize(order: JsValue) -> Result {
let o: Order = serde_wasm_bindgen::from_value(order)?;
let rate = match o.currency.as_str() {
"EUR" => 1.10,
"SAR" => 0.27,
_ => return Err(JsValue::from_str("devise non gérée")),
};
let s = OrderSummary {
amount_usd: o.amount * rate,
label: format!("{} {} => USD", o.amount, o.currency),
};
Ok(serde_wasm_bindgen::to_value(&s)?)
}
في JavaScript، نستدعي summarize({ amount: 12.5, currency: 'EUR' }) ونحصل على كائن { amount_usd: 13.75, label: '12.5 EUR => USD' }. التحويل يتمّ مرّتين: تسلسل في JS، فكّ تسلسل في Rust، ثم العكس.
الخطوة 7 — دمج module في مشروع Vite أو Webpack
wasm-pack build --target bundler --release
cd /chemin/vers/le/projet-vite
npm install /chemin/vers/wasm-demo/pkg
// src/App.tsx
import init, { fibonacci, summarize } from 'wasm-demo';
await init();
console.log('fib(40) =', fibonacci(40));
console.log(summarize({ amount: 100, currency: 'SAR' }));
Vite يُدير تلقائيًّا التقسيم إلى chunks وstreaming الـ .wasm عبر WebAssembly.instantiateStreaming. التجربة سلسة كاستيراد TypeScript.
الخطوة 8 — قياس المكسب الحقيقي
قبل هجرة جزء معتبر من الكود، نُكَمِّم المكسب. على Fibonacci تكراري، عامل التسارع مقابل JS صرف متواضع (V8 يُحَسِّن جيّدًا الحلقات الصحيحة). المكسب يصير ضخمًا على أحمال تُتعب V8: فكّ تشفير صور، parsing بروتوكولات ثنائية، حسابات عشرية مع تخصيصات كثيرة.
const t0 = performance.now();
for (let i = 0; i < 1000; i++) fibonacci(35);
const dt = performance.now() - t0;
console.log('Rust wasm:', dt.toFixed(2), 'ms');
على parsing JSON ضخم Rust + serde مقابل JSON.parse الأصلي، module Rust نادرًا ما يكون أسرع: JSON.parse مكتوب بـ C++ مُحَسَّن. لكن على parsing صيغة ثنائية اختصاصية بتحقّق قوي، نُلاحظ بانتظام عوامل 3 إلى 8.
الخطوة 9 — أخطاء شائعة وحلولها
| العَرَض | السبب | الحلّ |
|---|---|---|
| error[E0463]: can't find crate for std | هدف wasm32-unknown-unknown غائب | rustup target add wasm32-unknown-unknown |
| wasm-pack: command not found | Binaire ليس في PATH | أضف ~/.cargo/bin إلى PATH |
| module يُحَمَّل لكن الدوالّ undefined | غياب crate-type = ["cdylib"] |
عَدِّل Cargo.toml وأعد الترجمة |
| TypeError: Cannot read .wasm على Vite | هدف خاطئ (web بدل bundler) | أعد الترجمة بـ --target bundler |
حجم .wasm فوق 1 Mo |
ترجمة debug أو crates ثقيلة | أضف --release وopt-level = "s" |
| خطأ MIME application/wasm | خادم ساكن سيّء الإعداد | هَيِّئ MIME أو استعمل bundler |
تحسين حجم binary للإنتاج
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
panic = "abort"
الزوج opt-level = "s" وlto = true يطلب من LLVM تفضيل الحجم مع link-time optimization. panic = "abort" يُزيل كود unwinding (عديم الفائدة في WebAssembly جانب المتصفّح). على مشروع العرض، هذه الإعدادات تجعل الـ binary ينتقل من 92 ko إلى 38 ko.
لاحقًا، wasm-pack يستدعي wasm-opt Binaryen بـ -Os افتراضيًّا. لدفع أبعد، اضبط WASM_OPT_FLAGS="-Oz". تجنّب التبعيّات التي تجرّ tokio أو async-std في جانب المتصفّح.
للتعمّق
بمجرّد إتقان السلسلة جانب المتصفّح، اتّجاهان مفتوحان. الأوّل استعمال نفس module Rust جانب الخادم — دليل WASI وخوادم WebAssembly مع Wasmtime. الثاني نشره في الحافّة — دليل Cloudflare Workers Rust.
للأمن — عزل agent IA، تنفيذ كود مستخدم في sandbox — دليل عزل agent IA في sandbox WebAssembly يُعَمِّق نموذج القدرات لمكوّن WASI 0.2.
مصادر رسمية
- The wasm-bindgen Guide — توثيق مرجع التعليقات
- The wasm-pack Book — سلسلة build الكاملة
github.com/wasm-bindgen/wasm-bindgengithub.com/rustwasm/wasm-pack/releases- rustc — wasm32-unknown-unknown — صفحة الهدف الرسمية
serde-wasm-bindgenعلى crates.io
سلسلة wasm-pack + wasm-bindgen تُتيح إضافة حساب عالي الأداء لتطبيق JavaScript في أوامر قليلة. الكلفة الأوّلية تُسَدَّد فور استهداف دوالّ تثقل فعلًا في profiling. للمعماريات جانب الخادم، انتقل إلى هدف wasm32-wasip2 وruntime مثل Wasmtime.