# Javy: compiling JavaScript to WebAssembly with a CLI you can pipe into wasmtime

> Javy is a Bytecode Alliance toolchain that turns a JavaScript file into a Wasm module with an embedded JS runtime. The default build is large; dynamic linking is where the small binaries come from.

**bytecodealliance/javy** — JS to WebAssembly toolchain

- Repository: https://github.com/bytecodealliance/javy
- Stars: 2,751 · Forks: 135
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/bytecodealliance-javy

## The problem Javy solves: JavaScript as a Wasm module, not as a hosted script

Most ways of running JavaScript in a WebAssembly host end with the host owning the runtime. Javy inverts that. The CLI takes a JavaScript file and produces a .wasm binary that contains the runtime and your code together, so the artifact you hand to a Wasm engine is the program. The README frames the project as a "JavaScript to WebAssembly toolchain" and states the goal plainly: run your JavaScript on WebAssembly, executing it in a WebAssembly embedded JavaScript runtime.

The audience is narrow and identifiable. If you are building a plugin system, an edge function platform, or any product where tenant code must run inside a sandbox the host controls, a self-contained Wasm module with a JS entry point is a useful unit of deployment. The README's example is exactly that shape: a function that reads JSON from stdin, transforms it, and writes JSON to stdout. There is no server, no event loop, and no module resolution to configure.

Size is the other half of the pitch. The README claims dynamic linking can produce modules in the 1 to 16 KB range, and states that the default static linking produces modules of at least 869 KB. That is a wide gap, and it is the single most consequential number in the project. Treat 869 KB as the floor for the default path, not an average.

## How the build pipeline works: Rust CLI, Wasmtime, and a Javy.IO bridge

The repository is a Cargo workspace, and the crate list tells you where the work happens. crates/cli holds the command-line entry point, crates/codegen handles turning JavaScript into the Wasm output, crates/javy is the runtime side, and crates/runner executes modules for local testing. The workspace pins wasmtime 48.0.1 and wasmtime-wasi 48.0.1 as dependencies, so the embedded engine is Wasmtime rather than a bespoke interpreter. wasm-opt 0.116.1 and walrus 0.26.4 appear in the workspace dependencies, which is consistent with a pipeline that optimizes and rewrites the produced module rather than emitting bytes and stopping.

The data flow visible in the README example is deliberately minimal. Your code calls Javy.IO.readSync with file descriptor 0 to pull bytes from stdin, decodes them with TextDecoder, and parses JSON. On the way out it encodes JSON with TextEncoder and calls Javy.IO.writeSync with file descriptor 1. The example loops in 1024-byte chunks until readSync returns 0, accumulating into a single Uint8Array before parsing. That is the whole contract: bytes in on fd 0, bytes out on fd 1, JSON as the convention rather than a requirement.

The plugin crates are the part worth flagging. Cargo.toml lists crates/plugin, crates/plugin-api, crates/plugin-processing, and two test plugin fixtures for wasip1 and wasip2. The Makefile defines separate lint and test targets for native, wasip1 and wasip2, selected through a [package.metadata.javy] targets key in each crate's manifest. So the project is not only compiling your JavaScript; it has a plugin mechanism with its own API surface and its own compile targets. The README does not document that API, and points to ./docs/index.md instead.

## Installing Javy and running a first module end to end

The README does not describe a package manager install. It states that pre-compiled binaries of the Javy CLI can be found on the releases page, so the supported path is downloading a binary from the GitHub releases rather than running cargo install or npm install. The repository does contain an npm/ directory, but the README does not document an npm install flow, so do not assume one.

Once the binary is on your PATH, the build command is a single invocation. The README gives this example, which reads index.js and writes the module to a destination directory:

```bash
javy build index.js -o destination/index.wasm
```

Running javy --help lists the available commands; the README points there for anything beyond build.

The JavaScript side follows the stdin and stdout convention. This is the core of the README example, trimmed to the parts that matter for a first run:

```javascript
const input = readInput();
const result = foo(input);
writeOutput(result);

function foo(input) {
    return { foo: input.n + 1, newBar: input.bar + "!" };
}
```

readInput and writeOutput are the helpers from the README that wrap Javy.IO.readSync on file descriptor 0 and Javy.IO.writeSync on file descriptor 1. If you copy the full example, both come along.

The module runs under any WebAssembly engine. The README demonstrates it with wasmtime, piping JSON in and reading JSON back:

```bash
echo '{ "n": 2, "bar": "baz" }' | wasmtime index.wasm
```

The expected output is {"foo":3,"newBar":"baz!"}. If you see that, the toolchain, the embedded runtime, and your stdin handling are all working.

## The 869 KB default and the dynamic linking trade-off

The README is unusually direct about cost. Static linking is the default, and modules are at least 869 KB. Dynamic linking brings modules into the 1 to 16 KB range. Those two sentences describe a real architectural fork, and the README does not walk through what dynamic linking requires of the host.

That is the limitation to take seriously. A 1 KB module is only 1 KB because something else supplies the runtime. If your deployment target cannot provide that shared piece, you are back to the static build and its size floor. The README does not document the dynamic linking setup, so the practical question of what the host must load is answered in ./docs/index.md or not at all. Anyone planning around the 16 KB figure should confirm the host-side requirement before committing to it.

