Skip to content

Repository files navigation

unwasm

npm version npm downloads codecov

Universal WebAssembly tools for JavaScript.

unwasm lets you import a .wasm file the same way you import any other module. It reads the module at build time, works out what it imports and exports, and generates the right bindings for your bundler.

import { sum } from "sum.wasm";

Goal

unwasm aims to be a common, future-proof way to support WebAssembly modules across JavaScript runtimes, frameworks, and build tools. It follows the WebAssembly Community Group's ES Module Integration proposal as closely as possible, while staying compatible with today's ecosystem libraries.

Bindings API

When you import a .wasm module, unwasm resolves, reads, and parses it during the build to discover its imports and exports. It also tries to resolve imports automatically and emits bindings tailored to your bundler.

The shape of those bindings depends on your target environment:

  • Static bindings โ€” if the environment supports top-level await and the module needs no imports object (or unwasm can resolve one for you), the .wasm file behaves like a regular ESM import.
  • Lazy bindings โ€” if top-level await is unavailable, the module needs an imports object, or you set the lazy plugin option, unwasm exports a Proxy that you call as a function to instantiate the module with your own imports. The syntax stays close to ESM, and initialization happens only when you need it.

Example: Using static import

import { sum } from "sum.wasm";

Example: Using dynamic import

const { sum } = await import("sum.wasm");

If your module requires an imports object (which unwasm can often infer for you), the syntax changes slightly: you initialize the module with that object first.

Example: Using dynamic import with imports object

const { rand } = await import("rand.wasm").then((r) =>
  r.default({
    env: {
      seed: () => () => Math.random() * Date.now(),
    },
  }),
);

Example: Using static import with imports object

import initRand, { rand } from "rand.wasm";

await initRand({
  env: {
    seed: () => () => Math.random() * Date.now(),
  },
});

Note

With static import syntax, named exports are proxied until the module is initialized. If you call one of them before init, the proxy runs init without imports and returns a Promise that resolves with the call's result.

Module compatibility

Some libraries want a WebAssembly.Module so they can create the WebAssembly.Instance themselves. For those cases, add the ?module suffix to import a .wasm file as a Module directly.

import _sumMod from "sum.wasm?module";
const { sum } = await WebAssembly.instantiate(_sumMod).then((i) => i.exports);

Note

Run into a library that needs this? Open an issue โ€” we would love to help it migrate!

Integration

unwasm transforms .wasm imports into compatible bindings at build time. Today that happens through a Rollup plugin; more integrations are planned.

Install

Install the unwasm npm package:

# โœจ Auto-detect package manager
npx nypm install unwasm

Builder Plugins

Rollup
// rollup.config.js
import { unwasm } from "unwasm/plugin";

export default {
  plugins: [unwasm({/* options */})],
};

Plugin Options

  • esmImport (default: false): Import the .wasm file directly instead of bundling it. Required on Cloudflare Workers, and works in any environment with native .wasm module imports.
  • lazy (default: false): Import .wasm files through a lazily evaluated proxy, for runtimes without top-level await.

Tools

unwasm ships build-time helpers for working with .wasm modules directly.

parseWasm

Parses the wasm binary format to extract a module's imports and exports. It is a small, dependency-free reader that decodes only the sections describing the module interface and skips the rest.

import { readFile } from "node:fs/promises";
import { parseWasm } from "unwasm/tools";

const source = await readFile(new URL("examples/sum.wasm", import.meta.url));
const parsed = parseWasm(source);
console.log(JSON.stringify(parsed, undefined, 2));

Example parsed result:

{
  "modules": [
    {
      "exports": [
        {
          "id": 5,
          "name": "rand",
          "type": "Func"
        },
        {
          "id": 0,
          "name": "memory",
          "type": "Memory"
        }
      ],
      "imports": [
        {
          "module": "env",
          "name": "seed",
          "params": [],
          "returnType": "f64"
        }
      ]
    }
  ]
}

Auto Imports

unwasm can infer the imports object for you and bundle it using import maps (read more: MDN, Node.js, and WICG).

To tell the bundler how to resolve the imports a .wasm file needs, declare them in a parent package.json:

{
  "exports": {
    "./rand.wasm": "./rand.wasm"
  },
  "imports": {
    "env": "./env.mjs"
  }
}

Tip

You can prefix import names with # (for example #env) to follow Node.js conventions.

Wasm ESM Imports

Wasm modules can also import from ES modules (read more: ESM Integration Spec).

Example:

(module
  (import "./add-esmi-deps.mjs" "getValue" (func $getValue (result i32)))

  (func (export "addImported") (param $a i32) (result i32)
    local.get $a
    call $getValue
    i32.add
  )
)

Contribution

Local development
  • Clone this repository
  • Install the latest LTS version of Node.js
  • Enable Corepack using corepack enable
  • Install dependencies using pnpm install
  • Run tests using pnpm dev or pnpm test

License

Published under the MIT license. Made by @pi0 and community ๐Ÿ’›


๐Ÿค– auto updated with automd

About

๐Ÿ‡ผ WebAssembly tools for JavaScript

Resources

Stars

Watchers

Forks

Releases

Used by

Contributors

Languages