Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Perry backend

jco componentize --backend perry compiles JavaScript or TypeScript with a Perry CLI selected by path or installed in the project. Perry compiles the original module graph to a WASI Preview 2 command component. Jco does not bundle the source, erase its TypeScript types, or inject its Node compatibility adapters. Supported imports such as node:fs use Perry’s WASI implementation.

This backend is experimental. Perry does not yet generate bindings for custom WIT interfaces. It owns the component’s world and exports wasi:cli/run@0.2.x; your program runs from its top-level entry module. Omit --wit and --world-name when componentizing with Perry. Those options, Jco bundling/Node adapter options, and options specific to StarlingMonkey or QuickJS are rejected.

Install and select a compiler

Perry is not a dependency of Jco. To use an unreleased WASI-enabled source build directly, supply its executable path; no npm wrapper is needed:

jco componentize src/main.ts --backend perry \
  --backend-perry-cli-path /path/to/perry/target/debug/perry -o component.wasm

The path can also name a .js, .cjs, or .mjs launcher. Relative CLI paths are resolved from the directory where you invoke Jco, independently of --cwd. The explicit path bypasses npm resolution. --backend-perry-cli-path and --backend-perry-package cannot be used together.

For a project-installed compiler, install your chosen WASI-enabled version and commit your package manager’s lockfile:

npm install --save-dev @perryts/perry@<version>
jco componentize src/main.ts --backend perry -o component.wasm

The compiler must support perry compile --target wasi. The published @perryts/perry@0.5.1520 release predates that support. Installing it alone does not provide a working WASI toolchain. Follow Perry’s standalone WASI build instructions for a compiler built with the target-wasi Cargo feature, the matching WASI runtime archive, and wasi-sdk. The upstream configuration uses LLVM 22 and wasi-sdk 34. Configure WASI_SDK_PATH and, for an out-of-tree runtime, PERRY_RUNTIME_DIR as described upstream; Jco inherits the compiler environment. The browser-oriented --target wasm is a different target.

The filesystem workflow below was validated on Linux with Perry source commit 3945beb23e (CLI version 0.5.1659), its matching WASI runtime, LLVM 22.1.8, wasi-sdk 34, Rust nightly-2026-10-08, and Node 24.21.0. Use the WASI instructions at that commit when reproducing the source build; pin both compiler and runtime to the same revision. Version 0.5.1659 alone does not identify this source snapshot.

Without an explicit CLI path, Jco resolves @perryts/perry/package.json starting at the entry module’s directory, then invokes the package’s bin.perry entry. Normal Node package resolution finds the nearest enclosing project’s installation, including workspace symlinks. It does not search the global executable PATH. --cwd <directory> selects a different compiler project and child-process working directory. Source and output paths remain relative to the directory where you invoke Jco.

To select an npm alias or a locally packaged WASI compiler, use --backend-perry-package:

npm install --save-dev my-perry@npm:@perryts/perry@<version>
jco componentize src/main.ts --backend perry --backend-perry-package my-perry -o component.wasm

A local package must expose its package.json and declare "bin": { "perry": "path/to/cli" } (a string bin is also accepted). The entry can be a native executable or a .js, .cjs, or .mjs launcher. There is currently no importable Perry compiler API; Jco keeps the backend name independent of that implementation detail.

Before compiling your source, Jco checks WASI code generation by compiling a tiny isolated module with --target wasi --no-link and verifying that it produces a Wasm object. This detects compilers that silently fall back to a native target; Perry’s help/version strings do not reliably identify WASI-enabled builds. Failed checks include the selected CLI path, compiler diagnostics and setup guidance. The check has a 30-second timeout and does not link the WASI runtime. The actual build still needs the matching runtime and SDK, and its output is validated as a WASI P2 command before Jco writes the destination file.

Generate declarations, compile, and run on Node.js

Use a WASI P2 command WIT package to generate declarations. A convenient source is Jco’s bundled snapshot, copied by the scaffold command:

jco scaffold perry-app --wit builtin:wasi-command@0.2.x --target nodejs
cd perry-app
# Install Jco; select your compiler by explicit path below or install its package.
npm install @bytecodealliance/preview2-shim
jco guest-types wit --world-name wasi:cli/command@0.2.12 -o types

guest-types generates .d.ts declarations, not Perry runtime bindings. They describe the WASI interfaces for reference and type checking; they do not make import "wasi:filesystem/types" or custom guest exports compile in Perry. The compiler and its SDK determine the exact WASI patch versions in the output; inspect them with jco wit component.wasm. Use a top-level Perry program in place of the scaffold’s generated guest implementation and build scripts.

For example, save this as source.ts:

import { readFileSync, statSync } from "node:fs";

const input: string = readFileSync("/data/input.txt", "utf8");
console.log(`Perry size: ${statSync("/data/input.txt").size}`);
console.log(`Perry read: ${input}`);

Build the component, then transpile it with the ordinary synchronous WASI bindings:

jco componentize source.ts --backend perry \
  --backend-perry-cli-path /path/to/perry -o component.wasm
jco transpile component.wasm --bindgen-enable-wasm-exnref -o transpiled
mkdir -p data
printf 'hello from the filesystem\n' > data/input.txt

In an ESM project ("type": "module" in package.json), save run.mjs:

import { resolve } from "node:path";
import { filesystem } from "@bytecodealliance/preview2-shim";

filesystem._setPreopens({ "/data": resolve("data") });
const { run } = await import("./transpiled/component.js");
run.run();

Run node run.mjs. The program reads the contents and size of a file inside the preopened data/ directory. This flow uses the Preview 2 shim and does not require JSPI or Node’s experimental WASI API. Perry’s WASI runtime uses WebAssembly exception handling, so enable it in Jco with --bindgen-enable-wasm-exnref and use a Node version that supports the instructions emitted by your compiler. This is a transpiler option, not an experimental Node flag.

File writes have a known compatibility limitation with preview2-shim@0.30.0: the tested Perry runtime requests the WASI mutate-directory descriptor flag when opening a regular file for writing, which the shim rejects as unsupported. The workspace shim supports an explicit compatibility option for this behavior (not available in 0.30.0). With a build containing that support, configure the host before importing the transpiled component:

filesystem._setPreopens({ "/data": resolve("data") }, { enableMutateDirectory: true });

This gives the preopened directory mutation capability and preserves capability checks on its descendants. The flag is accepted and cleared on regular files; file writes still require write permission. See the shim’s directory mutation options for details. The example exercises reading and metadata and remains usable with 0.30.0.

The repository contains a filesystem example with build scripts and a Node.js demo.