wasm-bindgen & web-sys
Wasm secara default hanya bisa komunikasi dengan angka. wasm-bindgen otomatis generate glue code agar Rust bisa berinteraksi dengan JavaScript objects, DOM, dan Web APIs secara ergonomis.
wasm-bindgen: JS ↔ Rust Bridge
use wasm_bindgen::prelude::*;
// Export ke JavaScript
#[wasm_bindgen]
pub fn process_data(input: &str) -> String {
format!("Processed: {}", input.to_uppercase())
}
// Import dari JavaScript
#[wasm_bindgen]
extern "C" {
// Panggil console.log
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
// Panggil alert
fn alert(s: &str);
// Akses window.location.href
#[wasm_bindgen(js_namespace = window, js_name = location)]
static LOCATION: JsValue;
}
#[wasm_bindgen]
pub fn greet(name: &str) {
log(&format!("Halo dari Rust: {}", name));
}
web-sys: DOM Access dari Rust
// Cargo.toml
[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = [
"Document", "Element", "HtmlElement",
"Window", "Node", "console",
"HtmlCanvasElement", "CanvasRenderingContext2d",
] }
use wasm_bindgen::prelude::*;
use web_sys::{Document, Element, Window};
#[wasm_bindgen]
pub fn create_element(tag: &str, text: &str) -> Result<(), JsValue> {
// Dapatkan window dan document
let window: Window = web_sys::window()
.ok_or("no window")?;
let document: Document = window.document()
.ok_or("no document")?;
// Buat elemen baru
let element: Element = document.create_element(tag)?;
element.set_text_content(Some(text));
element.set_attribute("class", "from-rust")?;
// Tambahkan ke body
document.body()
.ok_or("no body")?
.append_child(&element)?;
Ok(())
}
js-sys: JavaScript Built-in Objects
use js_sys::{Array, Date, Math, JSON, Object};
#[wasm_bindgen]
pub fn demo_js_sys() {
// Math
let random = Math::random();
let sqrt = Math::sqrt(144.0); // 12.0
// Date
let now = Date::now(); // timestamp ms
// Array
let arr = Array::new();
arr.push(&JsValue::from(1));
arr.push(&JsValue::from(2));
// JSON
let obj = Object::new();
let json_str = JSON::stringify(&obj).unwrap();
}
Closure / Callback Pattern
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
#[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let button = document.get_element_by_id("my-btn").unwrap();
// Rust closure sebagai JS event listener
let closure = Closure::wrap(Box::new(move || {
web_sys::console::log_1(&"Button clicked! (from Rust)".into());
}) as Box<dyn FnMut()>);
button.add_event_listener_with_callback(
"click",
closure.as_ref().unchecked_ref(),
)?;
closure.forget(); // ⚠️ Jangan drop closure
Ok(())
}
Tips
- Feature flags — web-sys butuh feature flag per API. Hanya enable yang dipakai untuk output kecil
- Closure::forget() — Untuk event listener permanent. Atau simpan di static untuk cleanup
- Error handling — Gunakan Result<(), JsValue> untuk propagate JS errors ke Rust