Zero-runtime TypeScript. scriptc compiles ordinary TypeScript into small, fast native executables — no Node, no V8, no JavaScript engine in the binary.
$ cat fib.ts function fib(n: number): number { return n < 2 ? n : fib(n - 1) + fib(n - 2); } console.log(fib(30)); $ scriptc run fib.ts 832040 $ scriptc build fib.ts && ls -la fib -rwxr-xr-x 178K fib # a self-contained native binary, ~2ms startup No changes to your code. No annotations, no dialect — the same TypeScript you run on Node, type-checked by the real TypeScript compiler and compiled to native. What compiles behaves byte-for-byte like Node.
$ npm install -g scriptc Requires clang (preinstalled with Xcode Command Line Tools). macOS arm64 is the primary platform; Linux and Windows binaries build by cross-compilation, each verified by its own differential test lane.
Most TypeScript is far more static than the ecosystem assumes. scriptc decides, construct by construct, what can compile to native code — and tells you:
$ scriptc coverage app.ts statements analyzed 4481 compile statically 4451 (99%) blockers: ×2 functions with optional parameters as values SC1090 ×1 Promise.reject SC2020 Three tiers, always explicit:
The static surface covers the language and the standard library real programs use:
Programs typecheck against TypeScript's real es2025 lib (plus @types/node when your project has it), and your tsconfig.json governs checker strictness. Anything reached that has no lowering is a precise diagnostic, never a surprise.
Two enforcement mechanisms run on every change:
The deliberate divergences from Node (there are a few dozen, mostly around timing internals and error-object properties) are documented and numbered; nothing diverges silently.
Measured on Apple M-series against the same workloads in Node, Go, Rust, and Zig (all byte-identical output, verified):
comptime(() => ...) runs TypeScript at build time (in an isolated VM inside the compiler) and bakes the result into the binary as a literal. Native FFI ( --ffi ) binds signature-only TypeScript declarations to direct C ABI calls and links manifest-declared archives, objects, and system libraries. The boundary is explicit and length-delimited; see the Native FFI guide . --dynamic embeds the engine for npm deps and any code. scriptc coverage --dynamic reports exactly which statements run where and what the remaining blockers are. Static stays the default: a binary never silently grows an engine. Checked casts — JSON.parse(...) as Config inserts a runtime validation that throws a catchable error naming the offending path ( expected number at $.port, got string ). TypeScript's as is a promise; scriptc verifies it. Architecture flowchart LR TS[TypeScript] -->|tsc: parse + typecheck| L[lowering] L --> IR[typed IR] IR --> C[C] C -->|clang| BIN[native executable] Loading packages/compiler — frontend (tsc API → IR), the IR with validator/serializer, the LLVM and C backends. The IR is the only interface between the ends; LLVM is the default code generator (with a transparent fallback for programs outside its tier), and C is the reference backend forever (readable, source-line-annotated output via --backend c ). packages/runtime — the C runtime: refcounted values with a cycle collector, stackful fibers and the event loop (kqueue), the server stack, JS-exact number formatting. Feature units are link-gated: binaries pay only for what they use. packages/cli — scriptc build | run | coverage . Development $ pnpm install && pnpm build $ pnpm test # differential corpus + diagnostics snapshots $ SCRIPTC_SAN=1 pnpm test # the same corpus under ASan + RC audit $ pnpm scriptc build x.ts --emit-ir # keep .scriptc/x.c and x.ir.json Every feature lands with differential tests; both lanes green is the merge bar.
Hacker News
news.ycombinator.com