WebAssembly Component Model Experiments
October 15, 2025 Β· View on GitHub
I wrote a blog post that goes in much more details and is accessible to a wider audience: Building a plugin system with WebAssembly Component Model.
The WebAssembly Component Model is a broad-reaching architecture for building interoperable WebAssembly libraries, applications, and environments.
It is still very early days, but it is a very promising technology. However, the examples out there are either too simple or too complex.
The goal of this project is to demonstrate the power of the WebAssembly Component Model, with more than a simple hello world.
It is a basic REPL, with a plugin system where:
- plugins can be written in any language compiling to WebAssembly
- plugins are sandboxed by default
- the REPL logic is written in Rust, it also compiles to WebAssembly (you could swap it for your implementation in your own language)
There are two kinds of hosts:
- a CLI host
pluginlab, written in Rust running in a terminal - a web host, written in TypeScript running in a browser
Those hosts then run the same codebase which is compiled to WebAssembly:
- the REPL logic
- the plugins (made a few in rust, C, Go, and TypeScript)
Security model: the REPL cli implements a security model inspired by deno:
--allow-net: allows network access to the plugins, you can specify a list of domains comma separated (by default, no network access is allowed)--allow-read: allows read access to the filesystem--allow-write: allows write access to the filesystem--allow-all: allows all permissions (same as all the flags above), short:-A
Plugins are sandboxed by default - they cannot access the filesystem or network unless explicitly permitted. This allows safe execution of untrusted plugins while maintaining the flexibility to grant specific permissions when needed.
Plugins like ls or cat can interact with the filesystem using the primitives of the languages they are written in.
- on the CLI, a folder from the disk is mounted via the
--dirflag - on the browser, a virtual filesystem is mounted, the I/O operations are forwarded via a local fork of
@bytecodealliance/preview2-shim/filesystemshim, which shims thewasi:filesystemfilesystem interface
Check the online demo at
topheman.github.io/webassembly-component-model-experiments
Example of running the CLI pluginlab
Previous work with WebAssembly
In the last seven years I've done a few projects involving rust and WebAssembly:
- topheman/bevy-rust-wasm-experiments: Proof of concept that aims to demonstrate how to code a video game in rust that compiles both to a binary file and a web site (via WebAssembly, binding the accelerometer and gyroscope sensors from the mobile device)
- topheman/webassembly-wasi-experiments: Discover WebAssembly System Interface (WASI) with C/Rust targetting NodeJS, python, Wasmtime and the browser
- topheman/rust-wasm-experiments: Discover how to use Rust to generate WebAssembly, called by JavaScript
Usage
pluginlab (rust) - REPL cli host
Install the binary
Using cargo (from source)
cargo install pluginlab
Using homebrew
brew install topheman/tap/pluginlab
Run
pluginlab\
--repl-logic https://topheman.github.io/webassembly-component-model-experiments/plugins/repl_logic_guest.wasm\
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_greet.wasm\
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_ls.wasm\
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_echo.wasm\
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_weather.wasm\
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_cat.wasm\
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_tee.wasm\
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-c.wasm\
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-go.wasm\
--allow-all
Other flags:
--dir: directory to be preopened (by default, the current directory)--allow-net: allows network access to the plugins, you can specify a list of domains comma separated (by default, no network access is allowed)--allow-read: allows read access to the filesystem--allow-write: allows write access to the filesystem--allow-all: allows all permissions (same as all the flags above), short:-A--help: displays manual--debug: run the host in debug mode (by default, the host runs in release mode)
π Example of running the CLI host
pluginlab\ --repl-logic https://topheman.github.io/webassembly-component-model-experiments/plugins/repl_logic_guest.wasm\ --plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_greet.wasm\ --plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_ls.wasm\ --plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_echo.wasm\ --plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_weather.wasm\ --plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_cat.wasm\ --plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_tee.wasm\ --plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-c.wasm\ --plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-go.wasm\ --allow-all [Host] Starting REPL host... [Host] Loading REPL logic from: https://topheman.github.io/webassembly-component-model-experiments/plugins/repl_logic_guest.wasm [Host] Loading plugin: https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_greet.wasm [Host] Loading plugin: https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_ls.wasm [Host] Loading plugin: https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_echo.wasm [Host] Loading plugin: https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_weather.wasm [Host] Loading plugin: https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_cat.wasm [Host] Loading plugin: https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_tee.wasm [Host] Loading plugin: https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-c.wasm [Host] Loading plugin: https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-go.wasm repl(0)> echo foo foo repl(0)> echo $ROOT/$USER /Users/Tophe repl(0)> export FOO=totorepl(0)> echo FOO Hello, toto! repl(0)> ls wit wit/host-api.wit wit/plugin-api.wit wit/shared.wit repl(0)> weather Paris Sunny repl(0)> weather New York Partly cloudy repl(0)> azertyuiop Unknown command: azertyuiop. Try
helpto see available commands. repl(1)> echo USER Hello, Tophe! repl(0)> echo $0 Hello, Tophe! repl(0)>
web-host (typescript)
Go check topheman.github.io/webassembly-component-model-experiments online demo.
Development
Prerequisites
- Rust 1.87+
- Node.js 22.6.0+ (needs
--experimental-strip-typesflag) - Go 1.25+
- just
Setup
# Add WebAssembly targets
rustup target add wasm32-unknown-unknown wasm32-wasip1
# Install project dependencies (web part)
npm install
# Install Playwright browsers (e2e tests for web-host)
npx playwright install
# Install cargo component subcommand
cargo binstall cargo-component
C tooling
From the WebAssembly Component Model section for C tooling
# Initialize the .env file tracking the WASI SDK version for C development
# You will be asked to update the WASI_OS and WASI_ARCH variables if needed
just init-env-file
cargo install wit-bindgen-cli@0.44.0
# Install the wasm-tools tool - you can also use cargo install wasm-tools@1.235.0 if you don't have cargo-binstall
cargo binstall wasm-tools@1.235.0
# Download the WASI SDK into ./c_deps/wasi-sdk folder
just dl-wasi-sdk
Go tooling
From the WebAssembly Component Model section for Go tooling
- Install TinyGo
- Install wasm-tools - same as C
- Install wkg
cargo binstall wkg
pluginlab (rust) - REPL cli host
Build
just build
This will (see justfile):
- compile the pluginlab crate from rust to a binary file
- compile the repl-logic-guest crate from rust to wasm
- compile the plugin-* crates from rust to wasm
- compile the c_modules/plugin-* C plugins to wasm
Run
./target/debug/pluginlab\
--repl-logic ./target/wasm32-wasip1/debug/repl_logic_guest.wasm\
--plugins ./target/wasm32-wasip1/debug/plugin_greet.wasm\
--plugins ./target/wasm32-wasip1/debug/plugin_ls.wasm\
--plugins ./target/wasm32-wasip1/debug/plugin_echo.wasm\
--plugins ./target/wasm32-wasip1/debug/plugin_weather.wasm\
--plugins ./target/wasm32-wasip1/debug/plugin_cat.wasm\
--plugins ./target/wasm32-wasip1/debug/plugin_tee.wasm\
--plugins ./c_modules/plugin-echo/plugin-echo-c.wasm\
--plugins ./go_modules/plugin-echo/plugin-echo-go.wasm\
--allow-all
This will run the pluginlab binary which will itself:
- load and compile the
repl_logic_guest.wasmfile inside the embeddedwasmtimeengine injecting thehost-apiinterface - load and compile the
plugin*.wasmfiles passed via--pluginsinto the engine, injecting theplugin-apiinterface - launch the REPL loop executing the code from the
repl_logic_guest.wasmfile which will:- readline from the user
- parse the command
- dispatch the command to the plugin(s) if needed (run the
run,manfunctions of the plugins via thehost-apiinterface) - display the result
Other example:
./target/debug/pluginlab\
--repl-logic ./target/wasm32-wasip1/debug/repl_logic_guest.wasm\
--plugins ./target/wasm32-wasip1/debug/plugin_ls.wasm\
--plugins ./target/wasm32-wasip1/debug/plugin_echo.wasm\
--dir /tmp\
--allow-all
Test
# Runs unit tests and e2e tests on the REPL
just test
# Runs e2e tests on the REPL against the latest version of the plugins available at https://topheman.github.io/webassembly-component-model-experiments/plugins
# Launch to check for breaking changes with the WIT interface
just test-e2e-pluginlab-http-latest
Make a rust plugin
cargo component new --lib crates/plugin-hello
Publish
# Dry run
just publish-pluginlab-dry-run
Once you're happy with the changes, you can publish the pluginlab crate:
just publish-pluginlab
web-host (typescript)
Dev
npm run web-host:dev
This Will (see packages/web-host/package.json):
- generate types from the wit files using the jco tool
- build the plugins from rust to wasm (so that you don't have to do it manually)
- build the repl-logic-guest from rust to wasm (so that you don't have to do it manually)
- copy the wasm files in
target/wasm32-wasip1/releaseto thepackages/web-host/public/pluginsdirectory (to make them available via http for thepluginlabbinary) - transpile the wasm files to javascript using the jco tool into
packages/web-host/src/wasm/generated/*/transpiled(this is the glue code wrapping the wasm files which is needed to interact with in the browser or node) - start the vite dev server
Go to http://localhost:5173 to see the web host.
Build
npm run web-host:build
Will do the same as the dev command, small changes:
- the build tasks called on the rust side are
just *-release(release mode) - it doesn't start the vite dev server, it builds the static files in the
distdirectory
You can then run npm run web-host:preview to preview the build.
Test
The project is configured to run e2e tests on the web-host using playwright, the test files are in packages/web-host/tests.
To run the tests against your local dev server (launched with npm run dev)
npm run test:e2e:all: will run all the tests in headless modenpm run test:e2e:ui: will open the playwright ui to run the tests
To run the tests against a preview server (build with npm run build and launched with npm run preview):
npm run test:e2e:all:preview: will run all the tests in headless modenpm run test:e2e:ui:preview: will open the playwright ui to run the tests
Specific to github actions:
In .github/workflows/web-host.yml, after the build step, the tests are run against the preview server.
To be sure that the preview server is up and running before running the tests, we use the webServer.command option of playwright.config.ts to run WAIT_FOR_SERVER_AT_URL=http://localhost:4173/webassembly-component-model-experiments/ npm run test:e2e:all:preview
plugins
There are currently plugins implemented in 3 languages (most of them are in rust), their toolchain is already setup in the project, you just have to write the plugin code and run just build.
Rust
You can write plugins in rust in crates/plugin-*.
C
You can write plugins in C in c_modules/plugin-*, thanks to wit-bindgen (based on wit-bindgen).
Go
You can write plugins in Go in go_modules/plugin-*, thanks to TinyGo Compiler.
TypeScript
You can also write plugins in TypeScript in packages/plugin-*, thanks to jco componentize (based on componentize-js).
The downsides of writing plugins in TypeScript is mostly that your .wasm file will be much larger than the one compiled from rust or C:
- ~100KB of wasm for the rust plugin
- 11MB of wasm for the TypeScript plugin
The reason is that a JavaScript runtime needs to be embedded in the .wasm file, which is not the case for the rust plugin.
More about the SpiderMonkey runtime embedding.
Other languages
Coming.
CI
Testing
The pluginlab binary is built and tested e2e (using rexpect library).
The web-host is built and tested e2e (using playwright).
Deployment
The web-host is automatically deployed to github pages when pushed on the master branch, containing the latest wasm versions of the plugins available at https://topheman.github.io/webassembly-component-model-experiments/plugins.
Pre-release
https://github.com/topheman/webassembly-component-model-experiments/releases
When a git tag is pushed, a pre-release is prepared on github, linked to the tag, containing the wasm files for the plugins and the repl-logic, in order to version those. That way, you can use an old binary of pluginlab against the correct versions of the plugins:
pluginlab\
--repl-logic https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/repl_logic_guest.wasm\
--plugins https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/plugin_greet.wasm\
--plugins https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/plugin_ls.wasm\
--plugins https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/plugin_echo.wasm\
--plugins https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/plugin_weather.wasm\
--plugins https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/plugin_cat.wasm\
--plugins https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/plugin_tee.wasm\
--plugins https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/plugin-echo-c.wasm\
--plugins https://github.com/topheman/webassembly-component-model-experiments/releases/download/pluginlab@0.5.2/plugin-echo-go.wasm\
--allow-all
Developer experience
Formating and linting
- I use biome for formating and linting the TypeScript code
- I use cargo fmt for formating the rust code
- They are both configured to run on save in the editor
Git hooks
- I use husky to run lint-staged on pre-commit
- I use lint-staged to run linting and formating on the changed files - the following are automatically run on pre-commit:
- formating / linting the TypeScript code
- formating the rust code
- typechecking the TypeScript code
Local fork of @bytecodealliance/preview2-shim
- The original
@bytecodealliance/preview2-shimfor the browser doesn't support WRITE operations on the filesystem - A fork was created in
packages/web-host/overrides/@bytecodealliance/preview2-shimto fix this issue
Everything is described in PR#15 Support plugin-tee in the web host (must read π).
Resources
Optional tools
Those are optional tools that are handy for WebAssembly development:
# latest versions
cargo binstall cargo-component wasm-tools wasm-opt
# specific versions I used for this project
cargo binstall cargo-component@0.21.1 wasm-tools@1.235.0 wasm-opt@116
