WebAssembly (WASM) brings near-native performance to the browser. It's a compilation target for languages like Rust, C++, and Go, running in a sandboxed environment at speeds JavaScript can't match for compute-heavy tasks. About 5% of websites now use WebAssembly for everything from image processing to video games.
What WebAssembly is
WebAssembly is a binary instruction format designed for:
| Property | Benefit |
|---|---|
| Binary format | Compact, fast to parse |
| Stack-based VM | Efficient execution |
| Sandboxed | Secure, can't access system |
| Language agnostic | Compile from many languages |
| Deterministic | Consistent behavior |
It runs alongside JavaScript, not replacing it. JavaScript handles DOM and browser APIs; WASM handles computation.
When to use WebAssembly
Good use cases
| Application | Why WASM helps |
|---|---|
| Image processing | Pixel-by-pixel manipulation |
| Video encoding/decoding | ffmpeg.wasm |
| Cryptography | Heavy number crunching |
| Physics simulations | Real-time calculations |
| Games | Game logic and rendering |
| Data compression | zlib, brotli in browser |
| Scientific computing | Numerical simulations |
| CAD/3D modeling | Complex geometry |
When JavaScript is fine
- DOM manipulation
- Event handling
- Network requests
- Simple data transformations
- Most business logic
The overhead of calling WASM makes it inefficient for many small operations.
Performance comparison
| Task | JavaScript | WebAssembly | Speedup |
|---|---|---|---|
| Image blur (1000px) | 150ms | 20ms | 7.5x |
| JSON parse (1MB) | 10ms | 50ms | 0.2x (slower!) |
| SHA-256 hash | 25ms | 5ms | 5x |
| Array sort (1M items) | 100ms | 80ms | 1.25x |
WASM excels at sustained computation. For small tasks or those involving serialization overhead, JavaScript may be faster.
Getting started with Rust
Rust has excellent WebAssembly support through wasm-pack.
Setup
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add WASM target
rustup target add wasm32-unknown-unknown
# Install wasm-pack
cargo install wasm-pack
Create a project
cargo new --lib wasm-example
cd wasm-example
Configure Cargo.toml
[package]
name = "wasm-example"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
Write Rust code
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[wasm_bindgen]
pub fn process_image(data: &mut [u8], width: u32, height: u32) {
// Grayscale conversion
for i in (0..data.len()).step_by(4) {
let r = data[i] as f32;
let g = data[i + 1] as f32;
let b = data[i + 2] as f32;
let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
data[i] = gray;
data[i + 1] = gray;
data[i + 2] = gray;
}
}
Build
wasm-pack build --target web
Output goes to pkg/:
wasm_example.js- JavaScript wrapperwasm_example_bg.wasm- WebAssembly binarywasm_example.d.ts- TypeScript definitions
Using in JavaScript
import init, { fibonacci, process_image } from './pkg/wasm_example.js';
async function main() {
// Initialize WASM module
await init();
// Call exported functions
const result = fibonacci(40);
console.log('Fibonacci:', result);
// Process image data
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
process_image(imageData.data, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
}
main();
Memory management
WASM uses linear memory—a shared ArrayBuffer:
// Allocate memory in WASM
const ptr = wasm.allocate(size);
// Write data to WASM memory
const memory = new Uint8Array(wasm.memory.buffer);
memory.set(data, ptr);
// Call WASM function
wasm.process(ptr, data.length);
// Read results
const result = memory.slice(ptr, ptr + size);
// Free memory
wasm.deallocate(ptr);
For complex data, use wasm-bindgen's automatic handling.
Web Workers with WASM
Keep the main thread responsive:
// worker.ts
import init, { heavyComputation } from './pkg/wasm_example.js';
let initialized = false;
self.onmessage = async (e) => {
if (!initialized) {
await init();
initialized = true;
}
const result = heavyComputation(e.data);
self.postMessage(result);
};
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url));
worker.postMessage(inputData);
worker.onmessage = (e) => {
console.log('Result:', e.data);
};
Streaming compilation
Load large WASM files efficiently:
// Stream and compile in parallel with download
const wasmPromise = WebAssembly.instantiateStreaming(
fetch('/module.wasm'),
importObject
);
const { instance, module } = await wasmPromise;
This compiles while downloading—faster than downloading then compiling.
Debugging
Browser DevTools
Chrome and Firefox support WASM debugging:
- Open DevTools Sources panel
- Find
.wasmfile - Set breakpoints in disassembly
Source maps
With Rust and wasm-pack:
wasm-pack build --dev # Includes debug info
Bundle size optimization
| Technique | Savings |
|---|---|
| Release build | 50-80% |
| wasm-opt | 10-30% |
| gzip/brotli | 60-70% |
| LTO | 10-20% |
# Optimized build
wasm-pack build --release
# Further optimization
wasm-opt -O3 -o optimized.wasm pkg/module_bg.wasm
Existing WASM libraries
| Library | Use case |
|---|---|
| ffmpeg.wasm | Video processing |
| Squoosh | Image compression |
| pdf.js | PDF rendering |
| sql.js | SQLite in browser |
| Pyodide | Python in browser |
Often you can use existing WASM libraries instead of writing your own.
Integration patterns
Function calls
Best for discrete computations:
const result = wasmModule.compute(input);
Shared memory
Best for large data processing:
const sharedBuffer = new SharedArrayBuffer(1024 * 1024);
const array = new Uint8Array(sharedBuffer);
// Both JS and WASM access same memory
Message passing
Best with Web Workers:
worker.postMessage({ type: 'process', data });
Summary
WebAssembly provides near-native performance for compute-heavy tasks in the browser. Use it for image processing, cryptography, games, and simulations. Rust with wasm-pack offers excellent tooling. Run WASM in Web Workers to keep the UI responsive. Use streaming compilation for large modules. For most web development, JavaScript remains sufficient—reach for WASM when profiling shows a clear bottleneck.
