惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
量子位
V
Visual Studio Blog
博客园 - Franky
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
罗磊的独立博客
小众软件
小众软件
V
V2EX
GbyAI
GbyAI
B
Blog RSS Feed
博客园 - 三生石上(FineUI控件)
大猫的无限游戏
大猫的无限游戏
有赞技术团队
有赞技术团队
月光博客
月光博客
Recent Announcements
Recent Announcements
雷峰网
雷峰网
F
Fortinet All Blogs
M
MIT News - Artificial intelligence

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
I Compiled Rust to WebAssembly and Made My JavaScript 6 F...
Devanshu Bis · 2026-05-25 · via DEV Community

Click the Gaussian blur button on wasm-from-zero.vercel.app.

WASM: 38 ms. JS: 182 ms. Speedup: 4.8×.

That gap is the whole point of WebAssembly. Same algorithm. Same image. Same browser. But one version ran a hot inner loop compiled from Rust through wasm-bindgen to a 12 KB binary, and the other version ran the same loop as JavaScript. The browser optimised both. Wasm won by almost an order of magnitude.

This is the trick that powers Figma, Photoshop on the Web, Google Earth, AutoCAD Web, and a lot of the "wait, that runs in a browser tab?" demos you've seen the last three years. It used to take a six-month port of a C++ engine to ship. Today it takes a Cargo.toml, one wasm-pack build, and an import statement.

The mental model

WebAssembly is a binary format that runs in every modern browser. It's not a programming language — it's an assembly-like target you compile to, the same way you compile to x86_64 or ARM. The languages that target wasm today: Rust, C, C++, Go, Zig, AssemblyScript, Swift, Kotlin, Dart, .NET. Anything with an LLVM backend, basically.

The browser doesn't run your wasm under an interpreter. It JIT-compiles it to actual native machine code, then runs it at near-native speed. The wasm engine in V8 (and SpiderMonkey, JavaScriptCore) is the same engine that runs your JavaScript — it just gets to skip the type-inference + bailout dance JS needs, because wasm is already statically typed.

For pure compute hot loops, that means: no boxed numbers, no hidden classes, no deopts, no GC pauses. Just tight code that does the work the source said to do.

Step 1: write the kernel in Rust

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn grayscale(pixels: &mut [u8]) {
    for chunk in pixels.chunks_exact_mut(4) {
        let r = chunk[0] as f32;
        let g = chunk[1] as f32;
        let b = chunk[2] as f32;
        let luma = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
        chunk[0] = luma;
        chunk[1] = luma;
        chunk[2] = luma;
    }
}

Enter fullscreen mode Exit fullscreen mode

The #[wasm_bindgen] attribute is the magic. It generates the glue code that lets JavaScript pass a Uint8Array directly into Rust's &mut [u8] — and Rust to write back to the same bytes. No serialisation, no JSON round-trip, no copy across the language boundary.

Why does that matter? Because the canvas pixel buffer is a Uint8ClampedArray. The wasm function gets to point at the canvas's actual memory and rewrite it in place. Zero overhead between the JS world and the Rust world.

Step 2: compile to wasm

docker run --rm -v "$PWD/rust:/work" -w /work rust:1-slim \
    bash -c 'cargo install wasm-pack --locked && wasm-pack build --target web'

Enter fullscreen mode Exit fullscreen mode

The output is a pkg/ folder with three files that matter:

  • wasm_from_zero_bg.wasm — the actual binary, ~10 KB for the four filters in this demo
  • wasm_from_zero.js — wasm-bindgen-generated ES module wrapper
  • wasm_from_zero.d.ts — TypeScript definitions, autogenerated from the Rust signatures

You import it like any other module. No bundler plugin needed (Vite handles the .wasm URL resolution natively):

import init, { grayscale, invert, sepia, blur } from './wasm/wasm_from_zero'

await init()           // one-time: fetch + instantiate the .wasm
grayscale(pixels)      // direct function call from here on

Enter fullscreen mode Exit fullscreen mode

That await init() is the only async work. After it resolves, every filter call is a synchronous function invocation with zero per-call overhead.

Step 3: feed it the canvas

const ctx = canvas.getContext('2d')!
const data = ctx.getImageData(0, 0, w, h)

// Canvas hands us Uint8ClampedArray; wasm-bindgen's &mut [u8] wants
// Uint8Array. Same memory, different TS type — wrap a view, zero copy.
const pixels = new Uint8Array(data.data.buffer)

grayscale(pixels)              // wasm mutates in place

ctx.putImageData(data, 0, 0)   // paint result

Enter fullscreen mode Exit fullscreen mode

That's the whole bridge. Read the pixels off the canvas, hand them to wasm, write the result back. The wasm linear memory and the canvas buffer are the same bytes — the function call is just "go process this 4 MB pointer."

Step 4: race it against JavaScript

The killer demo is to run the exact same algorithm in plain JS and time both. I ported every kernel from Rust to JS byte-for-byte:

export function grayscaleJs(pixels: Uint8ClampedArray) {
  for (let i = 0; i < pixels.length; i += 4) {
    const luma = 0.299 * pixels[i] + 0.587 * pixels[i+1] + 0.114 * pixels[i+2]
    pixels[i] = luma
    pixels[i+1] = luma
    pixels[i+2] = luma
  }
}

Enter fullscreen mode Exit fullscreen mode

Same 0.299, 0.587, 0.114. Same loop shape. Same memory access pattern. The JS engine has every chance to optimise this — and it does, decently. But it can't escape the boxing of pixels[i] * 0.299 (the JS spec says that's a float64 multiplication, even though the result will be stored as a Uint8Clamped). Wasm just multiplies u8 * f32 directly.

On my laptop:

Filter WASM JS Speedup
invert 6 ms 9 ms 1.5×
grayscale 7 ms 14 ms
sepia 9 ms 22 ms 2-3×
blur (3×3) 38 ms 182 ms 4.8×

The pattern: the bigger the per-pixel arithmetic, the wider wasm's lead. invert is 255 - v × 3 channels, so cheap that JS almost catches up. The 3×3 Gaussian blur reads 9 neighbour pixels × 3 channels per output pixel — 27 million multiply-adds on a 1 MP image. That's exactly the shape where wasm pulls ahead.

What this changes

Five years ago, you wrote your image filters in JS and either accepted the slowness or stood up a Python service. Today, you stick the hot loop in a Rust crate, run one build, and ship a 10 KB binary alongside your bundle.

The interesting thing isn't the speedup — it's the distribution model. You're not asking the user to install anything. You're not running a server. The binary just rides along with your JS bundle. The browser caches it. It runs sandboxed by the same security model as JS. It works on every modern browser including mobile.

If you've been holding off on "real" graphics, audio, video, ML, crypto, parsing work in the browser because "it'd be too slow in JS" — that excuse is gone.

The code for this demo is on GitHub, with eight step-by-step commits you can follow. The live version is at wasm-from-zero.vercel.app. Open it. Click Gaussian blur. Watch the speedup card light up.

That's WebAssembly.


🔗 Code: github.com/dev48v/wasm-from-zero
🌐 Live demo: wasm-from-zero.vercel.app
📚 Series: TechFromZero — a new technology every day, all free, all open source.