Run WebAssembly in Node.js and Pass Data
WebAssembly (Wasm) allows developers to run high-performance, compiled code inside a Node.js environment alongside JavaScript. This article provides a practical, step-by-step guide on how to load and execute Wasm modules in Node.js, and explains how to successfully pass both simple data types, like numbers, and complex data structures, like arrays and strings, between JavaScript and compiled code.
1. Loading and Executing Wasm Modules in Node.js
Node.js has native support for WebAssembly via the global
WebAssembly object. To run a Wasm module, you must first
read the .wasm file into a buffer and then compile and
instantiate it.
Here is the standard pattern for loading a Wasm file:
const fs = require('fs');
const path = require('path');
async function loadWasm(wasmFilePath, importObject = {}) {
const wasmBuffer = fs.readFileSync(path.resolve(__dirname, wasmFilePath));
const { instance } = await WebAssembly.instantiate(wasmBuffer, importObject);
return instance.exports;
}2. Passing Simple Data (Numbers)
WebAssembly natively supports only numeric types (integers and
floats). Passing numbers between JavaScript and Wasm is seamless because
the WebAssembly.instantiate API automatically maps JS
numbers to Wasm’s numeric types.
The JavaScript Implementation:
async function run() {
// Load a module that exports an 'add' function
const exports = await loadWasm('math.wasm');
// Pass numbers directly
const sum = exports.add(5, 10);
console.log(`Result from Wasm: ${sum}`); // Outputs: 15
}
run();3. Passing Complex Data (Arrays and Strings)
Because WebAssembly cannot directly access JavaScript’s heap memory, you cannot pass objects, arrays, or strings directly as arguments. Instead, you must write this data into the shared WebAssembly Memory buffer and pass the memory offset (pointer) and size to the Wasm function.
Step 1: Writing an Array to Wasm Memory
To pass an array of numbers to Wasm, you need to write it into the
typed array backed by
wasmInstance.exports.memory.buffer.
async function passArrayToWasm() {
const exports = await loadWasm('analytics.wasm');
// Access the exported WebAssembly memory
const memory = exports.memory;
// Define the JavaScript array to pass
const jsArray = [10, 20, 30, 40, 50];
const elementCount = jsArray.length;
// Determine where in the Wasm memory to store the data
// (In a production environment, use a Wasm allocator like malloc)
const pointer = exports.get_shared_buffer_pointer();
// Create a view matching the data type (e.g., 32-bit integers)
const wasmMemoryView = new Int32Array(memory.buffer, pointer, elementCount);
// Copy the JS array data into the Wasm memory view
wasmMemoryView.set(jsArray);
// Execute the Wasm function by passing the pointer and the length
const average = exports.calculate_average(pointer, elementCount);
console.log(`Average calculated by Wasm: ${average}`);
}Step 2: Reading Strings from Wasm Memory
To retrieve a string generated by a Wasm module, the module must return a memory pointer and a length. JavaScript can then decode that slice of memory back into a JS string.
async function readStringFromWasm() {
const exports = await loadWasm('greetings.wasm');
// Call the Wasm function to generate a string
const pointer = exports.greet_user();
const length = exports.get_string_length();
// Create a Uint8Array view of the string bytes in memory
const memory = exports.memory;
const stringBuffer = new Uint8Array(memory.buffer, pointer, length);
// Decode the bytes into a JavaScript string
const decoder = new TextDecoder('utf-8');
const greeting = decoder.decode(stringBuffer);
console.log(greeting); // Outputs: "Hello from WebAssembly!"
}Summary of Data Flow Rules
- Primes and Floats: Pass directly as arguments and receive directly as return values.
- Arrays and Buffers: Allocate space in the Wasm memory, copy the bytes using a TypedArray view in JavaScript, and pass the starting memory address (pointer) and byte length to the Wasm function.
- Strings: Encode to UTF-8 bytes to write to memory,
or decode from UTF-8 bytes using
TextDecoderwhen reading from memory.