# WebAssembly on the Frontend > Portable bytecode that runs near-native in a sandboxed browser VM. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/webassembly Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## Summary WebAssembly (Wasm) is a binary instruction format that browsers execute in a sandboxed virtual machine at near-native speed. It is not a JavaScript replacement — it has no DOM access and requires JS glue to interop. The payoff is real for compute-heavy tasks: codecs, image processing, cryptography, and ported C++/Rust libraries. ## WebAssembly (Wasm) A binary instruction format that browsers compile to native machine code and execute inside a sandboxed virtual machine. It targets compute-heavy work — codecs, image processing, cryptography, physics simulations, and ported C++/Rust libraries — where JavaScript's dynamic overhead is measurable. Wasm has no DOM access and no ambient capabilities beyond what JS explicitly imports into it. It is a peer to JS, not a replacement. ## When Wasm is worth it on the frontend - Transcoding video or audio in the browser (ffmpeg.wasm, ~30 MB). - Encoding or decoding image formats: WebP, AVIF, JPEG-XL via native C libraries. - Cryptographic operations — SHA-256 or AES over large buffers where JS overhead compounds. - Physics or simulation loops with tight arithmetic that benefits from predictable native speed. - Porting an existing C++/Rust codebase (e.g., SQLite via sql.js, pdfium) to avoid a rewrite. ## Load and call a Wasm module Use `WebAssembly.instantiateStreaming` — it pipes the network response directly into the compiler, saving a parse-then-compile round-trip. The result exposes exported functions as plain JS callables. **Fetch, instantiate, and call a Wasm export** ```ts // math.wasm exports: add(a: i32, b: i32) => i32 async function loadMath() { const { instance } = await WebAssembly.instantiateStreaming( fetch("/math.wasm"), {} // import object — pass JS functions Wasm can call back ); // Wasm exports are plain callable functions const add = instance.exports.add as (a: number, b: number) => number; console.log(add(40, 2)); // 42 } loadMath(); ``` `instantiateStreaming` compiles while streaming — no full-download wait. Exports appear on `instance.exports`; cast them in TypeScript for type safety. ## Watch out: JS↔Wasm boundary cost Each call across the JS/Wasm boundary marshals arguments. Passing numbers is cheap; passing strings or objects requires encoding them into linear memory. If your code calls Wasm thousands of times per frame, batch the work — pass a buffer of inputs and get a buffer of outputs back in one call, not one call per item. ## Wasm vs. plain JavaScript **Pros** - Near-native throughput for tight compute loops — no dynamic type checks mid-loop. - Deterministic performance: no GC pauses affecting the hot path. - Ports existing C++/Rust libraries without a full rewrite. - Sandboxed: no file system or network access beyond explicit JS imports. **Cons** - .wasm bundles must download and compile before first use — adds startup cost. - No direct DOM or Web API access; every UI mutation crosses the JS boundary. - Debugging tools are less mature than JS DevTools; source maps help but aren't universal. - Linear memory management is manual in C/Rust; memory leaks don't surface as JS errors. - Toolchain complexity: Emscripten or wasm-pack add build steps most JS teams don't know. ## Key terms - **Wasm module**: A compiled .wasm binary: a structured binary encoding of a validated module that browsers parse and compile. - **JS↔Wasm boundary**: The call site where JS invokes a Wasm export or vice versa; each crossing incurs marshalling overhead. - **linear memory**: A flat, resizable ArrayBuffer Wasm uses as its heap; JS can read and write it directly. - **Emscripten**: Toolchain that compiles C/C++ to Wasm, generating the JS glue code needed to drive the module. - **wasm-bindgen**: Rust tool that generates JS bindings for Wasm modules compiled from Rust via wasm-pack. ## Related topics - [Web Workers & Off-Main-Thread](https://fearchitect.com/topics/web-workers-off-main-thread.md): Offload CPU-heavy work to a background thread, keeping the main thread free. - [Event Loop & scheduler.yield()](https://fearchitect.com/topics/event-loop-scheduler-yield.md): Break long tasks to keep the main thread responsive and hit INP. - [Bundle Architecture & Code Splitting](https://fearchitect.com/topics/bundle-architecture-code-splitting.md): Ship only the JS a route needs, cache the rest long-term. - [Render Performance](https://fearchitect.com/topics/render-performance-patterns.md): Skip renders, defer slow work, and virtualize long lists. - [Paint & Composite Optimization](https://fearchitect.com/topics/paint-composite-optimization.md): Animate only transform and opacity to skip paint entirely. - [The Rendering Pipeline](https://fearchitect.com/topics/rendering-pipeline.md): Style → Layout → Paint → Composite: what triggers each stage. ## Further reading - [MDN — WebAssembly JavaScript API](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface) - [WebAssembly specification](https://webassembly.github.io/spec/core/) - [Emscripten documentation](https://emscripten.org/docs/) - [wasm-bindgen — The wasm-bindgen Guide](https://rustwasm.github.io/docs/wasm-bindgen/)