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

Preview 3 shim

@bytecodealliance/preview3-shim implements WASI 0.3 (wasi:cli, wasi:clocks, wasi:filesystem, wasi:http, wasi:random and wasi:sockets) for Node.js and browsers. WASI 0.3 replaces the wasi:io streams and pollables of 0.2 with stream<T> and future<T> values built into the component model, so there is no io subsystem to map.

npm install @bytecodealliance/preview3-shim

jco transpile maps every supported 0.3.x version of a WASI interface to the current preview3-shim implementation. A component built against an earlier 0.3 release therefore runs against the newest implementation, which is acceptable because 0.3 releases are not expected to introduce breaking changes.

Every subpath resolves to the Node.js build under the node export condition and to the browser build otherwise, unless noted:

SubpathContents
@bytecodealliance/preview3-shimEvery subsystem namespace below, re-exported
@bytecodealliance/preview3-shim/cliwasi:cli environment, exit, run, stdio, and terminal interfaces
@bytecodealliance/preview3-shim/clockswasi:clocks wall and monotonic clocks
@bytecodealliance/preview3-shim/filesystemwasi:filesystem types and preopens, plus browser adapters
@bytecodealliance/preview3-shim/httpwasi:http types, client, and handler
@bytecodealliance/preview3-shim/randomwasi:random secure and insecure randomness
@bytecodealliance/preview3-shim/socketswasi:sockets TCP, UDP, and name lookup
@bytecodealliance/preview3-shim/streamNode.js only: stream(), StreamReader, and StreamWriter
@bytecodealliance/preview3-shim/futureNode.js only: future(), FutureReader, and FutureWriter
@bytecodealliance/preview3-shim/interfaces/*TypeScript declarations for each WASI interface

Instantiation and configuration

A transpiled 0.3 component imports the shim’s subpaths directly, so running it is a matter of configuring the shim’s global state and calling the component’s exports. The CLI setters mirror those of the Preview 2 shim, and Preview 3 exports are asynchronous:

import { _setArgs, _setEnv } from "@bytecodealliance/preview3-shim/cli";
import { _setPreopens } from "@bytecodealliance/preview3-shim/filesystem";

_setArgs(["component", "input.txt"]);
_setEnv({ MODE: "production" });
_setPreopens({ "/data": "/tmp/guest-data" }); // Node.js: guest path to host path

const component = await import("./dist/transpiled/component.js");
await component.run.run();

With --instantiation async, assemble the import object from the shim namespaces yourself, keyed by the versioned interface names the component imports:

import { cli, clocks, filesystem, random } from "@bytecodealliance/preview3-shim";
import { instantiate } from "./dist/transpiled/component.js";

const component = await instantiate(loader, {
    "wasi:cli/environment@0.3.0": cli.environment,
    "wasi:cli/stdout@0.3.0": cli.stdout,
    "wasi:clocks/monotonic-clock@0.3.0": clocks.monotonicClock,
    "wasi:filesystem/preopens@0.3.0": filesystem.preopens,
    "wasi:filesystem/types@0.3.0": filesystem.types,
    "wasi:random/random@0.3.0": random.random,
});

See Manual Wasm instantiation with WASI overrides for the instantiation flow; the Preview 3 shim has no WASIShim equivalent yet, so there is no per-instance sandbox and the setters above apply to every component in the process.

Working with asynchronous values

Preview 3 hosts exchange streams, futures, and async calls with the guest. A host import that returns a stream<u8> provides any ReadableStream or async iterable; one that returns a future<T> provides a Promise:

async function* lines(source) {
    try {
        for await (const chunk of source) {
            yield chunk;
        }
    } finally {
        await source.close();
    }
}

const imports = {
    "example:host/input": {
        // stream<u8>
        open: () => lines(openSource()),
        // future<result<_, string>>
        ready: () => Promise.resolve({ tag: "ok", val: undefined }),
    },
};

On Node.js, stream() and future() build a paired writer and reader so host code can push values into a guest-visible stream over time:

import { stream } from "@bytecodealliance/preview3-shim/stream";
import { future } from "@bytecodealliance/preview3-shim/future";

const { tx, rx } = stream();
const { tx: doneTx, rx: doneRx } = future();

// Hand rx and doneRx to the guest, then feed it from the host.
await tx.write(new TextEncoder().encode("first\n"));
await tx.write(new TextEncoder().encode("second\n"));
await tx.close();
await doneTx.write({ tag: "ok", val: undefined });

Two chapters describe the details that matter once data is in flight:

Supported platforms

Node.js

The Node.js build gives a component host filesystem, environment, and network access by default. Standard streams and filesystem operations run in a worker so that blocking host I/O never stalls the event loop that drives the component’s asynchronous exports.

Browsers

The browser build reuses the Preview 2 shim’s capability model: clocks and randomness use Web APIs, stdio is console-backed, outbound HTTP delegates to fetch, and filesystem preopens are empty until configured. The same filesystem adapters (InMemoryFilesystemAdapter, OpfsFilesystemAdapter, and application-provided ones) are re-exported from @bytecodealliance/preview3-shim/filesystem, with descriptors wrapped to expose the 0.3 stream based interface. See the Preview 2 shim notes for what each interface needs from the embedding application.