Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Manual Wasm instantiation with WASI Overrides

When a Wasm component depends on functionality provided by WASI, the jco transpile produces a WebAssembly module that can be loaded from NodeJS or the Browser that includes usages of unresolved imports like wasi:random/random.

note

Normally, WASI imports that need to be sourced from elsewhere would be mapped, using the --map option to jco transpile.

These instructions are for when mapping is insufficient or implementations must be redirected or changed at instantiation time.

A common usage of transpilation is to map the imports to a known package, like @bytecodealliance/preview2-shim:

jco transpile \
    component.wasm \
    --output dist/transpiled \
    --map wasi:cli/*@0.2.0=@bytecodealliance/preview2-shim/cli#*

Sometimes you may want to use your own implementation of WASI interfaces (whether partial or complete), known/resolved only at instantiation time.

Manual instantiation of a transpiled component with no overrides

To use custom instantiations, in NodeJS we must build with the async instantiation mode:

jco transpile \
    component.wasm \
    --instantiation async \
    --output dist/transpiled

We can instantiate the WebAssembly component for use with no custom overrides (i.e. the default WASI implementations provided by preview2-shim):

import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation";

async function main() {
    const wasmESModule = await import("path/to/transpiled/component.js");
    const loader = async (path) => {
      const buf = await readFile(`./dist/transpiled/${path}`);
      return await WebAssembly.compile(buf.buffer);
    };
    const component = wasmESModule.instantiate(loader, new WASIShim().getImportObject());
    // TODO: add code that utilizes the component's exports
}

await main();

note

When dealing with browser environments, the loader function is not necessary, and null/undefined can be used.

This is identical to mapping all imports to those provided by @bytecodealliance/preview2-shim.

Manual instantiation of a transpiled component with custom overrides

To use custom component overrides in addition to the WASI imports provided by preview2-shim, as before build the component with the async instantiation mode:

jco transpile \
    component.wasm \
    --instsantiation async \
    --output dist/transpiled \
    --map wasi:cli/*@0.2.0=@bytecodealliance/preview2-shim/cli#*

Then write an ES Module like the following:

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

import { random } from "@bytecodealliance/preview2-shim";
import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation";

async function main() {
  /// Load the ES module generated by `jco transpile`
  const wasmESModule = await import("./dist/transpiled/component.js");

  // Build a customized WASI shim by mizing custom implementations
  // and the provided implementation
  const customShim = new WASIShim({
    random: {
      // For these two interfaces we re-use the default provided shim
      random: random.random,
      "insecure-seed": random.insecureSeed,
      // For insecure, we can supply our own custom implementation
      // (in this case, one that is *VERY* insecure)
      insecure: {
        getInsecureRandomBytes: (len) => {
          return new Uint8Array(Number(len)).fill(0);
        },
        getInsecureRandomU64: () => 42n,
      },
    },
  });

  const loader = async (path) => {
    const buf = await readFile(`./dist/transpiled/${path}`);
    return await WebAssembly.compile(buf.buffer);
  };

  // Instantiate the Wasm component's ES module
  const component = await wasmESModule.instantiate(
    loader,
    customShim.getImportObject(),
  );

  // TODO: add code to utilize the component exports
}

await main();

Using WASIShim, you can generate your own custom implementations of WASI, making use of the published shims where necessary.

Versioned imports with WASIShim

You can also use verisons with the import objects produced by WASIShim:

import { WASIShim } from '@bytecodealliance/preview2-shim/instantiation';
import type {
    VersionedWASIImportObject,
    WASIImportObject,
} from '@bytecodealliance/preview2-shim/instantiation';

const shim = new WASIShim();

const unversioned: WASIImportObject = shim.getImportObject();
// console.log('unversioned', unversioned);
unversioned satisfies WASIImportObject;
unversioned satisfies VersionedWASIImportObject<''>;

const versioned: VersionedWASIImportObject<'0.2.3'> = shim.getImportObject({
    asVersion: '0.2.3',
});
//console.log('versioned', versioned);
versioned satisfies VersionedWASIImportObject<'0.2.3'>;