This project showcases a few things:
- A Javascript WebAssembly component that imports and uses a resource
Disposablesupport in Typescript paired withjco's generated[Symbol.dispose]()in types- A custom implementation of the resource
- A custom embedding of the component that uses the custom resource
All of the above is done with Typescript, built via Rollup.
To build this example into a demo page that you can visit, run:
npm install
npm run allNote
Feel free to replace npm with whatever npm-compatible tooling you prefer.
The all script will:
- Build the component that uses the resource
- Transpile it
- Build the embedding
- Run the embedding script which executes the transpiled component
> [email protected] all
> npm run build && npm run transpile && npm run build:embedding && npm run run:embedding
> [email protected] build
> npm run gen:types && npm run build:ts && npm run build:component
> [email protected] gen:types
> jco guest-types -o generated/types/guest/import wit/ --world-name imported
Generated Guest Typescript Definition Files (.d.ts):
- generated/types/guest/import/imported.d.ts 0.34 KiB
- generated/types/guest/import/interfaces/test-component-resources.d.ts 0.17 KiB
- generated/types/guest/import/interfaces/wasi-cli-run.d.ts 0.1 KiB
> [email protected] build:ts
> rollup -c component.rollup.mjs
src/component.ts → dist/component.js...
(!) Unresolved dependencies
https://rollupjs.org/troubleshooting/#warning-treating-module-as-external-dependency
test:component/resources (imported by "src/component.ts")
created dist/component.js in 848ms
> [email protected] build:component
> jco componentize -w wit/ --world-name imported dist/component.js -o dist/component.wasm
OK Successfully written dist/component.wasm.
> [email protected] transpile
> jco transpile dist/component.wasm -o dist/transpiled --instantiation=async
Transpiled JS Component Files:
- dist/transpiled/component.core.wasm 10.6 MiB
- dist/transpiled/component.core2.wasm 14.4 KiB
- dist/transpiled/component.core3.wasm 4.34 KiB
- dist/transpiled/component.core4.wasm 0.57 KiB
- dist/transpiled/component.d.ts 4.65 KiB
- dist/transpiled/component.js 254 KiB
- dist/transpiled/interfaces/test-component-resources.d.ts 0.17 KiB
- dist/transpiled/interfaces/wasi-cli-run.d.ts 0.07 KiB
- dist/transpiled/interfaces/wasi-cli-stderr.d.ts 0.16 KiB
- dist/transpiled/interfaces/wasi-cli-stdin.d.ts 0.15 KiB
- dist/transpiled/interfaces/wasi-cli-stdout.d.ts 0.16 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-input.d.ts 0.22 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-output.d.ts 0.22 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-stderr.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-stdin.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-stdout.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-clocks-monotonic-clock.d.ts 0.37 KiB
- dist/transpiled/interfaces/wasi-clocks-wall-clock.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-filesystem-preopens.d.ts 0.19 KiB
- dist/transpiled/interfaces/wasi-filesystem-types.d.ts 2.98 KiB
- dist/transpiled/interfaces/wasi-http-outgoing-handler.d.ts 0.47 KiB
- dist/transpiled/interfaces/wasi-http-types.d.ts 9.51 KiB
- dist/transpiled/interfaces/wasi-io-error.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-io-poll.d.ts 0.28 KiB
- dist/transpiled/interfaces/wasi-io-streams.d.ts 0.97 KiB
- dist/transpiled/interfaces/wasi-random-random.d.ts 0.14 KiB
> [email protected] build:embedding
> rollup -c embedding.rollup.mjs
embedding.mts → dist/transpiled/embedding.js...
(!) Circular dependency
../../../packages/preview2-shim/lib/browser/filesystem.js -> ../../../packages/preview2-shim/lib/browser/cli.js -> ../../../packages/preview2-shim/lib/browser/filesystem.js
created dist/transpiled/embedding.js in 1.2s
> [email protected] run:embedding
> node dist/transpiled/embedding.js
constructed [LocalExample(1)]!
[LocalExample(1)] Hello WORLD!
disposing [LocalExample(1)]Want to go through it step-by-step? Read along from here.
Similar to any other NodeJS project, you can install dependencies with npm install:
npm installNote
Feel free to replace npm with whatever npm-compatible tooling you prefer.
Note
This step is already done for you (the files are present in this repo). But the following steps are useful if you need to add / modify the component.wit and need to update the wit/deps to correspond.
This project makes use of the [wasi:http][wasi-http] and wasi:cli interfaces, and they have to be
installed from the central repository.
If using wkg (standard ecosystem tooling for pulling WIT interfaces), you can do this in one step:
wkg wit fetchNote
Generally, files in wit/deps are usually third party dependencies managed by WebAssembly ecosystem tooling,
contrasted with the first party WIT for your component (ex. wit/component.wit).
You can build the Javascript code in component.js into a WebAssembly component by running:
npm run buildYou should see output like the following:
> [email protected] build
> npm run gen:types && npm run build:ts && npm run build:component
> [email protected] gen:types
> jco guest-types -o generated/types/guest/import wit/ --world-name imported
Generated Guest Typescript Definition Files (.d.ts):
- generated/types/guest/import/imported.d.ts 0.34 KiB
- generated/types/guest/import/interfaces/test-component-resources.d.ts 0.17 KiB
- generated/types/guest/import/interfaces/wasi-cli-run.d.ts 0.1 KiB
> [email protected] build:ts
> rollup -c component.rollup.mjs
src/component.ts → dist/component.js...
(!) Unresolved dependencies
https://rollupjs.org/troubleshooting/#warning-treating-module-as-external-dependency
test:component/resources (imported by "src/component.ts")
created dist/component.js in 811ms
> [email protected] build:component
> jco componentize -w wit/ --world-name imported dist/component.js -o dist/component.wasm
OK Successfully written dist/component.wasm.WebAssembly Components are built against the system interfaces available in [WASI][wasi].
For example, using a tool called wasm-tools we can see the imports and exports
of the component we've just built:
wasm-tools component wit dist/component.wasm
You should see output that looks something like this:
package root:component;
world root {
import wasi:cli/environment@0.2.3;
import wasi:io/poll@0.2.3;
import wasi:clocks/monotonic-clock@0.2.3;
import wasi:io/error@0.2.3;
import wasi:io/streams@0.2.3;
import wasi:http/types@0.2.3;
import wasi:cli/stdin@0.2.3;
import wasi:cli/stdout@0.2.3;
import wasi:cli/stderr@0.2.3;
import wasi:cli/terminal-input@0.2.3;
import wasi:cli/terminal-output@0.2.3;
import wasi:cli/terminal-stdin@0.2.3;
import wasi:cli/terminal-stdout@0.2.3;
import wasi:cli/terminal-stderr@0.2.3;
import wasi:clocks/wall-clock@0.2.3;
import wasi:filesystem/types@0.2.3;
import wasi:filesystem/preopens@0.2.3;
import wasi:random/random@0.2.3;
import wasi:http/outgoing-handler@0.2.3; /// <---- This import is used by `fetch()`!
export wasi:http/incoming-handler@0.2.3; /// <---- This export enables responding to HTTP requests
}
/// ... elided ...Note
The meaning of all of these imports and exports is somewhat out of scope for this example, so we won't discuss
further, but please check out the [Component Model Book][cm-book] for more details.
As we noted earlier, WebAssembly Components are built against the system interfaces available in [WASI][wasi].
One of the benefits of using components and WASI is that we can reimplement those interfaces when the platform changes (this is sometimes called "virtual platform layering"). The host running the WebAssembly component can provide dependencies as necessary.
Thanks to jco transpile we can take our WebAssembly component (or any other WebAssembly component) and use
it on NodeJS, by converting the WebAssembly component into code that node can run today and
providing shims/polyfills for WASI functionality as necessary.
In practice this means producing a bundle of JS + WebAssembly Modules that can run in NodeJS:
npm run transpileYou should see output like the following:
> [email protected] transpile
> jco transpile dist/component.wasm -o dist/transpiled --instantiation=async
Transpiled JS Component Files:
- dist/transpiled/component.core.wasm 10.6 MiB
- dist/transpiled/component.core2.wasm 14.4 KiB
- dist/transpiled/component.core3.wasm 4.34 KiB
- dist/transpiled/component.core4.wasm 0.57 KiB
- dist/transpiled/component.d.ts 4.65 KiB
- dist/transpiled/component.js 254 KiB
- dist/transpiled/interfaces/test-component-resources.d.ts 0.17 KiB
- dist/transpiled/interfaces/wasi-cli-run.d.ts 0.07 KiB
- dist/transpiled/interfaces/wasi-cli-stderr.d.ts 0.16 KiB
- dist/transpiled/interfaces/wasi-cli-stdin.d.ts 0.15 KiB
- dist/transpiled/interfaces/wasi-cli-stdout.d.ts 0.16 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-input.d.ts 0.22 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-output.d.ts 0.22 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-stderr.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-stdin.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-cli-terminal-stdout.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-clocks-monotonic-clock.d.ts 0.37 KiB
- dist/transpiled/interfaces/wasi-clocks-wall-clock.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-filesystem-preopens.d.ts 0.19 KiB
- dist/transpiled/interfaces/wasi-filesystem-types.d.ts 2.98 KiB
- dist/transpiled/interfaces/wasi-http-outgoing-handler.d.ts 0.47 KiB
- dist/transpiled/interfaces/wasi-http-types.d.ts 9.51 KiB
- dist/transpiled/interfaces/wasi-io-error.d.ts 0.2 KiB
- dist/transpiled/interfaces/wasi-io-poll.d.ts 0.28 KiB
- dist/transpiled/interfaces/wasi-io-streams.d.ts 0.97 KiB
- dist/transpiled/interfaces/wasi-random-random.d.ts 0.14 KiB
The most important file in the generated bundle is dist/transpiled/component.js -- this is
the entrypoint for a (NodeJS, browser)script that wants to use the component we've just built.
While the component we built can be run in a WebAssembly runtime like wasmtime, we can also run it
from NodeJS via V8's WebAssembly support now that it has been transpiled.
To be able to use our transpiled component, the included embedding.mts script
can be used:
npm run embedding # or 'run:embedding' to skip the buildYou should see output like the following:
> [email protected] embedding
> npm run build:embedding && npm run run:embedding
> [email protected] build:embedding
> rollup -c embedding.rollup.mjs
embedding.mts → dist/transpiled/embedding.js...
(!) Circular dependency
../../../packages/preview2-shim/lib/browser/filesystem.js -> ../../../packages/preview2-shim/lib/browser/cli.js -> ../../../packages/preview2-shim/lib/browser/filesystem.js
created dist/transpiled/embedding.js in 1.1s
> [email protected] run:embedding
> node dist/transpiled/embedding.js
constructed [LocalExample(1)]!
[LocalExample(1)] Hello WORLD!
disposing [LocalExample(1)]