Compile and Instantiate
Loading a WebAssembly module from JavaScript is a two-phase process: compile the bytes into a validated module artifact, then instantiate that module to get a running instance with callable exports. The browser’s WebAssembly global exposes dedicated APIs for both phases — and a convenient shortcut that does both at once.
WebAssembly.compile()
Section titled “WebAssembly.compile()”WebAssembly.compile(bufferSource) takes any BufferSource (a Uint8Array, ArrayBuffer, or similar) and returns a Promise<WebAssembly.Module>. The resulting Module is a compiled, validated artifact — the browser has parsed and optimised the bytes, but nothing has been executed yet.
const response = await fetch('module.wasm');const bytes = await response.arrayBuffer();const module = await WebAssembly.compile(bytes);// `module` can be cached, cloned, or passed to a Worker via postMessageBecause the Module object is serialisable, it can be stored in IndexedDB or sent to another thread with postMessage — you pay the compilation cost once and reuse the result.
WebAssembly.instantiate()
Section titled “WebAssembly.instantiate()”WebAssembly.instantiate() has two overloads:
// Overload 1 — pass raw bytes: compiles AND instantiates in one callconst { module, instance } = await WebAssembly.instantiate(bytes, importObject);
// Overload 2 — pass an already-compiled Module: instantiates onlyconst instanceOnly = await WebAssembly.instantiate(module, importObject);// Note: this overload returns a WebAssembly.Instance directly, not {module, instance}The first overload is the most common starting point. The second is useful when you want to instantiate the same compiled module multiple times — for example, to create isolated sandboxes that share no state.
The instance.exports object
Section titled “The instance.exports object”After instantiation, instance.exports is a plain JavaScript object whose keys are the names declared in the Wasm module’s export section. Every exported function, memory, global, or table appears as a property. Exported functions behave like regular JS functions — you call them, pass JS numbers, and receive JS numbers back.
const { instance } = await WebAssembly.instantiate(bytes, {});const { square, cube } = instance.exports;
console.log(square(5)); // 25console.log(cube(3)); // 27The WAT module that defines those exports looks like this:
(module (func (export "square") (param $n i32) (result i32) local.get $n local.get $n i32.mul) (func (export "cube") (param $n i32) (result i32) local.get $n local.get $n i32.mul local.get $n i32.mul))Run it
Section titled “Run it”The runner below compiles the module, instantiates it, and calls both exports.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
instantiateStreaming | Compiles while it downloads, so an instance is ready as fast as possible | Needs Content-Type: application/wasm from the server, or it rejects immediately with a TypeError |
compile + instantiate as separate steps | Fine-grained control — e.g. cache the Module and instantiate it again without recompiling | Needs the full file buffer in memory before compilation can even start |
Common mistakes
Section titled “Common mistakes”- Always loading with
arrayBuffer()first and then callingWebAssembly.instantiate(), instead of usinginstantiateStreamingto compile while it downloads - Recompiling the module every time a new instance is needed, instead of calling
WebAssembly.compile()once and reusing thatModulewithWebAssembly.instantiate(module, ...) - Forgetting that an
i64export comes back as aBigIntand adding/multiplying it directly with anumberwithout converting first, which throws
💡 Real-world example
Squoosh compiles each Wasm codec once ahead of time, then instantiates it fresh whenever the user picks that codec to compress an image, avoiding repeated compile costs across a session.