Loading content...
WebAssembly (WASM) has matured from an experimental technology to a production-ready platform powering some of the most demanding web applications. From video editing in the browser to machine learning inference, WASM is enabling experiences that were previously impossible or impractical on the web.
JavaScript Limitations:
WebAssembly Advantages:
Image Processing (1000 images):
JavaScript: 8.5 seconds
WebAssembly: 1.2 seconds
Speedup: 7x faster
3D Rendering (60 FPS):
JavaScript: 45 FPS average
WebAssembly: 58 FPS average
Improvement: 29% smoother
Machine Learning Inference:
JavaScript: 450ms per prediction
WebAssembly: 85ms per prediction
Speedup: 5.3x faster
Rust (Recommended):
// 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 struct ImageProcessor {
width: u32,
height: u32,
}
#[wasm_bindgen]
impl ImageProcessor {
#[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32) -> ImageProcessor {
ImageProcessor { width, height }
}
pub fn apply_grayscale(&self, pixels: &mut [u8]) {
for chunk in pixels.chunks_exact_mut(4) {
let gray = (chunk[0] as f32 * 0.299
+ chunk[1] as f32 * 0.587
+ chunk[2] as f32 * 0.114) as u8;
chunk[0] = gray;
chunk[1] = gray;
chunk[2] = gray;
}
}
pub fn apply_blur(&self, pixels: &mut [u8], radius: u32) {
// Efficient blur implementation
let kernel_size = (radius * 2 + 1) as usize;
// ... blur algorithm
}
}
C/C++ (AssemblyScript):
// assembly/index.ts
export function add(a: i32, b: i32): i32 {
return a + b
}
export function processArray(arr: Int32Array): Int32Array {
const len = arr.length
const result = new Int32Array(len)
for (let i = 0; i < len; i++) {
result[i] = arr[i] * 2
}
return result
}
export class Matrix {
private data: Float64Array
private rows: i32
private cols: i32
constructor(rows: i32, cols: i32) {
this.rows = rows
this.cols = cols
this.data = new Float64Array(rows * cols)
}
get(row: i32, col: i32): f64 {
return this.data[row * this.cols + col]
}
set(row: i32, col: i32, value: f64): void {
this.data[row * this.cols + col] = value
}
multiply(other: Matrix): Matrix {
// Matrix multiplication
const result = new Matrix(this.rows, other.cols)
// ... implementation
return result
}
}
Rust with wasm-pack:
# Initialize project
cargo new --lib my_wasm_project
cd my_wasm_project
# Add dependencies to Cargo.toml
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
# Build
wasm-pack build --target web
# Output: pkg/ directory with .wasm and .js files
Using in JavaScript:
// Load and use WASM module
import init, { ImageProcessor, fibonacci } from './pkg/my_wasm_project.js'
async function loadWasm() {
// Initialize WASM
await init()
// Use fibonacci function
console.log(fibonacci(10)) // 55
// Create image processor
const processor = new ImageProcessor(1920, 1080)
// Process image data
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const ctx = canvas.getContext('2d')!
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
// Apply grayscale (processed in WASM)
processor.apply_grayscale(imageData.data)
// Apply blur
processor.apply_blur(imageData.data, 5)
ctx.putImageData(imageData, 0, 0)
}
loadWasm()
Professional Photo Editor:
// Rust WASM for image filters
use image::{DynamicImage, GenericImageView};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct PhotoEditor {
image: Option<DynamicImage>,
}
#[wasm_bindgen]
impl PhotoEditor {
#[wasm_bindgen(constructor)]
pub fn new() -> PhotoEditor {
PhotoEditor { image: None }
}
pub fn load_image(&mut self, data: &[u8]) -> Result<(), JsValue> {
match image::load_from_memory(data) {
Ok(img) => {
self.image = Some(img);
Ok(())
},
Err(e) => Err(JsValue::from_str(&e.to_string()))
}
}
pub fn adjust_brightness(&mut self, value: f32) -> Vec<u8> {
if let Some(img) = &self.image {
let adjusted = img.brighten(value as i32);
adjusted.to_bytes()
} else {
vec![]
}
}
pub fn apply_sepia(&mut self) -> Vec<u8> {
if let Some(img) = &self.image {
let mut result = img.to_rgba8();
for pixel in result.pixels_mut() {
let r = pixel[0] as f32;
let g = pixel[1] as f32;
let b = pixel[2] as f32;
pixel[0] = ((r * 0.393) + (g * 0.769) + (b * 0.189)).min(255.0) as u8;
pixel[1] = ((r * 0.349) + (g * 0.686) + (b * 0.168)).min(255.0) as u8;
pixel[2] = ((r * 0.272) + (g * 0.534) + (b * 0.131)).min(255.0) as u8;
}
result.to_vec()
} else {
vec![]
}
}
pub fn resize(&mut self, width: u32, height: u32) -> Vec<u8> {
if let Some(img) = &self.image {
let resized = img.resize(width, height, image::imageops::FilterType::Lanczos3);
resized.to_bytes()
} else {
vec![]
}
}
}
Performance Comparison:
Filter Application (4K image):
JavaScript (Canvas API): 850ms
WebAssembly (Rust): 95ms
Speedup: 9x faster
Batch Processing (100 images):
JavaScript: 85 seconds
WebAssembly: 9.5 seconds
Speedup: 9x faster
Physics Simulation:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct PhysicsEngine {
bodies: Vec<Body>,
gravity: f64,
}
struct Body {
x: f64,
y: f64,
vx: f64,
vy: f64,
mass: f64,
}
#[wasm_bindgen]
impl PhysicsEngine {
#[wasm_bindgen(constructor)]
pub fn new(gravity: f64) -> PhysicsEngine {
PhysicsEngine {
bodies: Vec::new(),
gravity,
}
}
pub fn add_body(&mut self, x: f64, y: f64, vx: f64, vy: f64, mass: f64) {
self.bodies.push(Body { x, y, vx, vy, mass });
}
pub fn step(&mut self, dt: f64) {
// Calculate forces
let len = self.bodies.len();
let mut forces: Vec<(f64, f64)> = vec![(0.0, 0.0); len];
for i in 0..len {
for j in (i + 1)..len {
let dx = self.bodies[j].x - self.bodies[i].x;
let dy = self.bodies[j].y - self.bodies[i].y;
let dist_sq = dx * dx + dy * dy;
let dist = dist_sq.sqrt();
if dist > 0.01 {
let force = (self.gravity * self.bodies[i].mass * self.bodies[j].mass) / dist_sq;
let fx = force * dx / dist;
let fy = force * dy / dist;
forces[i].0 += fx;
forces[i].1 += fy;
forces[j].0 -= fx;
forces[j].1 -= fy;
}
}
}
// Update positions and velocities
for i in 0..len {
let ax = forces[i].0 / self.bodies[i].mass;
let ay = forces[i].1 / self.bodies[i].mass;
self.bodies[i].vx += ax * dt;
self.bodies[i].vy += ay * dt;
self.bodies[i].x += self.bodies[i].vx * dt;
self.bodies[i].y += self.bodies[i].vy * dt;
}
}
pub fn get_positions(&self) -> Vec<f64> {
let mut positions = Vec::with_capacity(self.bodies.len() * 2);
for body in &self.bodies {
positions.push(body.x);
positions.push(body.y);
}
positions
}
}
High-Performance Encryption:
use wasm_bindgen::prelude::*;
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
#[wasm_bindgen]
pub struct Crypto {
cipher: Aes256Gcm,
}
#[wasm_bindgen]
impl Crypto {
#[wasm_bindgen(constructor)]
pub fn new(key: &[u8]) -> Result<Crypto, JsValue> {
if key.len() != 32 {
return Err(JsValue::from_str("Key must be 32 bytes"));
}
let key = Key::from_slice(key);
let cipher = Aes256Gcm::new(key);
Ok(Crypto { cipher })
}
pub fn encrypt(&self, plaintext: &[u8], nonce: &[u8]) -> Result<Vec<u8>, JsValue> {
if nonce.len() != 12 {
return Err(JsValue::from_str("Nonce must be 12 bytes"));
}
let nonce = Nonce::from_slice(nonce);
self.cipher
.encrypt(nonce, plaintext)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
pub fn decrypt(&self, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>, JsValue> {
if nonce.len() != 12 {
return Err(JsValue::from_str("Nonce must be 12 bytes"));
}
let nonce = Nonce::from_slice(nonce);
self.cipher
.decrypt(nonce, ciphertext)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
}
// Hash functions
#[wasm_bindgen]
pub fn sha256_hash(data: &[u8]) -> Vec<u8> {
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize().to_vec()
}
#[wasm_bindgen]
pub fn pbkdf2_derive_key(
password: &[u8],
salt: &[u8],
iterations: u32
) -> Vec<u8> {
use pbkdf2::{pbkdf2_hmac};
use sha2::Sha256;
let mut key = vec![0u8; 32];
pbkdf2_hmac::<Sha256>(password, salt, iterations as usize, &mut key);
key
}
Neural Network in WASM:
use wasm_bindgen::prelude::*;
use ndarray::{Array2, Array1};
#[wasm_bindgen]
pub struct NeuralNetwork {
weights1: Array2<f64>,
bias1: Array1<f64>,
weights2: Array2<f64>,
bias2: Array1<f64>,
}
#[wasm_bindgen]
impl NeuralNetwork {
#[wasm_bindgen(constructor)]
pub fn new(input_size: usize, hidden_size: usize, output_size: usize) -> NeuralNetwork {
use rand::Rng;
let mut rng = rand::thread_rng();
let weights1 = Array2::from_shape_fn((input_size, hidden_size), |_| {
rng.gen_range(-1.0..1.0)
});
let bias1 = Array1::from_shape_fn(hidden_size, |_| rng.gen_range(-1.0..1.0));
let weights2 = Array2::from_shape_fn((hidden_size, output_size), |_| {
rng.gen_range(-1.0..1.0)
});
let bias2 = Array1::from_shape_fn(output_size, |_| rng.gen_range(-1.0..1.0));
NeuralNetwork {
weights1,
bias1,
weights2,
bias2,
}
}
pub fn predict(&self, input: Vec<f64>) -> Vec<f64> {
let input = Array1::from_vec(input);
// First layer
let hidden = input.dot(&self.weights1) + &self.bias1;
let hidden = hidden.mapv(|x| Self::relu(x));
// Output layer
let output = hidden.dot(&self.weights2) + &self.bias2;
let output = Self::softmax(&output);
output.to_vec()
}
fn relu(x: f64) -> f64 {
if x > 0.0 { x } else { 0.0 }
}
fn softmax(arr: &Array1<f64>) -> Array1<f64> {
let max = arr.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let exp: Array1<f64> = arr.mapv(|x| (x - max).exp());
let sum: f64 = exp.sum();
exp / sum
}
}
// Rust side
use wasm_bindgen::prelude::*;
use rayon::prelude::*;
#[wasm_bindgen]
pub fn parallel_process(data: &mut [f64]) {
data.par_iter_mut().for_each(|x| {
*x = x.sqrt() * 2.0 + x.cos();
});
}
// JavaScript side
import init, { parallel_process } from './pkg/wasm_project.js'
async function processDataParallel() {
await init()
const data = new Float64Array(1000000)
// Fill with data...
console.time('parallel')
parallel_process(data)
console.timeEnd('parallel')
// ~50ms with multi-threading vs ~200ms single-threaded
}
use std::arch::wasm32::*;
#[wasm_bindgen]
pub fn simd_add_arrays(a: &[f32], b: &[f32]) -> Vec<f32> {
let mut result = vec![0.0; a.len()];
unsafe {
let chunks = a.len() / 4;
for i in 0..chunks {
let idx = i * 4;
let va = v128_load(a.as_ptr().add(idx) as *const v128);
let vb = v128_load(b.as_ptr().add(idx) as *const v128);
let vr = f32x4_add(va, vb);
v128_store(result.as_mut_ptr().add(idx) as *mut v128, vr);
}
// Handle remaining elements
for i in (chunks * 4)..a.len() {
result[i] = a[i] + b[i];
}
}
result
}
// Reuse allocations
#[wasm_bindgen]
pub struct Buffer {
data: Vec<u8>,
}
#[wasm_bindgen]
impl Buffer {
#[wasm_bindgen(constructor)]
pub fn new(capacity: usize) -> Buffer {
Buffer {
data: Vec::with_capacity(capacity),
}
}
pub fn process(&mut self, input: &[u8]) -> Vec<u8> {
self.data.clear();
// Reuse existing allocation
self.data.extend_from_slice(input);
// Process...
self.data.clone()
}
}
// ❌ Bad: Multiple calls
pub fn process_pixel(r: u8, g: u8, b: u8) -> (u8, u8, u8) {
// Process single pixel
}
// ✅ Good: Batch processing
pub fn process_image(pixels: &mut [u8]) {
for chunk in pixels.chunks_exact_mut(3) {
// Process all pixels in one call
}
}
// Use smaller types when possible
#[wasm_bindgen]
pub struct CompactData {
// 4 bytes instead of 8
count: u32,
// 2 bytes instead of 4
flag: u16,
// 1 byte
status: u8,
}
// Feature detection and fallback
async function initializeApp() {
if (typeof WebAssembly === 'object') {
try {
const wasm = await import('./wasm/processor.js')
await wasm.default()
return wasm
} catch (error) {
console.warn('WASM failed, falling back to JS', error)
return await import('./js/processor.js')
}
} else {
return await import('./js/processor.js')
}
}
const processor = await initializeApp()
// Use WASM for heavy computation, JS for UI
class ImageEditor {
private wasm: any
async initialize() {
this.wasm = await initWasm()
}
async applyFilter(filter: string) {
// UI in JavaScript
this.showLoading()
// Heavy computation in WASM
const result = await this.wasm.apply_filter(
this.imageData,
filter
)
// UI update in JavaScript
this.displayResult(result)
this.hideLoading()
}
}
# Cargo.toml
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Link-time optimization
codegen-units = 1
panic = "abort"
strip = true # Strip symbols
# Use wasm-opt
wasm-opt -Oz -o output.wasm input.wasm
# Brotli compression
brotli -Z output.wasm
# Typical results:
# Original: 1.2MB
# After wasm-opt: 850KB
# After brotli: 280KB
// Load WASM only when needed
button.addEventListener('click', async () => {
const { processImage } = await import('./wasm/image_processor.js')
const result = await processImage(imageData)
})
# Enable source maps in build
wasm-pack build --dev -- --features console_error_panic_hook
// Add performance markers
#[wasm_bindgen]
pub fn expensive_operation(data: &[u8]) -> Vec<u8> {
web_sys::console::time_with_label("expensive_operation");
// Operation logic
let result = process(data);
web_sys::console::time_end_with_label("expensive_operation");
result
}
// Monitor WASM memory usage
const memory = new WebAssembly.Memory({ initial: 10, maximum: 100 })
setInterval(() => {
const usedBytes = memory.buffer.byteLength
console.log(`WASM Memory: ${(usedBytes / 1024 / 1024).toFixed(2)}MB`)
}, 1000)
Challenge: Browser-based design tool competing with native apps
Solution:
Results:
Challenge: Display global 3D terrain and imagery
Solution:
Results:
Challenge: Complex CAD operations in browser
Solution:
Results:
Component Model:
Garbage Collection:
Interface Types:
WASI (WebAssembly System Interface):
WebAssembly has evolved from an experimental feature to a production-ready platform enabling new classes of web applications. Whether you're building performance-critical features, porting existing codebases, or pushing the boundaries of what's possible on the web, WASM provides the tools and performance needed to succeed.
The key is knowing when to use it: WASM shines for computationally intensive tasks, but JavaScript remains better for DOM manipulation and simple logic. The future lies in hybrid applications that leverage the strengths of both.
At Cortara Labs, we've helped numerous clients achieve breakthrough performance by integrating WebAssembly into their applications. From image processing to real-time simulations, WASM has enabled experiences that were previously impossible in the browser.
Discover how WebAssembly can transform your application's performance. Contact us for a technical consultation, or explore our services to see how we build high-performance web applications.
Follow @cortaralabs for more insights on modern web technologies and high-performance development.