Convert JavaScript source code into WebAssembly components using QuickJS.
componentize-qjs takes a JavaScript source file and a
WIT definition,
and produces a standalone WebAssembly component that can run on any
component-model runtime (e.g. Wasmtime).
Under the hood it:
- Embeds the QuickJS engine (via rquickjs) as the JavaScript runtime.
- Uses wit-dylib to generate WIT bindings that bridge the component model and the JS engine.
- Snapshots the initialized JS state with Wizer so startup cost is paid at build time, not at runtime.
Building currently requires a nightly Rust toolchain because it relies on the wasi-libc being compiled with -fPIC. This requirement will be lifted with an upcoming version of Rust.
cargo +nightly install --path .The npm package is not yet published to the registry. To use it, build from source:
cd npm && npm install && npm run build1. Define a WIT interface (hello.wit):
package test:hello;
world hello {
export greet: func(name: string) -> string;
}2. Implement it in JavaScript (hello.js):
function greet(name) {
return `Hello, ${name}!`;
}3. Build the component:
componentize-qjs --wit hello.wit --js hello.js -o hello.wasm4. Run it:
wasmtime run --invoke 'greet("World")' hello.wasm
# Hello, World!componentize-qjs [OPTIONS] --wit <WIT> --js <JS>
| Flag | Short | Description |
|---|---|---|
--wit <PATH> |
-w |
Path to the WIT file or directory |
--js <PATH> |
-j |
Path to the JavaScript source file |
--output <PATH> |
-o |
Output path (default: output.wasm) |
--world <NAME> |
-n |
World name when the WIT defines multiple worlds |
--stub-wasi |
Replace all WASI imports with trap stubs | |
--minify |
-m |
Minify JS source before embedding |
| Feature | Effect |
|---|---|
optimize-size |
Uses -Oz instead of -O3 for smaller output |
Build with features:
cargo +nightly build --release --features optimize-sizeWIT imports are available on globalThis keyed by their fully-qualified name:
// imports.wit
package local:test;
interface math {
add: func(a: s32, b: s32) -> s32;
multiply: func(a: s32, b: s32) -> s32;
}
world imports {
import math;
export double-add: func(a: s32, b: s32) -> s32;
}// imports.js
function doubleAdd(a, b) {
const math = globalThis["local:test/math"];
const sum = math.add(a, b);
return math.multiply(sum, 2);
}The npm package exposes both a CLI and a programmatic API. It is not yet published to the registry — see Installation for building from source.
./npm/bin/componentize-qjs --wit hello.wit --js hello.js -o hello.wasmimport { componentize } from "componentize-qjs";
const { component } = await componentize({
witPath: "hello.wit",
jsSource: "function greet(name) { return `Hello, ${name}!`; }",
});
// component is a Buffer containing the WebAssembly component bytesThis project builds on ideas and code from:
- ComponentizeJS by Joel Dice
- lua-component-demo by Alex Crichton
Licensed under Apache-2.0.