
Reverse bytenode .jsc (V8 code cache) to JavaScript — static, pure Rust, no patched V8/Node. Node 8→26 / V8 5.8–14.6; 25k .jsc tested, 0 fail.
Reverse bytenode-compiled .jsc files back to JavaScript.
Static parsing only: one pure-Rust binary — no patched V8, no Node runtime, no network.
Supports Node 8.0.0 → 26.10.0 (510 releases / 36 V8 minors); ≈ 25k .jsc files pass the
tests with 0 failures (31 Node lines × 41 fixtures, compiled with bytenode 1.7.0's flags); two
optimization layers (register folding → swc copy propagation) make the decompiled source readable.
English · 中文
Contents: Highlights · Quick start · Install · Usage · CLI reference · Supported versions · How it works · Verification · Repository layout · Limitations · Contributing
--runtime adds the __runtime stubs so the result runs under node;
--verify puts it through node --check.JSCD_NO_OPT=1 shows the raw form.ReferenceError.$ cat hello.js
function greet(name) {
return "hello " + name;
}
console.log(greet("world"));
$ npm i -g bytenode && bytenode -c hello.js # Node 20.20.2 in this transcript
$ xxd -l 48 hello.jsc
00000000: cc05 dec0 0bc2 e400 9200 0000 e521 2eaa .............!..
00000010: f002 0000 0000 0000 011c 5401 2006 a860 ..........T. ..`
00000020: 0000 0000 0600 0000 010c 4c60 0000 0000 ..........L`....
$ jscd hello.jsc
console.log(greet("world"));
function greet(a0) {
return "hello " + a0;
}
$ jscd hello.jsc --runtime > hello.out.js && node hello.out.js
hello world
jscd info hello.jsc decodes that header (sample in Usage); JSCD_NO_OPT=1 prints the
raw, pre-optimization form instead.
macOS / Linux — Homebrew
brew install ejfkdev/tap/jscd
Windows — Scoop
scoop bucket add ejfkdev https://github.com/ejfkdev/scoop-bucket
scoop install jscd
Straight from the manifest URL, without adding the bucket first:
scoop install https://raw.githubusercontent.com/ejfkdev/scoop-bucket/main/bucket/jscd.json
Prebuilt binaries — Releases.
cargo (any platform with Rust 1.96+)
cargo install jscd # build from crates.io
cargo binstall jscd # or fetch the release binary instead of compiling (cargo-binstall)
From source
cargo install --git https://github.com/ejfkdev/jscd # latest main
git clone https://github.com/ejfkdev/jscd && cd jscd
cargo build --release # -> target/release/jscd
cargo install --path . # ...or install that build into ~/.cargo/bin
Requires Rust 1.96+ (swc, the JS optimizer, needs a recent rustc). No system dependencies.
scripts/release.sh vX.Y.Z runs the release gates (cargo test --release,
clippy --all-targets -- -D warnings, and the end-to-end smoke scripts/ci_smoke.sh — fixtures
compiled to real .jsc, decompiled, syntax-gated and run-compared), then pushes an annotated
tag whose message becomes the GitHub Release description. The tag triggers
.github/workflows/release.yml, which rebuilds the six bare binaries listed above. Then
cargo publish to crates.io (release.sh keeps the tag and the Cargo.toml version in sync), and
python3 scripts/update_readme_help.py to refresh the CLI help below; the
Homebrew tap and the
Scoop bucket pick the new release up on their daily
auto-update run.
jscd app.jsc # decompile one file to stdout
jscd app.jsc app.js # ...to a file
jscd dist/ # every .jsc under dist/ → dist-out/ (tree mirrored)
jscd dist/ out/ # ...into out/ instead
jscd info app.jsc # header fields, detected Node/V8 version
jscd disasm --filter main app.jsc
jscd ro-map probe.jsc > m.json # build a read-only-heap name table
jscd --help # bilingual help (-h, `help`, `help <SUBCOMMAND>`)
.jsc file, or a directory scanned recursively for *.jsc; output defaults
to stdout for a file and to <INPUT>-out/ (tree mirrored) for a directory — -o / the OUTPUT
argument writes elsewhere, - means stdout..jsc from an unsupported V8 is rejected with the detected version and the supported range
in the message (jscd info prints supported: yes|no) — never a silent, runtime-only file.jscd ro-map and pass --ro-map
(JSCD_NO_RO_MAP=1 disables the embedded table). A mismatched table is ignored: you get
<ro0_…> placeholders, never a wrong name.JSCD_LANG (then LC_ALL / LC_MESSAGES / LANGUAGE / LANG /
LC_CTYPE): zh* is Chinese, anything else English; JSCD_LANG=zh|en forces one.| Flag | Applies to | Effect |
|---|---|---|
-o, --output <PATH> | all forms | output file / directory / - (same as the OUTPUT argument) |
--quiet | directory input | suppress per-file progress |
--json | all subcommands | machine-readable output |
--filter <SUBSTR> | disasm | only emit functions whose name contains SUBSTR |
--ro-map <PATH> | decompile | read-only-heap name table produced by jscd ro-map |
--verify | decompile | syntax gate: run the result through node --check (needs node on PATH) |
--runtime | decompile | keep the runnable preamble (__runtime stubs, flattened context vars, name aliases). Off by default — add it when you want to run the product with node |
-h, --help | all forms | print help (jscd help <SUBCOMMAND> for one command) |
-v, -V, --version | all forms | print name, version and repository |
Everything the CLI prints for --help and help <SUBCOMMAND>, verbatim (regenerate with python3 scripts/update_readme_help.py).
jscd --help and every subcommand, verbatimjscd --help
$ jscd --help
jscd v0.1.0 — V8 code cache → JavaScript
https://github.com/ejfkdev/jscd (MIT license)