Skip to content

Commit

Permalink
refactor(examples): refactor example instructions
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Adossi <[email protected]>
  • Loading branch information
vados-cosmonic committed Dec 31, 2024
1 parent 3e9d4b5 commit 91f616a
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 23 deletions.
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.
2 changes: 1 addition & 1 deletion example/hello.wit → examples/hello-world/guest/hello.wit
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 ComponentizeJSz'; 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";

0 comments on commit 91f616a

Please sign in to comment.