تطوير الويب

تجميع Rust إلى WebAssembly بـ wasm-bindgen: interop JavaScript

4 min de lecture

📌 الدليل الرئيسي: 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-bindgen
  • github.com/rustwasm/wasm-pack/releases
  • rustc — wasm32-unknown-unknown — صفحة الهدف الرسمية
  • serde-wasm-bindgen على crates.io

سلسلة wasm-pack + wasm-bindgen تُتيح إضافة حساب عالي الأداء لتطبيق JavaScript في أوامر قليلة. الكلفة الأوّلية تُسَدَّد فور استهداف دوالّ تثقل فعلًا في profiling. للمعماريات جانب الخادم، انتقل إلى هدف wasm32-wasip2 وruntime مثل Wasmtime.

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité