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.