ComponentizeJS Documentation

repository·main·Indexed 18 days ago

https://github.com/bytecodealliance/componentizejs

A tool for creating WebAssembly Components from JavaScript ESM source files by embedding a SpiderMonkey JS engine. It utilizes the Wizer technique for fast pre-initialization and supports WIT (WebAssembly Interface Types) mapping, async exported functions, and AOT compilation via weval. The package includes a Node.js API, a CLI, and the spidermonkey-embedding-splicer for stubbing WASI imports and splicing bindings.

Tokens
10.4K
Snippets
38
Records
47
Agent score
63%

What's inside ComponentizeJS

  1. How async support works in ComponentizeJS

    main

    ComponentizeJS allows you to write exported functions as async functions, even though they are converted into synchronous component functions at the Wasm level.

    When an exported function returns a Promise (e.g., when using fetch), ComponentizeJS automatically resolves the promise by running the event loop within the JS component until the value is ready.

    Note: This asynchrony is only supported for exported functions. Imported functions must remain synchronous as component-model-level async support is not yet available.

    export async function sayHello (name) {
      const text = await (await fetch(`http://localhost:8080/${name}`)).text();
      console.log(text);
    }
  2. Use StarlingMonkey's `fetch-event` for HTTP handling

    main

    When targeting worlds that export wasi:http/incoming-handler@0.2.0, the fetch-event is automatically attached. This allows you to handle incoming requests using addEventListener('fetch', ...).

    Requirements:

    • The http feature must be enabled.

    Warning: If you use fetch-event, do not manually export an incomingHandler or a 'wasi:http/incoming-handler@0.2.0' object from your ES module. If both are present, the engine assumes you are managing wasi:http manually.

  3. Create a WebAssembly component with componentize()

    main

    To create a WebAssembly component from JavaScript, you need a .wit file defining the interface (world) and a .js file that implements that interface. Use the componentize function from @bytecodealliance/componentize-js to transform the JavaScript source and WIT definition into a .wasm component.

    1. Define your interface in a .wit file.
    2. Implement the interface in a .js file.
    3. Use the componentize API to generate the component buffer.
    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);
  4. Run a ComponentizeJS component in Node.js using jco

    main

    To execute a component in Node.js, you must transpile the .wasm component into JavaScript using jco. This requires the @bytecodealliance/preview2-shim to handle WASI imports.

    1. Install jco globally: npm install -g @bytecodealliance/jco.
    2. Install the shim: npm install @bytecodealliance/preview2-shim.
    3. Transpile the component using the --map flag to redirect wasi-* imports to the experimental JS WASI shim: jco transpile <component.wasm> -o <output_dir> --map 'wasi-*=@bytecodealliance/preview2-shim/*'.
    4. Ensure your output directory has a package.json with "type": "module" to support ES modules.
    5. Execute the component using a Node.js import.
    # Install tools
    npm install -g @bytecodealliance/jco
    npm install @bytecodealliance/preview2-shim
    
    # Transpile
    jco transpile hello.component.wasm -o hello --map 'wasi-*=@bytecodealliance/preview2-shim/*'
    
    # Run
    node -e "import('./hello/hello.component.js').then(m => console.log(m.hello('ComponentizeJS')))"
  5. Build a Javascript WebAssembly component

    main

    To build a WebAssembly component from a Javascript (ES) module, you must first ensure your module conforms to a WebAssembly Interface Types (WIT) interface. The JS exports are implicitly mapped to the WIT world exports.

    For example, if your hello.wit defines:

    package local:hello;
    
    world component {
      export hello: func(name: string) -> string;
    }

    Your hello.js should export a function with the same name:

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

    To build the component:

    1. Navigate to the guest directory.
    2. Install dependencies using npm install.
    3. Run the build process using node componentize.js or the pre-configured npm run build script.
    npm install
    npm run build
  6. Build and test componentize-js

    main

    To build the project locally using NPM:

    npm install
    npm run build

    If you need to use componentize-js with AOT support (for example, via npm link or jco), you must also build weval:

    npm run build:weval

    This produces lib/starlingmonkey_embedding_weval.wasm and lib/starlingmonkey_ics.wevalcache.

    To clean up a local installation and remove StarlingMonkey:

    npm run clean
    npm install
    npm run build
    npm run build:weval
  7. Install ComponentizeJS

    main

    You can install ComponentizeJS as a Node.js library for programmatic use, or install it globally to use it as a CLI tool.

    As a Node.js library:

    npm install @bytecodealliance/componentize-js

    As a global CLI tool:

    npm install -g @bytecodealliance/componentize-js

    Via jco:

    npm install -g @bytecodealliance/jco @bytecodealliance/componentize-js
    npm install @bytecodealliance/componentize-js
  8. Run a ComponentizeJS component in Wasmtime

    main

    To run the generated .wasm component in Wasmtime, you must set up a Rust host environment.

    1. Configure your Cargo.toml as shown in the examples/hello-world/host/ directory.
    2. Implement the host logic in src/main.rs following the examples/hello-world/host/src/main.rs template.
    3. Build and run the host binary using cargo build --release.
    cargo build --release
    ./target/release/wasmtime-test
  9. How interface names are transformed

    main

    The project uses interface_name_from_string to derive JavaScript-friendly names from Wasm interface identifiers.

    1. It extracts the part after the last /.
    2. It identifies the version/alias part after the @ symbol.
    3. The base name is converted to lowerCamelCase.
    4. If a version/alias exists (e.g., name@version), the resulting name is formatted as {alias}_{version_with_underscores} (where dots and dashes in the version are replaced by underscores).
    fn interface_name_from_string(name: &str) -> Option<String> {
        // ... implementation details ...
    }
  10. Customize enabled engine features

    main

    You can control the capabilities of the embedded SpiderMonkey engine using the disableFeatures option. This allows you to create 'pure components' that do not depend on WASI APIs.

    Default Features

    • 'stdio': Output to stderr/stdout (depends on wasi:cli and wasi:io).
    • 'random': Cryptographic random (depends on wasi:random). Note: If disabled, random numbers become fully deterministic.
    • 'clocks': Clocks and duration polls (depends on wasi:clocks and wasi:io). Note: If disabled, setTimeout or setInterval will panic.
    • 'http': Outbound HTTP via the fetch global.
    • 'fetch-event': Incoming request handling via addEventListener('fetch', ...).

    Disabling Features

    To create a minimal component with no WASI dependencies, use: disableFeatures: ['random', 'stdio', 'clocks', 'http', 'fetch-event'].

    Constraints:

    • Pure components will trap instead of reporting errors.
    • You cannot disable a feature that is explicitly required by the target WIT world (e.g., if the world imports wasi:clocks, you cannot disable 'clocks').
    • Some features may be automatically disabled based on implementation (e.g., using wasi:http/incoming-handler manually prevents using fetch-event).