@andreiltd/componentize-qjs-binding-linux-arm64-gnu

中文文档
0.6.0发布于 2026/11/07Apache-2.0CommonJS零依赖构建来源已验证近 30 天下载 1.7万

Convert JavaScript to WebAssembly components using QuickJS

原始使用文档
文档目录26 节
  1. Overview
  2. Prerequisites
  3. Installation
  4. Rust CLI (crates.io)
  5. Rust CLI (from source)
  6. Prebuilt CLI binaries
  7. npm package
  8. Quick Start
  9. CLI Reference
  10. Cargo features
  11. Using Imports
  12. WIT Type Mappings
  13. Primitive Types
  14. Compound Types
  15. Imported Resources
  16. Cancelling Async Imports
  17. Async Exports
  18. Streams
  19. Futures
  20. Resource Cleanup
  21. Snapshot Initialization
  22. Node.js API
  23. CLI
  24. Usage
  25. Acknowledgments
  26. License

componentize-qjs

CI License: Apache-2.0

Convert JavaScript source code into WebAssembly components using QuickJS.

Overview

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:

  1. Embeds the QuickJS engine (via rquickjs) as the JavaScript runtime.
  2. Uses wit-dylib to generate WIT bindings that bridge the component model and the JS engine.
  3. Snapshots the initialized JS state with Wizer so startup cost is paid at build time, not at runtime.
  4. Prunes explicitly marked one-time setup code with Walrus and strips debug/build metadata from the snapshotted component.

Prerequisites

Rust 1.97 or later is required by Wasmtime (the wasm32-wasip2 target also needs a recent toolchain for PIC support in wasi-libc).

Installation

Rust CLI (crates.io)

cargo install componentize-qjs-cli --locked

This installs the componentize-qjs command.

Rust CLI (from source)

cargo install --path . --locked

Prebuilt CLI binaries

Prebuilt CLI archives are attached to each GitHub release for Linux, macOS, and Windows.

npm package

npm install componentize-qjs

This pulls in the right prebuilt native binding for your platform via the @andreiltd/componentize-qjs-binding-* optional dependencies.

If you want to build from source run:

cd npm && npm install && npm run build

Quick Start

1. Define a WIT interface (hello.wit):

package test:hello;

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

2. Implement it in JavaScript (hello.js):

JavaScript sources are ES modules. Export WIT functions and interfaces directly from the module.

export function greet(name) {
    return `Hello, ${name}!`;
}

3. Build the component:

componentize-qjs --wit hello.wit --js hello.js -o hello.wasm

4. Run it:

wasmtime run --wasm component-model-async=y --invoke 'greet("World")' hello.wasm
# "Hello, World!"

The built-in runtime published with componentize-qjs includes component-model async support. Pass --sync to embed the built-in non-async runtime instead, producing components that run on hosts without component-model async support. A custom runtime can also be supplied with --runtime.

CLI Reference

componentize-qjs [OPTIONS] --wit <WIT> --js <JS>
FlagShortDescription
--wit <PATH>-wPath to the WIT file or directory
--js <PATH>-jPath to the JavaScript source file
--output <PATH>-oOutput path (default: output.wasm)
--module-root <PATH>Root directory exposed read-only during Wizer for resolving JavaScript imports
--world <NAME>-nWorld name when the WIT defines multiple worlds
--stub-wasiReplace all WASI imports with trap stubs
--minify-mMinify JS source before embedding
--opt-sizeUse the built-in QuickJS runtime optimized for size
--syncUse the built-in non-async runtime (combine with --opt-size for the non-async opt-size runtime)
--runtime <PATH>Custom QuickJS runtime Wasm module to embed

Cargo features

FeatureEffect
component-model-async(default) Embed the component-model async runtime as the default built-in. The non-async runtime is always embedded and selectable via --sync. Disable to build a smaller binary with only the non-async runtime
opt-sizeSelects the bundled opt-size runtime when no runtime option is provided by the CLI or npm API

Build with features:

