@bytecodealliance/componentize-js

中文文档
0.23.0发布于 2026/09/21TypeScriptES Module构建来源已验证近 30 天下载 303.7万

ESM -> WebAssembly Component creator, via a SpiderMonkey JS engine embedding

原始使用文档
文档目录19 节
  1. Overview
  2. Example
  3. Explainer
  4. Wizer Pre-Initialization
  5. SpiderMonkey Embedding
  6. Weval AOT Compilation
  7. Platform APIs
  8. Usage
  9. Custom wizer binary
  10. Async Support
  11. CLI
  12. Features
  13. Using StarlingMonkey's fetch-event
  14. API
  15. Contributing
  16. Building
  17. Testing
  18. Running a specific test
  19. Contribution

ComponentizeJS

ESM -> WebAssembly Component creator,
via a SpiderMonkey JS engine embedding

A Bytecode Alliance project

build status

Overview

Provides a Mozilla SpiderMonkey embedding that takes as input a JavaScript source file and a WebAssembly Component WIT World, and outputs a WebAssembly Component binary with the same interface.

Note: This is an experimental project, no guarantees are provided for stability or support and breaking changes may be made in future.

Example

See the end-to-end example workflow for creating a JS component and running it in Wasmtime or Node.js.

Explainer

For background on the concepts involved, see https://bytecodealliance.org/articles/making-javascript-run-fast-on-webassembly.

The goal of this project specifically is to provide a comprehensive dynamic bindings system for creating arbitrary WebAssembly Components from JavaScript. That is, to provide full flexibility over the resultant JS environment and WIT World.

Wizer Pre-Initialization

Adaption follows the standard Wizer technique in pre-initializing a snapshot of the runtime against the source and bindings.

The snapshotting process executes the JS engine initialization, globals and parsing and compiling of the source code. Currently we also evaluate the top-level of the source so that the executed exports of the top-level ES module are provided already initialized.

As a result, at runtime - only the bytecode is being executed, without any initialization costs. This makes on-demand Wasm execution of JS incredibly fast.

SpiderMonkey Embedding

As a dynamic language with quirks, JavaScript cannot be compiled directly into bytecode without including a comprehensive ECMA-262 spec-compliant runtime engine. Componentization of JavaScript thus involves embedding the JS runtime engine into the component itself.

SpiderMonkey is chosen here as a JS engine with first-class WASI build support, using an embedding of the StarlingMonkey Wasm engine. The total embedding size is around 8MB.

One of the security benefits of the component model is complete code isolation apart from the shared-nothing code boundaries between components. By fully encapsulating the engine embedding for each individual component, this maintains comprehensive per-component isolation.

As more components are written in JavaScript, and there exist scenarios where multiple JS components are communicating in the same application, the plan for optimization here is to share the SpiderMonkey engine embedding between them. This can be done without breaking the shared-nothing semantics by having the engine itself loaded as a shared library of the components. Sharing functions via same SpiderMonkey build, not memory.

Establishing this initial prototype as a singular flexible engine foundation that can be turned into a shared library is therefore the focus for this project.

Weval AOT Compilation

Weval ahead-of-time (AOT) compilation can be enabled to improve runtime performance by pre-compiling JavaScript inline caches.

To enable AOT compilation, set the enableAot: true option or use the --aot CLI flag.

AOT compilation can also be configured with the following options:

OptionTypeExampleDescription
aotMinStackSizeBytes`numberNumberbigint

Custom weval binary for AOT

To use a custom (pre-downloaded) weval binary, set the wevalBin option to the path to your desired weval binary.

Platform APIs

The following APIs are available:

  • Legacy Encoding: atob, btoa, decodeURI, encodeURI, decodeURIComponent, encodeURIComponent
  • Streams: ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultReader, ReadableStreamDefaultController, ReadableByteStreamController, WritableStream ByteLengthQueuingStrategy CountQueuingStrategy, TransformStream
  • URL: URL, URLSearchParams
  • Console: console
  • Performance: Performance
  • Task: queueMicrotask, setInterval setTimeout clearInterval clearTimeout
  • Location: WorkerLocation, location
  • Encoding: TextEncoder, TextDecoder, CompressionStream, DecompressionStream
  • Structured Clone: structuredClone
  • Fetch: fetch, Request, Response, Headers
  • Forms, Files, and Blobs: FormData, MultipartFormData, File, Blob
  • Crypto: SubtleCrypto, Crypto, crypto, CryptoKey
  • Abort: AbortController, AbortSignal

Usage

Install and run as a Node.js library:

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

const { component } = await componentize(`
  import { log } from 'local:hello/logger';

  export function sayHello (name) {
    log(\`Hello \${name}\`);
  }

`, `
  package local:hello;
  interface logger {
    log: func(msg: string);
  }
  world hello {
    import logger;
    export say-hello: func(name: string);
  }
`);

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

See types.d.ts for the full interface options.

The component itself can be executed in any component runtime, see the example for an end to end workflow in Wasmtime.

Custom wizer binary

To use a custom (pre-downloaded) wizer binary, set the wizerBin option to the path to your desired wizer binary.

Async Support

To support asynchronous operations, all functions may optionally be written as sync or async functions, even though they will always be turned into sync component functions.

For example, to use fetch which requires async calls, we can write the same example component using an async function:

export async function sayHello (name) {
  const text = await (await fetch(`http://localhost:8080/${name}`)).text();
  console.log(text);
}

ComponentizeJS will automatically resolve promises returned by functions to syncify their return values, running the event loop within the JS component to resolution.

This asynchrony is only supported for exported functions - imported functions can only be synchronous pending component-model-level async support.

CLI

ComponentizeJS can be used as a CLI directly, or via the jco componentize command:

Direct use

npm install -g @bytecodealliance/componentize-js

Example:

componentize-js --wit wit -o component.wasm source.js

See jco componentize --help for more details.

Use via jco componentize

npm install -g @bytecodealliance/jco @bytecodealliance/componentize-js

Example:

jco componentize source.js --wit wit -o component.wasm

See jco componentize --help for more details.

Features

The set of enabled features in the engine can be customized depending on the target world and expected capabilities.

The default set of features includes:

  • 'stdio': Output to stderr and stdout for errors and console logging, depends on wasi:cli and wasi:io.
  • 'random': Support for cryptographic random, depends on wasi:random. When disabled, random numbers will still be generated but will not be random and instead fully deterministic.
  • 'clocks': Support for clocks and duration polls, depends on wasi:clocks and wasi:io. When disabled, using any timer functions like setTimeout or setInterval will panic.
  • 'http': Support for outbound HTTP via the fetch global in JS.
  • 'fetch-event': Support for fetch based incoming request handling (i.e. addEventListener('fetch', ...))

Setting disableFeatures: ['random', 'stdio', 'clocks', 'http', 'fetch-event'] will disable all features creating a minimal "pure component", that does not depend on any WASI APIs at all and just the target world.

Note that pure components will not report errors and will instead trap, so that this should only be enabled after very careful testing.

Note that features explicitly imported by the target world cannot be disabled - if you target a component to a world that imports wasi:clocks, then disableFeatures: ['clocks'] will not be supported.

Note that depending on your component implementation, some features may be automatically disabled. For example, if using wasi:http/incoming-handler manually, the fetch-event cannot be used.

Using StarlingMonkey's fetch-event

The StarlingMonkey engine provides the ability to use fetchEvent to handle calls to wasi:http/[email protected]#handle.

When targeting worlds that export wasi:http/[email protected] the fetch event will automatically be attached. Alternatively, to override the fetch event with a custom handler, export an explicit incomingHandler or 'wasi:http/[email protected]' object. Using the fetchEvent requires enabling the http feature.

[!WARNING] If using fetch-event, ensure that you do not manually import (i.e. exporting incomingHandler from your ES module).

Modules that export incomingHandler and have the http feature enabled are assumed to be using wasi:http manually.

API

export function componentize(opts: {
  /**
   * The path to the file to componentize.
   *
   * This file must be a valid JavaScript module, and can import other modules using relative paths.
   */
  sourcePath: string;
  /**
   * Path to WIT file or folder
   */
  witPath?: string;
  /**
   * Target world name for componentization
   */
  worldName?: string;
  /**
   * Path to custom ComponentizeJS engine build to use
   */
  engine?: string;
  /**
   * Path to custom weval cache to use
   */
  aotCache?: string;
  /**
   * Enable AoT using weval
   */
  enableAot?: boolean;
  /**
   * Use a pre-existing path to the `weval` binary, if present
   */
  wevalBin?: string;
  /**
   * Use a pre-existing path to the `wizer` binary, if present
   */
  wizerBin?: string;
  /**
   * Path to custom Preview2 Adapter
   */
  preview2Adapter?: string;
  /**
   * Disable WASI features in the base engine
   * Disabling all features results in a pure component with no WASI dependence
   *
   * - stdio: console.log(), console.error and errors are provided to stderr
   * - random: Math.random() and crypto.randomBytes()
   * - clocks: Date.now(), performance.now()
   * - http: fetch() support
   *
   */
  disableFeatures?: ('stdio' | 'random' | 'clocks' | 'http' | 'fetch-event')[];
  /**
   * Enable WASI features in the base engine
   * (no experimental subsystems currently supported)
   */
  enableFeatures?: [];
  /**
   * Pass environment variables to the spawned Wizer or Weval Process
   * If set to true, all host environment variables are passed
   * To pass only a subset, provide an object with the desired variables
   */
  env?: boolean | Record<string, string>;
  /**
   * Runtime arguments to provide to the StarlingMonkey engine initialization
   * (see https://github.com/bytecodealliance/StarlingMonkey/blob/main/include/config-parser.h)
   */
  runtimeArgs?: string;
  /**
   * Use a debug build
   * Note that the debug build only includes the names section only for size optimization, and not DWARF
   * debugging sections, due to a lack of support in Node.js for these debugging workflows currently.
   */
  debugBuild?: boolean;
  /**
   * Debug options
   */
  debug?: {
    /**  Whether to enable bindings-specific debugging */
    bindings?: boolean;
    /**  Path to debug bindings to use */
    bindingsDir?: string;
    /**  Whether to enable binary-specific debugging */
    binary?: boolean;
    /**  Path    to enable binary-specific debugging */
    binaryPath?: string;
    /** Whether to enable wizer logging */
    wizerLogging: false;
  }
}): Promise<{
  /**
   * Component binary
   */
  component: Uint8Array;
  /**
   * Used guest imports in JavaScript (excluding those from StarlingMonkey engine)
   */
  imports: [[string, string]][];
  /**
   * Debugging output (only present if enabled)
   */
  debug?: {
    /** Whether bindings debugging was enabled */
    bindings?: boolean;
    /** Work directory used during processing */
    workDir?: string;
    /** Whether binary debugging was enabled */
    binary?: boolean;
    /** Path to the produced binary */
    binaryPath?: string;
  };
}>

Converts a JS source into a component binary.

Imports provides the list of used guest imports only, while the StarlingMonkey engine may pull in additional imports. Direct component analysis should be used to correctly infer the real imports list.

Contributing

To contribute, you'll need to set up the following:

  • git submodule update --init --recursive to update the submodules.
  • Stable Rust with the wasm32-unknown-unknown and wasm32-wasi targets installed.
  • wasi-sdk-20.0 installed at /opt/wasi-sdk/

Building

Building and testing the project can be performed via NPM scripts (see package.json):

npm install
npm run build

Before being able to use componentize-js with AOT support (ex. via npm link, from jco), you'll need to run:

npm run build:weval

(npm run build will also execute the weval build)

This will produce lib/starlingmonkey_embedding_weval.wasm and lib/starlingmonkey_ics.wevalcache.

To clean up a local installation (i.e. remove the installation of StarlingMonkey):

npm run clean

Testing

To run all tests:

npm run test

To run tests with AOT (weval) enabled:

npm run test:weval

Running a specific test

To run a specific test suite, you can pass an argument to vitest:

npm run test -- test/wasi.js

License

This project is licensed under the Apache 2.0 license with the LLVM exception. See LICENSE for more details.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.

版本与发布记录

@bytecodealliance/componentize-js 共发布 64 个版本,首次发布于 2023/02/21;过去 12 个月发布了 7 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.23.0latest2026/09/211 个月前
0.22.02026/07/313 个月前
0.21.02026/05/205 个月前
0.21.0-rc.0rc2026/05/195 个月前
0.20.02026/04/146 个月前
0.20.0-rc.02026/04/146 个月前

关于 @bytecodealliance/componentize-js 的常见问题

@bytecodealliance/componentize-js 是什么?

ESM -> WebAssembly Component creator, via a SpiderMonkey JS engine embedding

如何安装 @bytecodealliance/componentize-js?

在项目目录运行 npm install @bytecodealliance/componentize-js,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 componentize-js,可以用 npx componentize-js 直接运行。

@bytecodealliance/componentize-js 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bytecodealliance/componentize-js 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bytecodealliance/componentize-js 可以商用吗?

@bytecodealliance/componentize-js 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@bytecodealliance/componentize-js 的下载量有多少?

根据 npm 官方下载统计,@bytecodealliance/componentize-js 在 2026/10/09 – 2026/11/07 这 30 天内被下载 3,036,532 次,日均约 101,218 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bytecodealliance/componentize-js 还在维护吗?

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