Setting Up the Project
The fastest path to a working Wasm module is Rust plus wasm-pack, since the toolchain handles binding generation, bundler compatibility, and packaging into an npm-ready module automatically. After installing Rust via rustup and running rustup target add wasm32-unknown-unknown, wasm-pack new my-wasm-app scaffolds a crate with a Cargo.toml pre-configured for a cdylib crate type, which is required because Wasm modules are compiled as dynamic libraries rather than standalone executables when targeting the browser. The generated lib.rs includes a #[wasm_bindgen] attribute on exported functions, which is the macro that auto-generates the JavaScript-facing glue code and TypeScript type definitions during the build.
Cricket analogy: Scaffolding a project with wasm-pack new is like a team's pre-season setup at a place like the National Cricket Academy, where the kit, nets, and fitness baseline are all prepared before a single ball is bowled in a real match.
Writing and Exporting Functions
Inside lib.rs, a function decorated with #[wasm_bindgen] and a pub fn signature becomes callable from JavaScript with automatic type marshaling for primitives like numbers, strings, and booleans. Complex types such as structs need #[wasm_bindgen] on the struct itself, and methods become JavaScript class methods on the generated binding. For performance-sensitive code that processes large arrays, it's more efficient to pass a &[u8] slice backed by shared linear memory rather than serializing data through JSON, since crossing the JS-Wasm boundary with structured data has real marshaling cost that scales with payload size.
Cricket analogy: Exporting a function with #[wasm_bindgen] is like naming a designated non-striker's runner in an old rule set — a formal declaration that makes a specific role visible and usable to the rest of the match officials.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
match n {
0 => 0,
1 => 1,
_ => {
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let c = a + b;
a = b;
b = c;
}
b
}
}
}
#[wasm_bindgen]
pub fn sum_bytes(data: &[u8]) -> u32 {
data.iter().map(|&b| b as u32).sum()
}Building and Loading in the Browser
Running wasm-pack build --target web produces a pkg directory containing the .wasm binary, a JavaScript loader, and .d.ts type definitions ready to import directly with an ES module import statement. The loader's default export is an async init function that must be awaited before calling any exported Wasm function, because instantiation itself is asynchronous — the browser needs to fetch, compile, and instantiate the binary via WebAssembly.instantiateStreaming under the hood. Once initialized, calling fibonacci(50) from JavaScript executes inside the Wasm linear memory sandbox, and the wasm-bindgen glue automatically converts the u64 return value into a JavaScript BigInt since JS numbers can't represent the full 64-bit range precisely.
Cricket analogy: Awaiting the async init function before calling Wasm exports is like waiting for the pitch report and toss result before setting a batting order — you can't finalize your plan until that preliminary step completes.
import init, { fibonacci, sum_bytes } from './pkg/my_wasm_app.js';
async function run() {
await init(); // must await before calling exports
console.log(fibonacci(50)); // returns a BigInt
const bytes = new Uint8Array([1, 2, 3, 4, 5]);
console.log(sum_bytes(bytes)); // 15
}
run();Use wasm-pack build --target bundler when integrating with webpack or Vite, --target web for direct ES module usage without a bundler, and --target nodejs when the module runs in a Node.js server context instead of a browser.
Passing large JavaScript objects across the boundary via serde-serialized JSON (using #[wasm_bindgen] with serde_wasm_bindgen) is convenient but has real overhead — for hot paths processing large arrays or buffers, use typed arrays backed by linear memory instead of JSON serialization.
- wasm-pack new scaffolds a Rust crate configured with crate-type = ["cdylib"] for Wasm compilation.
- #[wasm_bindgen] on functions and structs generates the JavaScript glue code and TypeScript definitions automatically.
- Primitives marshal automatically across the boundary; large buffers should use linear-memory-backed typed arrays instead of JSON for performance.
- wasm-pack build --target web/bundler/nodejs produces output tailored to the consuming environment.
- The generated init() function is async and must be awaited before calling any exported Wasm function.
- Rust's u64 return values become JavaScript BigInt since JS numbers can't represent the full 64-bit range.
- Choose the JSON/serde path for convenience with small structured data, and raw byte slices for performance-critical large payloads.
Practice what you learned
1. What crate type must a Rust Cargo.toml specify to compile to a browser-targeted WebAssembly module?
2. What does the #[wasm_bindgen] attribute do when applied to a public Rust function?
3. Why must the init() function from a wasm-pack build be awaited before calling exported functions?
4. Why is passing large arrays via a &[u8] slice generally faster than JSON serialization across the Wasm boundary?
5. What JavaScript type does a Rust function returning u64 map to after crossing the wasm-bindgen boundary?
Was this page helpful?
You May Also Like
WebAssembly Ecosystem Overview
A tour of the languages, toolchains, runtimes, and platforms that make up the WebAssembly ecosystem in 2026, from browser sandboxes to server-side WASI workloads.
Comparing Wasm Runtimes
A practical comparison of Wasmtime, WasmEdge, wasmer, and browser engines, covering performance, WASI support, and ideal use cases for each.
WebAssembly Interview Questions
Commonly asked WebAssembly interview questions covering the module format, memory model, security, and performance, with clear model answers.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics