WebAssembly (WASM) brings near-native performance to the web. While JavaScript remains the primary web language, WASM enables computation-heavy tasks that were previously impractical in browsers. Here's what you need to know.
What Is WebAssembly?#
WebAssembly is a binary instruction format that runs in browsers alongside JavaScript. It's:
- Fast: Near-native execution speed
- Safe: Runs in sandboxed environment
- Portable: Same code runs everywhere
- Language-agnostic: Compile from C, C++, Rust, Go, etc.
When to Use WebAssembly#
Good Use Cases#
✓ Computationally intensive tasks
- Image/video processing
- Cryptography
- Physics simulations
- Data compression
✓ Porting existing code
- C/C++ libraries
- Game engines
- Desktop applications
✓ Performance-critical algorithms
- Real-time audio processing
- Machine learning inference
- 3D rendering
When JavaScript Is Fine#
✗ DOM manipulation
✗ Simple data transformations
✗ UI interactions
✗ API calls
✗ Most business logic
Getting Started with Rust#
Setup#
1# Install Rust
2curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
3
4# Add WASM target
5rustup target add wasm32-unknown-unknown
6
7# Install wasm-pack
8cargo install wasm-packSimple Example#
1// src/lib.rs
2use wasm_bindgen::prelude::*;
3
4#[wasm_bindgen]
5pub fn fibonacci(n: u32) -> u32 {
6 match n {
7 0 => 0,
8 1 => 1,
9 _ => fibonacci(n - 1) + fibonacci(n - 2)
10 }
11}
12
13#[wasm_bindgen]
14pub fn process_image(pixels: &[u8]) -> Vec<u8> {
15 // Apply grayscale filter
16 pixels.chunks(4)
17 .flat_map(|rgba| {
18 let gray = (rgba[0] as f32 * 0.299
19 + rgba[1] as f32 * 0.587
20 + rgba[2] as f32 * 0.114) as u8;
21 vec![gray, gray, gray, rgba[3]]
22 })
23 .collect()
24}Build and Use#
# Build
wasm-pack build --target web1// Usage in JavaScript
2import init, { fibonacci, process_image } from './pkg/my_wasm.js';
3
4async function main() {
5 await init();
6
7 console.log(fibonacci(40)); // Faster than JS equivalent
8
9 const imageData = ctx.getImageData(0, 0, width, height);
10 const processed = process_image(new Uint8Array(imageData.data));
11 imageData.data.set(processed);
12 ctx.putImageData(imageData, 0, 0);
13}
14
15main();Memory Management#
Linear Memory#
1// WASM has a linear memory buffer
2const memory = new WebAssembly.Memory({ initial: 1 }); // 64KB pages
3
4// JavaScript can read/write to this memory
5const buffer = new Uint8Array(memory.buffer);
6buffer[0] = 42;Passing Complex Data#
1// Rust side
2#[wasm_bindgen]
3pub fn sum_array(arr: &[f64]) -> f64 {
4 arr.iter().sum()
5}
6
7// For more complex types, use serde
8use serde::{Deserialize, Serialize};
9
10#[derive(Serialize, Deserialize)]
11pub struct Point {
12 x: f64,
13 y: f64,
14}
15
16#[wasm_bindgen]
17pub fn distance(a: JsValue, b: JsValue) -> f64 {
18 let a: Point = serde_wasm_bindgen::from_value(a).unwrap();
19 let b: Point = serde_wasm_bindgen::from_value(b).unwrap();
20 ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt()
21}// JavaScript side
import { distance } from './pkg/my_wasm.js';
const d = distance({ x: 0, y: 0 }, { x: 3, y: 4 });
console.log(d); // 5Async and Threading#
Web Workers#
1// worker.js
2importScripts('./pkg/my_wasm.js');
3
4const { process_data } = wasm_bindgen;
5
6wasm_bindgen('./pkg/my_wasm_bg.wasm').then(() => {
7 self.onmessage = (e) => {
8 const result = process_data(e.data);
9 self.postMessage(result);
10 };
11});SharedArrayBuffer (Multi-threading)#
1// Requires enabling threads in wasm-pack
2use rayon::prelude::*;
3
4#[wasm_bindgen]
5pub fn parallel_sum(arr: &[f64]) -> f64 {
6 arr.par_iter().sum()
7}Integration Patterns#
Streaming Compilation#
1// Load and compile in parallel with download
2const wasmModule = WebAssembly.compileStreaming(
3 fetch('./module.wasm')
4);
5
6const instance = await WebAssembly.instantiate(
7 await wasmModule,
8 imports
9);Lazy Loading#
1// Load WASM only when needed
2let wasmModule = null;
3
4async function getWasmModule() {
5 if (!wasmModule) {
6 wasmModule = await import('./pkg/my_wasm.js');
7 await wasmModule.default();
8 }
9 return wasmModule;
10}
11
12// Usage
13button.addEventListener('click', async () => {
14 const wasm = await getWasmModule();
15 const result = wasm.expensive_computation(data);
16});Real-World Examples#
Image Processing#
1use image::{DynamicImage, GenericImageView};
2
3#[wasm_bindgen]
4pub fn resize_image(data: &[u8], width: u32, height: u32) -> Vec<u8> {
5 let img = image::load_from_memory(data).unwrap();
6 let resized = img.resize(width, height, image::imageops::FilterType::Lanczos3);
7
8 let mut output = Vec::new();
9 resized.write_to(&mut output, image::ImageOutputFormat::Png).unwrap();
10 output
11}PDF Generation#
1use printpdf::*;
2
3#[wasm_bindgen]
4pub fn create_pdf(title: &str, content: &str) -> Vec<u8> {
5 let (doc, page, layer) = PdfDocument::new(title, Mm(210.0), Mm(297.0), "Layer");
6 let font = doc.add_builtin_font(BuiltinFont::Helvetica).unwrap();
7
8 let current_layer = doc.get_page(page).get_layer(layer);
9 current_layer.use_text(content, 12.0, Mm(10.0), Mm(280.0), &font);
10
11 doc.save_to_bytes().unwrap()
12}Cryptography#
1use sha2::{Sha256, Digest};
2
3#[wasm_bindgen]
4pub fn hash_password(password: &str, salt: &[u8]) -> Vec<u8> {
5 let mut hasher = Sha256::new();
6 hasher.update(password.as_bytes());
7 hasher.update(salt);
8 hasher.finalize().to_vec()
9}Performance Comparison#
1// Benchmark: Fibonacci(40)
2console.time('JavaScript');
3fibonacciJS(40);
4console.timeEnd('JavaScript'); // ~1500ms
5
6console.time('WebAssembly');
7fibonacciWASM(40);
8console.timeEnd('WebAssembly'); // ~150ms (10x faster)Debugging#
Browser DevTools#
// Enable WASM debugging in Chrome
// chrome://flags/#enable-webassembly-debugging
// Source maps (with wasm-pack)
wasm-pack build --devConsole Logging from Rust#
1use web_sys::console;
2
3#[wasm_bindgen]
4pub fn my_function() {
5 console::log_1(&"Hello from WASM!".into());
6}Best Practices#
Size Optimization#
# Optimize for size
wasm-pack build --release
# Further optimization with wasm-opt
wasm-opt -O3 -o optimized.wasm input.wasmError Handling#
1use wasm_bindgen::prelude::*;
2
3#[wasm_bindgen]
4pub fn parse_json(input: &str) -> Result<JsValue, JsValue> {
5 serde_json::from_str(input)
6 .map(|v: serde_json::Value| serde_wasm_bindgen::to_value(&v).unwrap())
7 .map_err(|e| JsValue::from_str(&e.to_string()))
8}Conclusion#
WebAssembly opens new possibilities for web applications—bringing performance-critical operations to the browser that were previously only possible with native code.
Start with a clear use case where JavaScript performance is insufficient. Use Rust with wasm-bindgen for the best developer experience. Measure performance to ensure WASM actually helps your specific workload.
WebAssembly isn't a JavaScript replacement—it's a powerful complement for when you need that extra performance.