Packages

Compile JavaScript to WebAssembly with Javy and run it on wasmtime, with dynamic linking by default for tiny modules and fast cold starts.

Current section

Files

Jump to
javex README.md
Raw

README.md

# Javex
Compile JavaScript to WebAssembly with [Javy](https://github.com/bytecodealliance/javy)
and run it on [wasmtime](https://wasmtime.dev/), from Elixir.
Javex uses **dynamic linking by default**: each compiled module imports
QuickJS from a shared Javy plugin that is instantiated once per
`Javex.Runtime`. Compiled modules are tiny (a few KB) and cold starts
are fast enough to spin up a fresh instance per call.
```elixir
js = ~S"""
function readInput() {
const chunks = [];
let total = 0;
while (true) {
const buf = new Uint8Array(1024);
const n = Javy.IO.readSync(0, buf);
if (n === 0) break;
total += n;
chunks.push(buf.subarray(0, n));
}
const out = new Uint8Array(total);
let o = 0;
for (const c of chunks) { out.set(c, o); o += c.length; }
return JSON.parse(new TextDecoder().decode(out));
}
function writeOutput(value) {
Javy.IO.writeSync(1, new TextEncoder().encode(JSON.stringify(value)));
}
const input = readInput();
writeOutput({ sum: input.a + input.b });
"""
{:ok, mod} = Javex.compile(js)
{:ok, %{"sum" => 3}} = Javex.run(mod, %{a: 1, b: 2})
```
Javy's default I/O surface is `Javy.IO.readSync(fd, buf)` and
`Javy.IO.writeSync(fd, buf)`; the `readInput` / `writeOutput` helpers
above are the convention from
[Javy's README](https://github.com/bytecodealliance/javy#example).
## Installation
```elixir
def deps do
[{:javex, "~> 0.1"}]
end
```
Add a runtime to your application's supervision tree:
```elixir
# lib/my_app/application.ex
children = [
Javex.Runtime
# or: {Javex.Runtime, default_fuel: 1_000_000}
]
```
`Javex.compile/2` works without any running process — it reads the
bundled provider plugin from `priv/` directly — so scripts and tests
can compile modules without starting a runtime.
Javex ships a Rust NIF (built with
[`rustler_precompiled`](https://github.com/philss/rustler_precompiled))
that wraps `javy-codegen` and `wasmtime`. Precompiled artifacts are
published as GitHub release assets, so consumers do not need a Rust
toolchain. Set `JAVEX_BUILD=1` to force a local source build. The Javy
plugin Wasm is bundled in `priv/`.
## API
- `Javex.compile/2` — compile a JS source string into a `Javex.Module`.
- `Javex.run/3` — run a compiled module with JSON or raw byte I/O.
- `Javex.Runtime.start_link/1` — start additional runtimes with custom
fuel, memory, or timeout defaults.
`%Javex.Module{}` is a plain struct; if you need to persist one,
`:erlang.term_to_binary/1` round-trips it.
## Design
See `lib/javex.ex` for the full module docs. A few highlights:
- Starting a `Javex.Runtime` is the consumer's responsibility — add it
to your own supervision tree. wasmtime's `Engine` is `Send + Sync`,
so one runtime handles the whole BEAM. Spin up extra runtimes when
you need different resource tiers (e.g. a tight fuel/memory cap for
untrusted code).
- Each call creates a fresh Store and instance. This is viable
precisely because dynamic linking keeps per-call cost low (the
provider is already live).
- Modules track the SHA-256 of the provider plugin they were compiled
against. Running on a runtime with a mismatched provider returns
`{:error, %Javex.IncompatibleProviderError{}}` instead of a cryptic
link-time trap.
## License
MIT.