With the
ES Module Integration proposal
not implemented everywhere yet, here's a comparison of how we can load
.wasm
modules in different runtimes and compilers. I'm using a wasm file that
exports a single function which returns an integer.
(module (func $result (result i32) i32.const 42) (export "result" (func $result)) )
In the browser we can fetch (or use XMLHttpRequest) the wasm file contents and instantiate.
fetch('result.wasm') .then(response => response.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes)) .then(({instance}) => console.log(instance.exports.result()))
Before your code reaches the browser it might go through a bundler, most of which will try to bundle the wasm file next to the output js file.
Importing wasm modules is not enabled by default and needs an experimental flag (asyncWebAssembly) to be set. With the flag enabled you can directly and dynamically import wasm modules according to the proposal.
import {result} from './result.wasm' console.log(result()) import('./result.wasm') .then(({result}) => console.log(result()))
In Vite.js and Rollup, using @rollup/plugin-wasm, wasm modules have a default export which is an initialization function.
import init from './result.wasm' init() .then(({result}) => console.log(result()))
Esbuild does not have a default wasm loader, but contains an example plugin in its documentation. Using the plugin allows you to load wasm the same way as the Rollup example above (using a function as default export).
In Node.js, and serverless runtimes based on it (Netlify functions, AWS
Lambda, …), modules can be loaded either by reading the file or using
the experimental flag
--experimental-wasm-modules
(v12.3+) when running node.
WebAssembly.compile(require('fs').readFileSync('./result.wasm')) .then(WebAssembly.instantiate) .then(({exports}) => console.log(exports.result())) // With --experimental-wasm-modules import {result} from './result.wasm' console.log(result())
As explained in their
blog post
on modules, with the correct settings (see the wrangler config at the end of
the page) the default export of .wasm
imports will be a
WebAssembly.Module
which you can instantiate. For now it seems the .mjs
extension is
required for your JavaScript modules. Dynamically loading a WebAssembly module
results in an error.
import module from './result.wasm' const instance = await WebAssembly.instantiate(module) console.log(instance.exports.result()) // Will error at runtime import('./result.wasm').catch(console.error)
Vercel currently has two flavors of serverless JavaScript: serverless functions, which run Node.js via AWS Lambda, and edge functions. In Next.js your api routes are run as serverless functions, so you have access to the full Node.js api, while edge functions are used for middleware and rendering React server components. Setting up Next.js to use WebAssembly requires a change to their Webpack config.
The support for WebAssembly in Next.js 12 is unfortunately pretty bad. I was unable to compile any api route containing WebAssembly imports, the compiler simply hangs. Importing WebAssembly modules in React server components results in cryptic errors:
error - Error: Your page must export a `default` component error - unhandledRejection: TypeError: Only absolute URLs are supported error - unhandledRejection: TypeError: Only absolute URLs are supported
With Next.js 11 I'm able to import the WebAssembly exports directly. There's no default export. Dynamically loading WebAssembly modules is supported. I'm not able to test the Edge runtime because the features using it require Next.js 12.
import {result} from './result.wasm' console.log(result()) import('./result.wasm') .then(({result}) => console.log(result()))
If, like me, you're trying to ship a Javascript package with some WebAssembly that you'd like to be able to run almost anywhere the safest bet for now is embedding the wasm file in a base64 encoded string. This makes you skip the loading phase completely in exchange for a more bloated filesize.
const wasm = 'AGFzbQEAAAABBQFgAAF/AwIBAAcKAQZyZXN1bHQAAAoGAQQAQSoLABUEbmFtZQEJAQAGcmVzdWx0AgMBAAA=' // Use a package like base64-arraybuffer here instead of atob to support all runtimes const bytes = Uint8Array.from(atob(wasm), c => c.charCodeAt(0)) const module = new WebAssembly.Module(bytes) WebAssembly.instantiate(module) .then(({exports}) => console.log(exports.result()))