Skip to content

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(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 postMessage

Because 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() has two overloads:

// Overload 1 — pass raw bytes: compiles AND instantiates in one call
const { module, instance } = await WebAssembly.instantiate(bytes, importObject);
// Overload 2 — pass an already-compiled Module: instantiates only
const 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.

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)); // 25
console.log(cube(3)); // 27

The 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))

The runner below compiles the module, instantiates it, and calls both exports.

WebAssembly
OptionBenefitCost
instantiateStreamingCompiles while it downloads, so an instance is ready as fast as possibleNeeds Content-Type: application/wasm from the server, or it rejects immediately with a TypeError
compile + instantiate as separate stepsFine-grained control — e.g. cache the Module and instantiate it again without recompilingNeeds the full file buffer in memory before compilation can even start
  • Always loading with arrayBuffer() first and then calling WebAssembly.instantiate(), instead of using instantiateStreaming to compile while it downloads
  • Recompiling the module every time a new instance is needed, instead of calling WebAssembly.compile() once and reusing that Module with WebAssembly.instantiate(module, ...)
  • Forgetting that an i64 export comes back as a BigInt and adding/multiplying it directly with a number without 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.

What does WebAssembly.instantiate(bytes, {}) return?
Which property of the instantiation result holds callable exported functions?
Can a compiled WebAssembly.Module be re-instantiated multiple times?