There is a second constraint in the example itself. The stdin reader allocates a fresh 1024-byte Uint8Array on every loop iteration, then copies chunks into a final buffer. For small JSON payloads this is fine. For large inputs it is a lot of short-lived allocation inside a sandboxed runtime, and the README does not discuss buffer sizing or streaming. It is sample code, not a library, but it is the sample most people will start from.

## Where Javy is the wrong choice

Javy does not run Node.js. There is no documented fs, http, or child_process, no npm resolution, and no package.json step in the README. If your code depends on a library that reaches into Node built-ins or ships a native addon, Javy will not carry it. The I/O surface documented here is file descriptors 0 and 1 through Javy.IO, and that is the boundary.

Debugging is the other gap. When a module fails under wasmtime you get whatever the engine and your own writeOutput calls reveal. The README does not describe a debugger, source maps, or a stack trace mapping back to your JavaScript. Teams used to breakpoints in Chrome DevTools should expect to instrument by printing JSON to stdout.

Startup and per-invocation cost also deserve scrutiny. A module that embeds a JavaScript runtime is not a lightweight function in the way a hand-written Rust Wasm module is. The README makes no claim about cold start or throughput, and none should be inferred from the size figures. If your workload is a high-frequency call path rather than a plugin boundary, measure before you commit.

## Componentize-js and the alternative approach

Componentize-js is the comparison people reach for, and the difference is architectural rather than cosmetic. Componentize-js targets the WebAssembly Component Model: it produces a component with typed interfaces, so the host calls your JavaScript through a declared WIT interface instead of through byte streams. Javy's documented contract is the opposite. Your program owns its entry point, reads raw bytes from stdin, and writes raw bytes to stdout. That makes Javy simpler to reason about and easier to run under a plain Wasm engine like wasmtime, and it makes typed host-to-guest calls something you build yourself on top of the byte stream.

If you need the host to invoke named exports with structured arguments, a component model toolchain matches that shape more directly. If you need a single self-contained module that behaves like a command-line program, Javy's model is the smaller conceptual surface. The two are not interchangeable, and picking one is mostly a decision about who defines the interface: the host, or your JavaScript.

## Maintenance, releases and licence

The repository is not archived, and the last push was on 2026-09-02. Recent releases are v9.1.0 on 2026-07-30, v9.0.0 on 2026-06-12, and v8.1.1 on 2026-04-06. The workspace version in Cargo.toml is 9.1.0, which matches the most recent tag. Cadence over that window is roughly every six to eight weeks, and the v9.0.0 to v9.1.0 step suggests minor releases carry real changes rather than only fixes. Note that the workspace dependency on the javy crate is pinned at version 8.1.1-alpha.1, which trails the workspace version; if you depend on that crate directly, check what the tag actually ships before assuming parity.

The licence is Apache-2.0 WITH LLVM-exception according to the workspace manifest, and the repository carries LICENSE.md. The LLVM exception is the part worth reading rather than skimming: it changes how the licence interacts with code you combine with the project. That is a question for your own legal review, not something to settle from a manifest line.

Upgrade cost is mostly the pinned toolchain. rust-toolchain.toml and pinned-nightly-version exist at the top level, and the workspace pins wasmtime at 48.0.1 and wasm-opt at 0.116.1. Building from source means matching those pins. Using the released CLI binary avoids that, but it also means your module's runtime version is whatever the release embedded, so a Wasmtime upgrade on your host and a Javy upgrade on your build machine are two separate tracks that you have to keep aligned yourself.

## Conclusion

Javy fits teams that already ship WebAssembly and want a JavaScript authoring layer, especially where a 1 to 16 KB dynamically linked module matters. It is the wrong tool if you need Node.js APIs, npm packages with native bindings, or a runtime you can debug with browser DevTools, because the documented surface is JSON over stdin and stdout through Javy.IO. Before adopting it, build the README example with javy build index.js -o destination/index.wasm, run it under wasmtime, and compare the static build size against the dynamic linking path the README describes.

## FAQ

### What is Javy and what does it produce?

Javy is a JavaScript to WebAssembly toolchain from the Bytecode Alliance. It takes a JavaScript file and produces a WebAssembly module that embeds a JavaScript runtime, so the module itself is the program you run.

### How do I install the Javy CLI?

The README states that pre-compiled binaries of the Javy CLI can be found on the releases page. It does not document a cargo install or npm install path, even though the repository contains an npm directory.

### How small can a Javy Wasm module be?

The README says dynamic linking can produce modules in the 1 to 16 KB range. The default static linking produces modules that are at least 869 KB.

### How do I run a module built with Javy?

The README pipes JSON into the module with a WebAssembly engine, showing echo '{ "n": 2, "bar": "baz" }' | wasmtime index.wasm, which returns {"foo":3,"newBar":"baz!"}. Your JavaScript reads stdin through Javy.IO.readSync on file descriptor 0 and writes stdout through Javy.IO.writeSync on file descriptor 1.

## Sources

- [bytecodealliance/javy on GitHub](https://github.com/bytecodealliance/javy)
- [Issues](https://github.com/bytecodealliance/javy/issues)
- [License: Apache-2.0](https://github.com/bytecodealliance/javy/blob/main/LICENSE)
- [README](https://github.com/bytecodealliance/javy/blob/main/README.md)
- [Releases](https://github.com/bytecodealliance/javy/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bytecodealliance-javy
