Load and Instantiate WebAssembly Modules in JavaScript

WebAssembly (Wasm) allows developers to run high-performance code in the browser alongside JavaScript. To use a WebAssembly module, you must first fetch its binary file, compile it, and instantiate it so JavaScript can access its exported functions. This article provides a clear, step-by-step guide on how to load and instantiate WebAssembly modules using modern JavaScript APIs, focusing on both the efficient streaming method and the fallback buffer-based method.

The Modern Approach: Streaming Instantiation

The most efficient way to load and instantiate a WebAssembly module is by using the WebAssembly.instantiateStreaming() API. This method compiles and instantiates the module on the fly as the .wasm file is being downloaded over the network, which significantly reduces startup time.

Syntax and Example

To use streaming instantiation, you pass a fetched response promise directly to WebAssembly.instantiateStreaming(), along with an optional import object.

// Define imports (functions, memory, etc.) that the Wasm module needs from JavaScript
const importObject = {
  env: {
    log: (arg) => console.log(arg)
  }
};

// Fetch, compile, and instantiate the Wasm file in a single streaming operation
WebAssembly.instantiateStreaming(fetch('module.wasm'), importObject)
  .then(obj => {
    // Access the exported functions from the Wasm instance
    const result = obj.instance.exports.add(5, 10);
    console.log(result); // Outputs the result of the Wasm function
  })
  .catch(error => {
    console.error("Failed to load and instantiate Wasm:", error);
  });

Using async/await, the same process looks like this:

async function loadWasm() {
  try {
    const importObject = {};
    const obj = await WebAssembly.instantiateStreaming(fetch('module.wasm'), importObject);
    
    // Call an exported function
    obj.instance.exports.myFunction();
  } catch (error) {
    console.error("Error loading WebAssembly:", error);
  }
}

loadWasm();

Note: For instantiateStreaming() to work, the server serving the .wasm file must return the correct MIME type: application/wasm.


The Fallback Approach: ArrayBuffer Instantiation

If the server does not support the application/wasm MIME type, or if you are running the code in an environment where instantiateStreaming is not available, you must fallback to loading the file as an ArrayBuffer and then compiling it.

This process involves downloading the entire file first, converting it into a typed array of bytes, and then instantiating those bytes.

Syntax and Example

async function loadWasmFallback() {
  try {
    const response = await fetch('module.wasm');
    const bytes = await response.arrayBuffer();
    
    // Instantiate using the byte array
    const obj = await WebAssembly.instantiate(bytes);
    
    // Access the exported functions
    obj.instance.exports.myFunction();
  } catch (error) {
    console.error("Fallback WebAssembly instantiation failed:", error);
  }
}

loadWasmFallback();

Understanding the Components

Whether you use streaming or fallback compilation, the instantiation process involves the same key components:

  1. The importObject (Optional): This is a JavaScript object containing values, functions, or memory spaces that you want to pass into your WebAssembly module. Wasm modules can call these JavaScript functions during execution.
  2. The Result Object: The resolved promise returns a JavaScript object containing two properties:
    • module: A WebAssembly.Module object that can be cached or shared with web workers.
    • instance: A WebAssembly.Instance object that contains all the exported WebAssembly functions inside instance.exports.