Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ jobs:
key: output-example-jit-cargo-${{ hashFiles('example/src/main.rs', 'example/Cargo.lock', 'example/hello.wit') }}

- name: Test Example
run: cd example && npm run build && ./test.sh
working-directory: examples/hello-world
run: bash test.sh

test-aot:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -349,4 +350,5 @@ jobs:
key: output-example-aot-cargo-${{ hashFiles('example/src/main.rs', 'example/Cargo.lock', 'example/hello.wit') }}

- name: Test Example
run: cd example && npm run build && ./test.sh
working-directory: examples/hello-world
run: bash test.sh
7 changes: 0 additions & 7 deletions example/README.md

This file was deleted.

4 changes: 0 additions & 4 deletions example/test.sh

This file was deleted.

3 changes: 3 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Examples

This folder contains example projects that use `componentize-js`.
132 changes: 132 additions & 0 deletions examples/hello-world/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Example Javascript component

This folder contains an example Javascript project that uses `componentize-js`
as a library to build a basic [WebAssembly component][cm-book].

[cm-book]: https://component-model.bytecodealliance.org/

## Overview

This folder contains *two* codebases:

- `guest` contains the Javascript WebAssembly Component
- `host` contains a Rust host that has been configured to run the component

### `guest` - A WebAssembly component written in Javascript

The [WebAssembly Interface Types ("WIT")][wit] interface ([`hello.wit`](./guest/hello.wit)) for the component is:

```wit
package local:hello;

world component {
export hello: func(name: string) -> string;
}
```

A Javascript (ES) module that conforms to the interface shown above looks like the following:

```js
export function hello (name) {
return `Hello ${name}`;
}
```

> [!NOTE]
> The ES module is assumed implicitly to *be* the targeted `world`.
>
> This means that the JS export of the `hello` function maps to the
> WIT `hello` `export` of the `component` world.
>
> The world does not have to be called `component`.

See [`hello.js`](./guest/hello.js) for the full code listing.

We call the produced WebAssembly component "guest" as it is code that will run on
the WebAssembly virtual machine/runtime.

[wit]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md

## `host` - A WebAssembly runtime embedding written in Rust

Since our component does not export a standardized way to run it (in this case,
the standard we *could* have used would be [WASI CLI][wasi-cli]), we must use a custom host which
embeds a WebAssembly runtime ([`wasmtime`][wasmtime] in this case) to run the WebAssembly Component.

`wasmtime` is easiest to use from [Rust][rust], so we have the `host` that contains
setup code which enables use of the component we wrote, and calls it.

See [`host/src/main.rs`](./host/src/main.rs) for the full code listing.

[wasmtime]: https://github.com/bytecodealliance/wasmtime
[wasi-cli]: https://github.com/WebAssembly/wasi-cli
[rust]: https://rust-lang.org

## Build the component

To build the WebAssembly component, enter the `guest` directory and install dependencies:

```console
npm install
```

Then either run the `componentize.js` script directly:

```console
node componentize.js
```

Or use the pre-configured `build` script:

```console
npm run build
```

## Run the component

### Via automation

To run the component and test it's output, use the included bash script:

```console
./test.sh
```

### Manually

To run the component manually, we must run our custom `wasmtime` embedding manually.

First enter the `host` directory and use `cargo run`:

```console
cargo run
```

## Common Issues

### No such file or directory

If you get an error that looks like the following:

```
thread 'main' panicked at src/main.rs:39:67:
called `Result::unwrap()` on an `Err` value: failed to read from `../../guest/hello.component.wasm`

Caused by:
No such file or directory (os error 2)
```

This means that the default path (which is relative, and embedded in the binary) to the component
produced by the `guest` is not present.

To fix this, specify `COMPONENT_WASM_PATH` as an environment variable before `cargo run`:

```console
COMPONENT_WASM_PATH=/absolute/path/to/hello.component.wasm cargo run
```

If you're running the produced `wasmtime-test` binary itself:

