Import JavaScript Functions into WebAssembly

WebAssembly (Wasm) modules are designed to run alongside JavaScript, and you can indeed import JavaScript functions directly into a Wasm module. This article explains how this integration works, the step-by-step process of passing JavaScript functions during module instantiation, and how to call them from within your WebAssembly code.

How the Import Mechanism Works

WebAssembly cannot access the host environment (like the browser DOM or Node.js APIs) directly. Instead, it relies on the host to provide these capabilities. You can import JavaScript functions into a WebAssembly module by passing an importObject during the instantiation of the Wasm module.

When you instantiate a Wasm module using WebAssembly.instantiate() or WebAssembly.instantiateStreaming(), you provide this importObject as the second argument. The Wasm module can then call these imported functions just like its own internal functions.

Step-by-Step Implementation

To import a JavaScript function into WebAssembly, you must define the function in JavaScript, declare it in your WebAssembly code, and map them together during instantiation.

1. Declare the Import in WebAssembly

In your WebAssembly code (written in WebAssembly Text Format, or WAT), you declare the external function using the import keyword. You must specify the namespace, the function name, and the function’s signature (parameters and return types).

(module
  ;; Import 'js_log_number' from the 'imports' namespace
  (import "imports" "js_log_number" (func $log_num (param i32)))
  
  (func (export "call_js")
    ;; Call the imported JavaScript function with the argument 42
    i32.const 42
    call $log_num
  )
)

2. Define the Function in JavaScript

In your JavaScript file, create the function you want WebAssembly to execute. Structure this function inside an object that matches the namespaces declared in your Wasm code.

// Define the JavaScript function
function logNumberFromWasm(number) {
  console.log("Value from WebAssembly:", number);
}

// Create the import object matching the Wasm declaration
const importObject = {
  imports: {
    js_log_number: logNumberFromWasm
  }
};

3. Instantiate the Module with the Imports

Pass the importObject to the WebAssembly instantiation function. Once loaded, you can run the exported Wasm function, which in turn executes the imported JavaScript function.

WebAssembly.instantiateStreaming(fetch('module.wasm'), importObject)
  .then(obj => {
    // Call the exported Wasm function, which triggers the JS function
    obj.instance.exports.call_js();
  });

Data Type Limitations

When importing JavaScript functions into WebAssembly, you must adhere to WebAssembly’s strict type system. WebAssembly only natively understands numeric types: * Integers (i32, i64) * Floating-point numbers (f32, f64)

If you need to pass complex data types like strings, arrays, or objects from WebAssembly to an imported JavaScript function, you cannot pass them directly. Instead, you must write the data to the WebAssembly module’s shared memory (WebAssembly.Memory) and pass the memory offset (pointer) and length as integers to the JavaScript function. JavaScript can then read the data directly from the shared memory buffer.