Use Rust in JavaScript through wasm-pack
wasm-pack is a great tool that automagically lifts and lowers complex structs, enums, strings, etc. back and forth across the WebAssembly boundary using wasm-bindgen via the #[wasm_bindgen]
macro. It also can generate a self-contained npm package that you can use directly or distribute via npmjs.com.
This all means that you can write a library in Rust that can then be consumed by JavaScript using the magic of WebAssembly.
Let's get started by making sure that our Cargo.toml
is configured correctly:
[package]
name = "mylib"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
chrono = "0.4.31"
wasm-bindgen = "0.2.90"
ℹ crate-type = ["cdylib"]
tells the Rust compiler to generate a dynamic library. In this case, that means a .wasm
file without a start function.
📚 Read more on the wasm-pack getting started guide
📖 Did you know? dep-name = "1.2.3"
in the Cargo world is equivalent to "dep-name": "^1.2.3"
in the npm world. All version specifiers are ^
caret-ranged by default. You specify exact versions in Cargo.toml
using =1.2.3
instead.
Now that we have our Rust package configuration out of the way lets move on to the actual Rust code that we want to turn into WebAssembly:
use chrono::{Datelike, TimeZone, Utc};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Person {
#[wasm_bindgen(getter_with_clone)]
pub name: String,
pub birth_date: i64, // Unix seconds
}
#[wasm_bindgen]
impl Person {
pub fn compute_age(&self) -> i32 {
let birth_date = Utc.timestamp_opt(self.birth_date, 0).unwrap();
let current_date = Utc::now();
current_date.year() - birth_date.year()
}
}
#[wasm_bindgen]
pub fn create_alan_turing() -> Person {
Person {
name: String::from("Alan Turing"),
birth_date: -1815328800, // June 23, 1912
}
}
🕒 We're using the chrono Rust crate to do some time-related operations here.
Look at all those #[wasm_bindgen]
macros! They're doing the heavy lifting here of transforming the struct
, impl
functions, and fn
arguments & return value into WebAssembly pointers and back again. When you run wasm-pack build
Cargo will compile the Rust code with all the juicy WebAssembly wrapper code generated by #[wasm_bindgen]
and wasm-pack will also generate a nice mylib.js
file that handles all the JavaScript-side conversions for you too! It even generates a reasonably good .d.ts
file for TypeScript autocompletion.
Now is a good time to start thinking about the JavaScript side of this Rust & JavaScript story.
import init, { create_alan_turing, Person } from "./pkg/mylib.js";
await init();
const alanTuring = create_alan_turing();
console.log(alanTuring);
console.log(alanTuring.compute_age());
<script type="module" src="index.js"></script>
<p>Check the DevTools console!</p>
Now that we have our Rust code and our JavaScript code, lets run it to see it work:
Look at that 🧙♂️ we're magically using some Rust-defined functions and struct
objects from JavaScript! 😱 This is a very basic example. wasm-bindgen (the magic macro) also supports callbacks, constructors, getters & setters, and more!