cargo build --release --features opt-size

Using Imports

WIT imports are available as ES module imports using their fully-qualified WIT interface 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
import math from "local:test/math";

export function doubleAdd(a, b) {
    const sum = math.add(a, b);
    return math.multiply(sum, 2);
}

JavaScript modules imported by the entry file are resolved during Wizer initialization. Relative imports are resolved from the entry file path passed to --js; bare package imports are resolved under the read-only module root. By default the CLI uses the current directory when the entry file is under it, or the entry file's parent directory otherwise. Use --module-root <PATH> to expose a project root that contains shared files or node_modules.

WIT Type Mappings

Primitive Types

WIT TypeJS TypeNotes
boolboolean
u8, u16, u32number
s8, s16, s32number
u64, s64BigInt
f32, f64number
charstringMust be exactly one Unicode scalar value
stringstring

Compound Types

WIT TypeJS TypeExample
list<T>Array[1, 2, 3]
list<u8>Uint8Array or Arraynew Uint8Array([1, 2, 3])
map<K, V>Mapnew Map([["key", value]])
tuple<T, U, ...>Array[42, "hello"]
option<T>T | undefined (wrapped: { tag: "some"|"none", val })undefined for none (null is also accepted as none when passed to the host); wrapped when T is itself a bare option, as in jco: option<option<T>> is wrapped, option<option<option<T>>> is not
result<T, E>top-level function result: return T or throw E; nested result: { tag: "ok"|"err", val?: T|E }return 42 / throw "error"
record { ... }object (camelCase keys){ myField: 1 }
variant{ tag: string, val?: T }{ tag: "circle", val: 2.5 }
enumstring (case name)"red"
flagsobject (camelCase booleans){ read: true, write: false }
own<R>, borrow<R>resource object (methods on its prototype)input.blockingRead(n)

Imported Resources

Imported resources are exposed as JavaScript classes. Resource methods are called on the handle:

import stdin from "wasi:cli/[email protected]";
import stdout from "wasi:cli/[email protected]";

const input = stdin.getStdin();     // an InputStream
const output = stdout.getStdout();  // an OutputStream

// Methods whose WIT return type is result<...> return the ok payload or throw.
const chunk = input.blockingRead(4096);   // method on the resource (len is a number)
output.blockingWriteAndFlush(chunk);

[static] methods are exposed on the resource class and [constructor] makes the class callable with new.

Owned imported resources support [Symbol.dispose]() for deterministic cleanup. Passing one to a WIT own<T> parameter transfers ownership and invalidates the original wrapper. Borrowed wrappers expire when their component call completes and cannot be transferred or disposed as owners. Resources lent to an in-flight import remain alive and cannot be disposed or transferred until that import completes. Ownership is restored for unconsumed stream/future writes and for imported calls cancelled before they start. Await imports using an incoming borrowed resource before returning from its export.

Cancelling Async Imports

A pending async import exposes a cancel() method on its Promise. Calling it requests cancellation of that import. If cancellation succeeds, the original Promise rejects with async import cancelled; a call that finishes first may still resolve normally. The enclosing export remains active, so its catch/finally blocks can await further host operations. Await the original Promise before releasing resources it borrowed. Call cancel() from the same async export invocation that started the import; it has no effect from another export invocation.

Calls that complete immediately may have no cancel method; use optional chaining when completion timing is unknown. Repeated cancellation is harmless, and a saved cancellation function cannot affect a later call that reuses the same runtime handle.

const pending = service.compute(input);
const abort = () => pending.cancel?.();
signal.addEventListener("abort", abort);
try {
    return await pending;
} finally {
    signal.removeEventListener("abort", abort);
    await service.release();
}

Async Exports

Async exports are declared with the async keyword in WIT and implemented as JavaScript async functions:

package example:greeting;

world greeter {
    export greet: async func(name: string) -> string;
}
export async function greet(name) {
    // You can use await here
    await Promise.resolve();
    return `Hello, ${name}!`;
}

