| name | wasm-bindgen-js-interop |
| description | wasm-bindgen Rust↔JavaScript interop — export Rust functions/structs to JS, import JS APIs into Rust, wasm-pack build pipeline, TypeScript type generation, memory management across the boundary. Sources: rustwasm/wasm-bindgen (MIT/Apache-2.0). |
/wasm-bindgen-js-interop
When to Use
- Porting compute-intensive Rust to the browser (crypto, image processing, parsers)
- Exposing Rust structs with methods as JavaScript classes
- Calling browser APIs (DOM, fetch, console) from within Rust/WASM
- Generating TypeScript types for Rust WASM exports
Do NOT use for
- Server-side Node.js native addons (use [[napi-rs-native-addons]])
- Sandboxed untrusted code execution (use [[wasmtime-wasi-sandbox]])
- Pure JavaScript performance optimization
Export Rust → JavaScript
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
#[wasm_bindgen]
pub struct Parser {
data: Vec<u8>,
}
#[wasm_bindgen]
impl Parser {
#[wasm_bindgen(constructor)]
pub fn new(input: &[u8]) -> Parser {
Parser { data: input.to_vec() }
}
pub fn parse(&self) -> JsValue {
let result = serde_json::json!({ "length": self.data.len() });
serde_wasm_bindgen::to_value(&result).unwrap()
}
pub fn free_memory(self) {
drop(self);
}
}
Import JavaScript → Rust
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = performance)]
fn now() -> f64;
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
#[wasm_bindgen(js_namespace = window)]
fn fetch(url: &str) -> js_sys::Promise;
}
#[wasm_bindgen]
pub fn benchmark(n: u32) -> f64 {
let start = now();
for _ in 0..n { }
now() - start
}
wasm-pack build pipeline
cargo install wasm-pack
wasm-pack build --target bundler --out-dir pkg
wasm-pack build --target nodejs --out-dir pkg
wasm-pack build --target web --out-dir pkg
JavaScript/TypeScript consumption
import init, { Parser, greet } from './pkg/my_lib.js';
async function main() {
await init();
console.log(greet("World"));
const parser = new Parser(new Uint8Array([1, 2, 3]));
const result = parser.parse();
parser.free();
}
Memory management across the boundary
const parser = new Parser(data);
try {
return parser.parse();
} finally {
parser.free();
}
#[wasm_bindgen]
pub fn process_buffer(buf: &js_sys::Uint8Array) -> js_sys::Uint8Array {
let data = buf.to_vec();
js_sys::Uint8Array::from(data.as_slice())
}
Async Rust → JS Promise
use wasm_bindgen_futures::future_to_promise;
#[wasm_bindgen]
pub fn fetch_and_parse(url: String) -> js_sys::Promise {
future_to_promise(async move {
let window = web_sys::window().unwrap();
let resp = wasm_bindgen_futures::JsFuture::from(window.fetch_with_str(&url)).await?;
let resp: web_sys::Response = resp.dyn_into()?;
let json = wasm_bindgen_futures::JsFuture::from(resp.json()?).await?;
Ok(json)
})
}
Anti-Fake-Pass Checklist
❌ Not calling .free() on WASM structs → WASM heap leaks; JS GC cannot collect WASM memory
❌ Not calling init() before using exports → "module not instantiated" runtime error
❌ Passing large Vec<u8> by value → copies entire buffer; use js_sys::Uint8Array reference
❌ Using bundler target for plain HTML → bundler target requires a module bundler; use web target
❌ Importing DOM APIs without web-sys feature flags → must enable specific web-sys features in Cargo.toml
❌ Running wasm-bindgen output in Node.js with bundler target → use nodejs target for Node.js