unwasm

repository·main·Indexed 42 days ago

https://github.com/unjs/unwasm

WebAssembly tools for JavaScript, featuring a build tool plugin for Rollup and Vite that integrates WASM modules into JavaScript pipelines. It provides automated transformation into ESM or standard module bindings, supports static and lazy bindings, and includes a dependency-free parseWasm tool for inspecting WebAssembly binary interfaces (imports and exports) without decoding the entire module. Version 0.6.0.

Tokens
2.9K
Snippets
15
Records
18
Agent score
43%

What's inside unwasm

  1. Configure auto-imports via package.json

    main

    unwasm can automatically resolve a .wasm module's imports using import maps. You can define these mappings in a parent package.json using the imports field.

    For example, if a .wasm file requires an env module, you can map it like this:

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

    You can also use the # prefix (e.g., #env) to follow Node.js import map conventions.

    {
      "exports": {
        "./rand.wasm": "./rand.wasm"
      },
      "imports": {
        "env": "./env.mjs"
      }
    }
  2. How unwasm bindings work

    main

    unwasm allows you to import .wasm files as if they were standard ES modules. It parses the module at build time to discover imports and exports and generates bindings tailored to your environment.

    There are two main types of bindings:

    1. Static bindings: Used when the environment supports top-level await and the module requires no imports (or unwasm can resolve them). The .wasm file behaves like a regular ESM import.
    2. Lazy bindings: Used when top-level await is unavailable, the module requires an imports object, or you explicitly set the lazy plugin option. unwasm exports a Proxy that you call as a function to instantiate the module with your own imports.

    If you use static import syntax, named exports are proxied until the module is initialized. Calling a named export before initialization triggers an automatic initialization without imports, returning a Promise that resolves with the result.

    // Static import
    import { sum } from "sum.wasm";
    
    // Dynamic import
    const { sum } = await import("sum.wasm");
    
    // Dynamic import with an imports object
    const { rand } = await import("rand.wasm").then((r) =>
      r.default({
        env: {
          seed: () => () => Math.random() * Date.now(),
        },
      }),
    );
    
    // Static import with an imports object
    import initRand, { rand } from "rand.wasm";
    
    await initRand({
      env: {
        seed: () => () => Math.random() * Date.now(),
      },
    });
  3. Configure the Rollup plugin for unwasm

    main

    To use unwasm with Rollup, install the package and add the unwasm plugin to your rollup.config.js.

    // rollup.config.js
    import { unwasm } from "unwasm/plugin";
    
    export default {
      plugins: [unwasm({/* options */})],
    };
    import { unwasm } from "unwasm/plugin";
    
    export default {
      plugins: [unwasm({/* options */})],
    };
  4. Import WASM modules as ESM or Modules

    main

    The plugin handles .wasm imports by transforming them into JavaScript code. You can control the behavior using query parameters or plugin options:

    1. Module Mode: By default, the plugin attempts to parse the WASM module to determine its imports and exports. If parsing fails, it falls back to a generic module mode.
    2. Explicit Module Mode: Appending ?module to the import path (e.g., import wasm from './file.wasm?module') forces the plugin to treat the file as a module.
    3. ESM Bindings: If esmImport: true is set in the plugin configuration, the plugin generates ESM bindings that reference the WASM files as external assets.
  5. Configure unwasm plugin options

    main

    The unwasm plugin accepts the following configuration options:

    • esmImport (default: false): If true, the .wasm file is imported directly instead of being bundled. This is required for environments like Cloudflare Workers that support native .wasm module imports.
    • lazy (default: false): If true, .wasm files are imported through a lazily evaluated proxy. This is useful for runtimes that do not support top-level await.
  6. Import a WebAssembly Module directly using ?module

    main

    If a library requires a WebAssembly.Module instance instead of an instantiated object, append the ?module suffix to your import. This allows you to manually instantiate the module using WebAssembly.instantiate.

    import _sumMod from "sum.wasm?module";
    const { sum } = await WebAssembly.instantiate(_sumMod).then((i) => i.exports);
    import _sumMod from "sum.wasm?module";
    const { sum } = await WebAssembly.instantiate(_sumMod).then((i) => i.exports);
  7. Use parseWasm to inspect WebAssembly modules

    main

    The parseWasm tool is a dependency-free helper that parses a WebAssembly binary to extract its interface (imports and exports) without decoding the entire module. This is useful for build-time analysis.

    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));
    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));
  8. Configure UnwasmPluginOptions

    main

    When using the Unwasm plugin, you can provide an options object to control how WebAssembly assets are handled.

    Key options include:

    • esmImport: If set to true, the plugin will directly import .wasm files instead of bundling them as base64 strings. Defaults to false.
    • lazy: If set to true, the plugin avoids using top-level await and instead uses a proxy. This is useful for compatibility with environments that do not support top-level await. Defaults to false.
    • silent: If set to true, all warnings from the plugin are suppressed. Defaults to false.
    const options: UnwasmPluginOptions = {
      esmImport: true,
      lazy: true,
      silent: false
    };
  9. Configure build entries for unwasm

    main

    The unwasm build configuration uses defineBuildConfig to specify the entry points for the project. Currently, the build targets two main entry points: src/plugin/index and src/tools/index.

    import { defineBuildConfig } from "obuild/config";
    
    export default defineBuildConfig({
      entries: ["src/plugin/index", "src/tools/index"],
    });
  10. parseWasm return value schema

    main

    The parseWasm function returns an object describing the module's interface. The structure is as follows:

    {
      "modules": [
        {
          "exports": [
            {
              "id": number,
              "name": string,
              "type": "Func" | "Memory" | ...
            }
          ],
          "imports": [
            {
              "module": string,
              "name": string,
              "params": array,
              "returnType": string
            }
          ]
        }
      ]
    }
  11. Initialize the unwasm plugin

    main

    The unwasm() function is the primary entry point for integrating WebAssembly modules into your build pipeline (e.g., Rollup or Vite). It accepts an options object of type UnwasmPluginOptions to configure how WASM files are transformed and bundled.

    Key behaviors:

    • Module Detection: It automatically detects .wasm files and transforms them into usable JavaScript bindings.
    • Binding Modes: It supports both ESM (ECMAScript Module) and standard module bindings.
    • Asset Emission: If esmImport is enabled, the plugin will emit the raw WASM files as assets in the bundle.
    • Error Handling: If parsing fails, the plugin can fall back to module mode or remain silent if silent: true is configured.
    import { unwasm } from 'unwasm';
    
    export default {
      // ... other plugin config
      plugins: [
        unwasm({
          esmImport: true,
          // other UnwasmPluginOptions
        })
      ]
    };