Exported resource methods and static methods may also be async. Implement them as async members on the exported JavaScript class; resource constructors remain synchronous:

package example:counter;

interface counter-api {
    resource counter {
        constructor(initial: u32);
        add: async func(value: u32) -> u32;
        create: static async func(initial: u32) -> counter;
    }
}

world counter {
    export counter-api;
}
class Counter {
    constructor(initial) { this.value = initial; }
    async add(value) { this.value += value; return this.value; }
    static async create(initial) { return new this(initial); }
}

export const counterApi = { Counter };

Streams

Streams transfer a sequence of values between components. Lifted WIT streams implement the JavaScript async-iterable protocol, and async or synchronous iterables are lowered to WIT streams automatically. The stream type is inferred from the function parameter or result:

package example:streaming;

world streaming {
    export uppercase: async func(input: stream<string>) -> stream<string>;
}
export async function uppercase(input) {
    return (async function* () {
        for await (const value of input) {
            yield value.toUpperCase();
        }
    })();
}

Async iteration yields one element at a time, except for stream<u8>, which yields bounded Uint8Array chunks to avoid one promise per byte.

Use the wit.Stream factory when direct access to both endpoints is needed. If only one stream type exists in the WIT world, the type may be omitted:

const { readable, writable } = wit.Stream(wit.Stream.U8);
await writable.writeAll(new Uint8Array([1, 2, 3]));
writable.drop();

wit.Stream.from adapts an iterable explicitly and exposes a completion promise:

const source = (async function* () {
    yield "one";
    yield "two";
})();
const { readable, completion } = wit.Stream.from(source, wit.Stream.STRING);

Available type constants (populated from WIT metadata):

WIT typeConstant
stream<u8> / future<u8>wit.Stream.U8 / wit.Future.U8
stream<u32> / future<u32>wit.Stream.U32 / wit.Future.U32
stream<string> / future<string>wit.Stream.STRING / wit.Future.STRING
stream<f64> / future<f64>wit.Stream.F64 / wit.Future.F64

All constructors return { readable, writable }.

Complex element types are also supported. The type constant is generated recursively from the WIT type structure:

// stream<result<string, u32>>
wit.Stream(wit.Stream.RESULT_STRING_U32);

// stream<option<u32>>
wit.Stream(wit.Stream.OPTION_U32);

// stream<tuple<u32, string>>
wit.Stream(wit.Stream.TUPLE_U32_STRING);

// Named record types use their WIT name:
// record point { x: f64, y: f64 }
// stream<point>
wit.Stream(wit.Stream.POINT);

Explicit WIT stream aliases are also exposed as constants:

type prompt-stream = stream<message-chunk>;
wit.Stream(wit.Stream.PROMPT_STREAM);

Use wit.Stream.types or wit.Future.types to discover all available type constants at runtime.

StreamReadable methods:

MethodReturnsDescription
read(count?)Promise<T[]> (or Uint8Array for u8)Read up to count values
next()Promise<IteratorResult<T>>Read the next value (Uint8Array chunk for u8)
return()Promise<IteratorResult<T>>End iteration and release the readable handle
cancelRead()result or undefinedCancel an in-progress read
drop()voidRelease the stream handle
[Symbol.asyncIterator]()StreamReadableConsume the stream with for await...of

StreamWritable methods:

MethodReturnsDescription
write(data)Promise<number>Write values, returns count written
writeOne(value)Promise<number>Write exactly one value, including array-shaped WIT values
writeAll(data)Promise<number>Write all values, retrying as needed
writeIterableItem(value)Promise<boolean>Write one iterable yield, batching matching numeric typed arrays
cancelWrite()result or undefinedCancel an in-progress write
drop()voidRelease the stream handle

Futures

Futures transfer a single value. They work like streams but carry exactly one value:

package example:async-value;

world async-value {
    export compute: async func() -> future<string>;
}
export async function compute() {
    const { readable, writable } = wit.Future();

    // Write the value (fire-and-forget; completes when reader reads)
    writable.write("computed result");

    return readable;
}

