Get started with JS²
js2
(
@loopdive/js2
) is an ahead-of-time compiler that turns TypeScript and JavaScript into WebAssembly. There is no bundled
interpreter and no engine embedded in the output — the compiler emits WasmGC directly. This guide installs
the CLI, compiles a first module, and runs the result from Node.js and the browser.
1 Install
The compiler ships as a single npm package. Install it globally, or run it on demand with
npx
:
# Global install — adds the `js2wasm` binary to PATH
npm install -g @loopdive/js2
# One-shot — no global install
npx @loopdive/js2 input.ts -o out/
Verify the install:
js2wasm --version
js2wasm --help
js2wasm
is the CLI entry point; the npm package name is
@loopdive/js2
.
2 Compile your first module
Write a tiny TypeScript file:
cat > add.ts <<'EOF'
export function add(a: number, b: number): number {
return a + b;
}
EOF
Compile it:
js2wasm add.ts -o .
You should now see four files alongside
add.ts
:
| File | Purpose |
|---|---|
add.wasm |
The compiled WebAssembly binary |
add.wat |
WebAssembly text format (human-readable) |
add.d.ts |
TypeScript declarations for the module's exports |
add.imports.js |
A
createImports()
helper that builds the import object
|
The
.imports.js
helper centralises the JavaScript host glue (string builtins, boxed-number constructors, host calls). You
import it from your loader rather than constructing the import object by hand.
3 Run in Node.js
Create a small loader,
run.mjs
:
run.mjsimport { readFile } from "node:fs/promises";
import { createImports } from "./add.imports.js";
const bytes = await readFile("./add.wasm");
const { instance } = await WebAssembly.instantiate(bytes, createImports());
console.log(instance.exports.add(2, 3)); // 5
Run it:
node run.mjs
--experimental-wasm-gc
flag to enable WasmGC.
4 Run in the browser
The same module loads when fetched over HTTP:
<script type="module">
import { createImports } from "./add.imports.js";
const response = await fetch("./add.wasm");
const { instance } = await WebAssembly.instantiateStreaming(
response,
createImports(),
);
console.log(instance.exports.add(2, 3)); // 5
</script>
Any reasonably current Chromium, Firefox, or Safari handles WasmGC natively — there is no polyfill step.
5 Common CLI flags
The flags you reach for most often:
| Flag | What it does |
|---|---|
-o, --out <dir> |
Output directory (default: same dir as the input file). |
-O, --optimize |
Run Binaryen
wasm-opt
(default
-O3
). Smaller, faster.
|
-O1
..
-O4
|
Pick the optimizer level explicitly. |
--target wasi |
Emit WASI imports (
fd_write
,
proc_exit
) instead of JS host glue.
|
--target linear |
Emit linear-memory output instead of WasmGC. |
--allow-fs |
Permit
node:fs
host imports for non-WASI builds (off by default).
|
--wit |
Also emit a WIT interface file for the Component Model. |
--wat |
Print only the
.wat
text to stdout (skip binary output).
|
--mode production |
Set
process.env.NODE_ENV="production"
at compile time; drops dead branches.
|
--define K=V |
Substitute an identifier path with a literal before parsing. |
A worked example:
js2wasm add.ts -o dist/ -O3 --wit
This writes optimized
dist/add.wasm
,
dist/add.wat
,
dist/add.d.ts
,
dist/add.imports.js
, and
dist/add.wit
. For the complete reference, see the
CLI documentation
.
6 Where to next
- The playground compiles TypeScript to WasmGC live in the browser, with runnable examples for async/await, classes, and algorithms.
- The FAQ covers the project's stance on TypeScript types, runtime fallbacks, and Test262 conformance.
- The source on GitHub includes the full CLI reference, methodology notes, and architecture decision records.