Skip to content

anfibiacreativa/componentizejs-test

Repository files navigation

This is a test implementation of bytecodealliance/componentize-js

Pre-requisites

  • Node.js and npm are required to build and run this project.

  • Cargo must be installed, following the instructions in the link, for your specific platform.

Please notice that I have implemented this demo with node.js 16.16.0 and npm 8.11.0

Creating the repo from scratch

Create the project folder

mkdir componentize-demo && cd componentize-demo

Now we will initialize this folder as an npm package, running

npm init -y

(we pass the -y flag to skip the interactive questions).

We will then open the generated package.json file and add the following line:

"type": "module"

This is required to enable ES modules in Node.js.

Now we will install the componentize-js package:

npm install --save-dev @bytecodealliance/componentize-js

and we're going to create a new file called componentize.mjs that will be used to generate our Wasm module. Copy and paste the following code to it.

import { componentize } from '@bytecodealliance/componentize-js';
import { readFile, writeFile } from 'node:fs/promises';

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

const { component } = await componentize(jsSource, witSource);

await writeFile('hello.component.wasm', component);

Create the WIT and JS files

Now we're going to create a WIT file called hello.wit. WIT stands for WebAssembly Interface Types and it's a file that describes the interface of a WebAssembly module. Copy and paste the following code to it.

default world hello {
  export hello: func(name: string) -> string
}

We are also going to create a JavaScript file called hello.js that will be used to implement the WIT file. This file exports a single function. Copy and paste the function to the file.

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

Generate the Wasm module

Now we're ready to generate our Wasm module:

node componentize.mjs

You should see the generated binary file hello.component.wasm in your project folder.

Create the Rust project

Now we're going to create a Rust project that will be used to load and run our Wasm module.

For that, we're going to run the following command:

cargo init --bin

at the root of our project, just like we did with npm for Node.js.

This will create a Cargo.toml file and a src folder with a main.rs file inside.

Now we're going to add the following lines to the Cargo.toml file:

[package]
name = "wasmtime-test"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace]

[dependencies]
anyhow = "1.0.65"
async-std = { version = "1.12.0", features = ["attributes"] }
cap-std = "1.0.12"
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "299131ae2d6655c49138bfab2c4469650763ef3b", features = ["component-model"] }
wasi-common =  { git = "https://github.com/bytecodealliance/preview2-prototyping", rev = "dd34a00d4386000cd00071cff18b9e3a12788075" }
wasi-cap-std-sync = { git = "https://github.com/bytecodealliance/preview2-prototyping", rev = "dd34a00d4386000cd00071cff18b9e3a12788075" }
wasmtime-wasi-sockets =  { git = "https://github.com/bytecodealliance/preview2-prototyping", rev = "dd34a00d4386000cd00071cff18b9e3a12788075" }
wasmtime-wasi-sockets-sync = { git = "https://github.com/bytecodealliance/preview2-prototyping", rev = "dd34a00d4386000cd00071cff18b9e3a12788075" }

This will add the dependencies we need to run our Wasm module.

We will also go to src/main.rs and add the following code to it:

use anyhow::Result;
use wasi_cap_std_sync::WasiCtxBuilder;
use wasi_common::{wasi, Table, WasiCtx, WasiView};
use wasmtime::{
    component::{Component, Linker},
    Config, Engine, Store, WasmBacktraceDetails,
};
use wasmtime_wasi_sockets::{WasiSocketsCtx, WasiSocketsView};
use wasmtime_wasi_sockets_sync::WasiSocketsCtxBuilder;

wasmtime::component::bindgen!({
    world: "hello",
    path: "hello.wit",
    async: true
});

#[async_std::main]
async fn main() -> Result<()> {
    let builder = WasiCtxBuilder::new().inherit_stdio();
    let mut table = Table::new();
    let wasi = builder.build(&mut table)?;

    let mut config = Config::new();
    config.cache_config_load_default().unwrap();
    config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
    config.wasm_component_model(true);
    config.async_support(true);

    let engine = Engine::new(&config)?;
    let mut linker = Linker::new(&engine);

    let component = Component::from_file(&engine, "hello.component.wasm").unwrap();


    struct CommandCtx {
        table: Table,
        wasi: WasiCtx,
        sockets: WasiSocketsCtx,
    }
    impl WasiView for CommandCtx {
        fn table(&self) -> &Table {
            &self.table
        }
        fn table_mut(&mut self) -> &mut Table {
            &mut self.table
        }
        fn ctx(&self) -> &WasiCtx {
            &self.wasi
        }
        fn ctx_mut(&mut self) -> &mut WasiCtx {
            &mut self.wasi
        }
    }
    let sockets = WasiSocketsCtxBuilder::new()
        .inherit_network(cap_std::ambient_authority())
        .build();
    impl WasiSocketsView for CommandCtx {
        fn table(&self) -> &Table {
            &self.table
        }
        fn table_mut(&mut self) -> &mut Table {
            &mut self.table
        }
        fn ctx(&self) -> &WasiSocketsCtx {
            &self.sockets
        }
        fn ctx_mut(&mut self) -> &mut WasiSocketsCtx {
            &mut self.sockets
        }
    }

    wasi::command::add_to_linker(&mut linker)?;
    let mut store = Store::new(
        &engine,
        CommandCtx {
            table,
            wasi,
            sockets,
        },
    );

    let (instance, _instance) =
        Hello::instantiate_async(&mut store, &component, &linker).await?;

    let res = instance.call_hello(&mut store, "ComponentizeJS").await?;
    println!("{}", res);
    Ok(())
}

This code will load our Wasm module and run it.

Building and running the Rust project

The first thing we need to do, is to build our Rust project. For that, we're going to run the following command:

cargo build --release

Now we're ready to run it.

cargo run ./target/release/componentizejs

After running this command, you should see the following output:

Hello ComponentizeJS

Transpiling and running it in the Node.js

Now we're going to transpile our Wasm module to JavaScript and run it in Node.js. First of all, we need to install additional dependencies:

npm install --save @bytecodealliance/jco @bytecodealliance/preview2-shim

We will also update our package.json file to add the following lines:

"scripts": {
    "transpile": "jco transpile hello.component.wasm -o hello --wasi-shim",
    ...
}

Note I did not install the jco CLI globally, but locally. What this line does is execute jsco to transpile the .wasm component using Typescript interfaces, and generate the moules in a new folder called hello. For that, it uses the flag --wasi-shim to resolve the imports.

Now we can go back to the terminal and run the following command:

npm run transpile

We should see the following output:

Transpiled JS Component Files:

 - hello/hello.component.core.wasm     6.44 MiB
 - hello/hello.component.core2.wasm    30.1 KiB
 - hello/hello.component.d.ts          0.58 KiB
 - hello/hello.component.js            44.2 KiB
 - hello/imports/environment.d.ts      0.09 KiB
 - hello/imports/exit.d.ts             0.16 KiB
 - hello/imports/filesystem.d.ts       3.85 KiB
 - hello/imports/monotonic-clock.d.ts  0.14 KiB
 - hello/imports/preopens.d.ts         0.47 KiB
 - hello/imports/random.d.ts            0.1 KiB
 - hello/imports/streams.d.ts          0.58 KiB
 - hello/imports/wall-clock.d.ts

Those have been saved to a new folder in your project called hello.

Now you can use the REPL to run the following command:

node -e "import('./hello/hello.component.js').then(m => console.log(m.hello('ComponentizeJS')))"

You should see the following output:

Hello ComponentizeJS

About

Componentize.js on node and wasmtime

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published