```console
COMPONENT_WASM_PATH=/absolute/path/to/hello.component.wasm path/to/wasmtime-test
```
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { componentize } from '@bytecodealliance/componentize-js';
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';

const enableAot = process.env.ENABLE_AOT == '1'
import { componentize } from '@bytecodealliance/componentize-js';

// AoT compilation makes use of weval (https://github.com/bytecodealliance/weval)
const enableAot = process.env.ENABLE_AOT == '1';

const jsSource = await readFile('hello.js', 'utf8');

Expand Down
Binary file added examples/hello-world/guest/hello.component.wasm
Binary file not shown.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package local:hello;

world hello {
world component {
export hello: func(name: string) -> string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"@bytecodealliance/componentize-js": "*"
},
"scripts": {
"build": "node componentize.js && cargo build --release",
"test": "./target/release/wasmtime-test"
"build": "node componentize.js && cargo build --release"
}
}
File renamed without changes.
File renamed without changes.
31 changes: 24 additions & 7 deletions example/src/main.rs → examples/hello-world/host/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use anyhow::Result;
use wasmtime::{
component::{Component, Linker},
Expand All @@ -6,11 +8,23 @@ use wasmtime::{
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};

wasmtime::component::bindgen!({
world: "hello",
path: "hello.wit",
async: true
});
mod bindings {
// This macro produces generated code that is used to link
// the functionality exposed by the component, and eventually
// call it.
wasmtime::component::bindgen!({
world: "component",
path: "../guest/hello.wit",
async: true
});
}

// Default path to the WebAsssembly component as generated in the 'guest' folder,
// facilitating `cargo run` from the 'host' directory.
//
// If this binary is compiled and used from another folder, this path will likely be invalid,
// and in that case, using the `COMPONENT_PATH` environment variable is preferred.
const DEFAULT_COMPONENT_PATH: &str = "../guest/hello.component.wasm";

#[async_std::main]
async fn main() -> Result<()> {
Expand All @@ -28,7 +42,10 @@ async fn main() -> Result<()> {
let engine = Engine::new(&config)?;
let mut linker = Linker::new(&engine);

let component = Component::from_file(&engine, "hello.component.wasm").unwrap();
let component_path = std::env::var("COMPONENT_WASM_PATH")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(DEFAULT_COMPONENT_PATH));
let component = Component::from_file(&engine, component_path).unwrap();

struct CommandExtendedCtx {
table: ResourceTable,
Expand Down Expand Up @@ -63,7 +80,7 @@ async fn main() -> Result<()> {
},
);

let hello = Hello::instantiate_async(&mut store, &component, &linker).await?;
let hello = bindings::Component::instantiate_async(&mut store, &component, &linker).await?;
let res = hello.call_hello(&mut store, "ComponentizeJS").await?;
println!("{}", res);
Ok(())
Expand Down
29 changes: 29 additions & 0 deletions examples/hello-world/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash

# NOTE: COMPONENT_WASM_PATH will be picked up by the test binary as well
export COMPONENT_WASM_PATH=$(realpath guest)/hello.component.wasm
export TEST_BINARY_PATH=$(realpath host)/target/release/wasmtime-test

# Build the JS component if not present
echo -e "[info] expecting component WASM at [$COMPONENT_WASM_PATH]...";
if [ ! -f "$COMPONENT_WASM_PATH" ]; then
cd guest && npm install && npm build
fi

# Build the Rust embedding test binary if not present
echo -e "[info] expecting test binary at [$COMPONENT_WASM_PATH]...";
if [ ! -f "$TEST_BINARY_PATH" ]; then
cd host && cargo build --release
fi

# Run the test binary, capturing the output
CMD_OUTPUT=$($TEST_BINARY_PATH)

# Ensure hte output contained what we expected
if ! echo $CMD_OUTPUT | grep -q 'Hello ComponentizeJS'; then
echo "[error] test binary output (below) does not contain 'Hello ComponentizeJS':";
echo "$CMD_OUTPUT";
exit 1;
fi

echo "[success] test embedding binary produced expected output";
Loading