Future type constants follow the same pattern: wit.Future.U32, wit.Future.STRING, etc.

Future handles are deliberately not thenable: use await readable.read() to obtain the payload. This keeps return readable in an async export from implicitly consuming the future, and preserves nested future handles. Repeated read() calls share the same Promise; a cancelled read rejects that Promise and allows a subsequent read to retry.

wit.Future.from(value, type) adapts a value or Promise and returns { readable, completion }. Return its readable from an async export rather than returning the payload Promise directly, which JavaScript would await.

FutureReadable methods:

MethodReturnsDescription
read()Promise<T>Read the single value
cancelRead()result or undefinedCancel an in-progress read
drop()voidRelease the future handle

FutureWritable methods:

MethodReturnsDescription
write(value)Promise<boolean>Write the value, returns success
cancelWrite()result or undefinedCancel an in-progress write
drop()voidRelease the future handle

Resource Cleanup

Owned imported resources, stream endpoints, and future endpoints support Explicit Resource Management via Symbol.dispose. In environments that support using:

{
    const stream = wit.Stream();
    using writable = stream.writable;
    using readable = stream.readable;
    // Each endpoint is disposed when leaving scope.
}

The factory's { readable, writable } pair is not itself disposable. Complete an endpoint's pending operation (or cancel it and await its settlement) before leaving its using scope or calling .drop(). Host-requested task cancellation retains pending ABI buffers until the host acknowledges each operation's completion or cancellation.

Otherwise, call .drop() explicitly to release handles.

Snapshot Initialization

Snapshot creation uses a 1 GiB host-call copy budget to accommodate large module graphs. This allowance belongs to the build-time initializer; execution hosts continue to choose their own budgets for instantiated components.

Node.js API

The npm package exposes both a CLI and a programmatic API.

CLI

npx componentize-qjs --wit hello.wit --js hello.js -o hello.wasm

(Or, if you installed the package globally, just componentize-qjs ....)

Usage

import { componentize } from "componentize-qjs";

const { component } = await componentize({
    witPath: "hello.wit",
    jsSource: "export function greet(name) { return `Hello, ${name}!`; }",
    optSize: true,
});
// component is a Buffer containing the WebAssembly component bytes

Runtime selection is available through optSize, sync, runtime, or runtimeBytes. optSize and sync may be combined to select the non-async opt-size runtime, but neither can be combined with a custom runtime/runtimeBytes. The runtime option is a path to a custom QuickJS runtime Wasm module.

Acknowledgments

This project builds on ideas and code from:

License

Licensed under Apache-2.0.

版本与发布记录

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 共发布 12 个版本,首次发布于 2026/05/06;过去 12 个月发布了 12 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.0latest2026/11/07今天
0.5.02026/10/2216 天前
0.4.52026/09/161 个月前
0.4.42026/09/022 个月前
0.4.32026/07/263 个月前
0.4.22026/07/103 个月前

关于 @andreiltd/componentize-qjs-binding-linux-arm64-gnu 的常见问题

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 是什么?

Convert JavaScript to WebAssembly components using QuickJS

如何安装 @andreiltd/componentize-qjs-binding-linux-arm64-gnu?

在项目目录运行 npm install @andreiltd/componentize-qjs-binding-linux-arm64-gnu,或使用 pnpm add / yarn add / bun add。

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/andreiltd__componentize-qjs-binding-linux-arm64-gnu 是否存在。

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 可以商用吗?

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 的下载量有多少?

根据 npm 官方下载统计,@andreiltd/componentize-qjs-binding-linux-arm64-gnu 在 2026/10/06 – 2026/11/04 这 30 天内被下载 17,407 次,日均约 580 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@andreiltd/componentize-qjs-binding-linux-arm64-gnu 还在维护吗?

最新版本 0.6.0 发布于 2026/11/07(今天);至今共发布 12 个版本,其中过去 12 个月发布了 12 个;首次发布于 2026/05/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。