An ahead-of-time JavaScript/TypeScript → WebAssembly compiler in Rust, conformance tracked by test262.
$ jsc -p "[1,2,3,4].filter(x => x % 2 === 0).map(x => x * 10).reduce((a,b) => a+b, 0)"
60
$ jsc -e 'console.log("sum", [1,2,3].reduce((a,b) => a+b, 0))'
sum 6- A compiler, not an interpreter. Every program becomes a validated WebAssembly module (emitted with
wasm-encoder). The CLI runs it onwasmtimeor writes the bytes out withjsc build. - An embeddable library. The whole pipeline lives in the
jsc-corecrate;jscis a thin CLI over it. - An in-browser compiler.
crates/jsc-wasmcompilesjsc-coreitself to wasm (wasm-bindgen); the@cdlab/jscpackage (packages/jsc) wraps it withcompile(src)/run(src)for the browser and Node, andapps/jsc-webis a playground built on that package. - TypeScript by type stripping only — no type checking.
.ts/.mts/.ctsfiles have their types removed by swc's TypeScript strip pass before codegen; see TypeScript.
From source (Rust 1.99+):
git clone https://github.com/cdLab996/jsc.git
cd jsc
cargo install --path crates/jsc # installs the `jsc` binary
# or: cargo build --release # -> target/release/jscPushing a v* tag builds a static Linux x86_64 binary (jsc-linux-x64.tar.gz) and attaches it to a GitHub Release; other platforms build from source.
The browser package: bun install, then bun run build:wasm (builds crates/jsc-wasm into packages/jsc/wasm) and bun run dev:web for the playground.
jsc follows node's command-line shape:
jsc program.js # compile and run a file; prints only console output
jsc -e 'console.log(40 + 2)' # compile and run an inline source string
jsc -p '40 + 2' # run inline source and print its completion value
jsc build program.js -o out.wasm # write the WebAssembly module (-o - writes to stdout)
jsc program.ts # TypeScript: types are stripped, not checkedconsole.log streams to stdout while the module runs; -p prints the completion value afterwards, Node-style ([ 1, 4, 9 ]). Exactly one of FILE, -e, -p is accepted.
| Exit code | Meaning |
|---|---|
0 |
success |
1 |
uncaught exception (Uncaught <value> on stderr), runtime trap, or I/O failure |
2 |
compile error (a caret diagnostic pointing at the source span) or invalid command line |
$ jsc -e 'throw new TypeError("boom")'
Uncaught TypeError: boom
$ echo $?
1The dialect is chosen by FILE extension:
.ts,.mts,.cts— TypeScript. swc parses the file as TypeScript and its strip pass erases type syntax (annotations,interface,type, generics,as,!); nothing is type-checked..tsx— rejected (exit2); JSX is not supported.- any other extension, and all
-e/-pinput — JavaScript, so type syntax there is a compile error.
enum and namespace are not supported: the strip pass lowers them to var-based JavaScript that codegen rejects with an undeclared variable compile error. Invalid input in either dialect — including errors swc recovers from, such as strict-mode early errors — is a compile error (exit 2). The @cdlab/jsc package compiles JavaScript only.
test262 is the primary metric. crates/jsc-test262 runs the official suite at a pinned commit against the release jsc binary:
scripts/test262-fetch.shchecks out tc39/test262 into.tmp/test262.tests/test262/harness/holds the harness includes;tests/test262/unsupported-features.txtlists the feature tags that make a test ineligible (Symbol, Proxy, Reflect, BigInt, RegExp, typed arrays, WeakMap/WeakRef, Intl, Temporal, …). Tests flaggedmodule,async, ornoStrictare ineligible as well.tests/test262/baseline.txtis the set of passing tests;tests/test262/history.tsvrecords the pass count per commit.
Current result (last row of tests/test262/history.tsv): 5195 pass / 22992 eligible (22.6%) / 47665 total.
bash scripts/test262-fetch.sh
cargo build --release -p jsc
cargo run --release -p jsc-test262 # full run with a per-directory report
cargo run --release -p jsc-test262 -- built-ins/Array # only tests under a path prefix
cargo run --release -p jsc-test262 -- --check # fail if any baseline test regressed
cargo run --release -p jsc-test262 -- --update-baseline # rewrite baseline.txt
cargo run --release -p jsc-test262 -- --history # append a row to history.tsvCI (.github/workflows/):
ci.yml— on push tomainand on pull requests that touch Rust paths (crates/**, Cargo manifests and lockfile, toolchain/lint configs,tests/test262/**,scripts/test262-fetch.sh):cargo fmt --check,typos,cargo clippy(warnings denied),cargo nextest, doctests,cargo shear,cargo deny; plus a test262 job that runsjsc-test262 --checkagainst the baseline.web.yml— on push tomainand pull requests that touchcrates/**,packages/**,apps/**,scripts/**, Cargo or bun manifests (and manual dispatch): builds@cdlab/jsc, runsbiomelint, builds the playground, and runs the package tests.release.yml— onv*tags (and manual dispatch): builds the static musl binary and publishes the release.
| Area | What works |
|---|---|
| Values | number, boolean, undefined, null, string, arrays, objects, functions, Map, Set, Promise, Date, generators |
| Operators | arithmetic, comparison, ==/=== and negations, logical and ??, typeof, instanceof, in, delete, compound assignment, ++/--, optional chaining ?.; ToNumber coercion for arithmetic, unary, update and relational operators |
| Bindings | block-scoped let/const (assigning a const is a compile error); array/object destructuring with defaults and rest; spread in array literals and calls |
| Control flow | if, for, while, for-of, for-in, switch, labels, ternary |
| Functions | declarations, arrows, closures (capture by reference), default and rest parameters; missing arguments are undefined, extra arguments are ignored; function* generators; async functions and top-level await |
| Classes | fields, methods, constructors, extends/super, static methods and fields, instance getters/setters |
| Built-ins | console.log, Math, String/Number/Boolean, parseInt/parseFloat, common array and string methods, Object.keys/values/entries, Map/Set, JSON.parse/stringify, Date |
| Async | Promise (new Promise, resolve/reject, .then/.catch) with a microtask queue |
| Errors | throw, try/catch/finally; Error/TypeError/RangeError with .name, .message, .stack and instanceof; member access on null/undefined throws a catchable TypeError; running out of memory throws RangeError |
Unsupported syntax, and unknown methods on statically known receivers, are rejected at compile time with a source-span diagnostic; an unknown method on a dynamically shaped receiver throws TypeError at run time.
source ──swc──▶ AST ──codegen──▶ IR ──emit──▶ .wasm ──run──▶ result
- frontend — parses source with
swc. - codegen — lowers the AST to a small IR: scopes, closure capture analysis, generator/async state machines. Top-level bindings captured by top-level functions live in module cells.
- emit — IR → wasm. Every value is a NaN-boxed
i64(numbers are rawf64bits, with NaN canonicalized on every rebox through__box_f64; other kinds are tagged in the quiet-NaN space). Strings, arrays, objects, closures and environments live in linear memory behind a type-byte header, allocated by__alloc, a bump allocator that grows memory on demand up to 512 MiB. Exceptions use manual unwinding (no wasm exception-handling proposal).Map/Setkeys compare with SameValueZero (__same_value_zero). - run — instantiates the module on
wasmtime, provides the host imports, drains the microtask queue, and formats the result.
Details are in DESIGN.md.
- Strict-mode semantics only. Sloppy-mode constructs such as
withor assigning an undeclared variable are compile errors. - Fixed-shape objects. Computed keys (
{[k]: v}), object spread, object-literal accessors and private class fields are rejected; writing a property an object does not have, or assigning past the end of an array, throws aTypeError; dynamic properties arrive with the wave-2 object model. - No GC. The heap is bump-allocated and never freed; the 512 MiB cap ends a run with
RangeError: out of memory. - Not self-contained. The module imports six host functions from the
envnamespace (print,__convert,__method,__promise,__then,__now), provided by thewasmtimehost injsc-coreand by the JS shim in@cdlab/jsc; it is not a WASI module. - Byte strings. Strings are byte sequences, not UTF-16:
"é".lengthis2. - Missing built-ins. No
RegExp(regex literals are rejected),Symbol,Proxy,Reflect,BigInt,WeakMap/WeakSet/WeakRef, typed arrays,eval, orFunction.prototype.call/apply/bind; no ES modules. - Restricted forms. Class and generator expressions, async arrows and async generators are rejected.
A generic object model with a dynamic property-lookup fallback, UTF-16 strings, a JavaScript prelude for built-ins, self-contained WASI modules, GC, and ES modules.