Vercel's scriptc Compiles TypeScript to Native Binaries Without a Runtime
July 28, 2026

scriptc, Vercel's new TypeScript compiler, produces native binaries that start in two milliseconds and weigh under 200 kilobytes. No Node, no V8, no JavaScript engine in the output. It open-sourced five days ago and hit Hacker News within hours.
That promise is real, but conditional. It holds for dependency-free, fully-typed TypeScript. The moment you pull in an npm package, the story changes.
How the Three-Tier Compilation Model Works#

Most TypeScript-to-binary tools bundle a runtime. Bun compile ships a 57MB binary because the entire Bun runtime is inside. scriptc takes a different path.
Your TypeScript goes through tsc for parsing and type-checking, then lowers to a typed intermediate representation. From there, the compiler splits into three tiers.
The static tier is the default. The LLVM backend emits native machine code with monomorphized generics and reference-counted values. The output is a self-contained binary with no runtime dependencies.
The dynamic tier activates when scriptc encounters code it cannot prove safe at compile time. It embeds quickjs-ng, a ~620KB JavaScript engine, and routes that code through it. Untyped values, any-typed expressions, and imported npm JavaScript all land here.
The reject tier is the safety net. If a construct has no lowering in either path, scriptc emits a diagnostic with a code frame and a rewrite hint.
The project puts it bluntly. "Nothing is ever silently miscompiled." That guarantee is the architectural bet that separates scriptc from compilers that optimize for coverage at the cost of occasional silent bugs.
The scriptc coverage command reports which tier each statement lands in. On the project's own benchmark, 4,451 of 4,481 statements compiled statically. But that benchmark is dependency-free TypeScript written to the compiler's strengths.
200KB Binaries and 2ms Cold Starts Against the Field#
The size difference is not marginal. A scriptc static binary for hello-world weighs 170-200KB according to the README table. Bun compile produces a 57MB binary for the same program.
One caveat before the numbers settle. The scriptc homepage reports a different figure for the same benchmark, listing ~320KB and ~4ms startup instead of the README's 170-200KB and ~2.4ms. This article uses the README numbers, which are more specific.
- Cold start drops from ~47ms (Node) to ~2.4ms (scriptc), roughly 20x faster
- Memory drops from 67-116MB RSS (Node) to 1-4MB (scriptc)
- Node SEA produces 60-100MB binaries, 300x larger than scriptc's static output
- Dynamic mode grows the scriptc binary to ~3MB with embedded quickjs-ng, still 19x smaller than Bun
For serverless functions where cold start and memory directly affect billing, those numbers matter. Perry, another TypeScript-to-native compiler that emerged in 2026, produces ~300KB hello-world binaries with a similar LLVM-based pipeline and targets a broader surface including iOS, Android, and WASM.
But every number here comes with the same asterisk. These are hello-world benchmarks from vendor-published data. A real application with npm dependencies will not produce a 200KB binary.
The npm Dependency Problem and the Dynamic Tier#
This is where scriptc's promise meets the npm ecosystem, and the collision is not gentle.
Most npm packages ship as compiled JavaScript with a .d.ts file providing type information. The .d.ts makes your editor's autocomplete work. It does nothing for scriptc's static tier, which needs actual TypeScript source to analyze and compile.
The scale of this problem is measurable. According to pkgpulse, only about 42% of all npm packages have any form of TypeScript support in 2026. The top 100 packages hit 95%, but the long tail dominates real dependency trees.
Even "TypeScript-supported" packages mostly ship compiled JavaScript with type declarations bolted on. The source TypeScript stays in the repo but never reaches your node_modules.
The practical result is that --dynamic becomes the default for almost any project with external dependencies. Your binary grows from 200KB to ~3MB, and part of your code runs in quickjs-ng instead of native.
That is still a significant improvement over Bun's 57MB. But the pitch of "no JavaScript engine in the binary" only holds for code you wrote yourself.
There is also the f64 performance ceiling. scriptc compiles all JavaScript numbers as 64-bit floating-point doubles, including loop counters and array indices.
Integer inference, the optimization V8 has used for decades to make tight loops fast, is listed as a future roadmap item. For CLI tools, this will not matter. For compute-heavy workloads, the native binary may start faster but run the hot loop slower than V8's JIT-optimized path.
Escape Hatches That Actually Ship#
scriptc ships three features with no equivalent in Bun compile or Deno compile.
comptime(() => ...) runs TypeScript at build time inside an isolated VM within the compiler and bakes the result into the binary as a literal. This is the feature Zig popularized that TypeScript has never had.
Native FFI through the --ffi flag binds signature-only TypeScript declarations to direct C ABI calls. No N-API addons, no build step. You declare the function signature in TypeScript, point scriptc at the shared library, and call it.
Checked casts turn TypeScript's compile-time-only as assertions into runtime validation. Write JSON.parse(input) as Config and scriptc inserts a check that throws a catchable error naming the exact path where the mismatch happened.
comptime()bakes build-time TypeScript evaluation into the binary as literals--ffibinds TypeScript declarations directly to C ABI calls without N-API addons- Checked casts turn
asinto runtime validation with path-level error diagnostics
These features are real and working. But scriptc enters a landscape that is actively crowding.
Perry compiles TypeScript to native via LLVM with a Rust-based frontend and already supports iOS, Android, and WASM targets. Microsoft has explicitly rejected adding native AOT to TypeScript itself.
RyanCavanaugh's comment was direct. "TypeScript is a type checker for JavaScript, not a runtime stack." scriptc is building on ground the language's own team has declared outside their scope.
918k Lines, One Contributor, and the Trust Question#
The scriptc repository shows one contributor. The commit history shows 918,000 lines of code landed in a single week. Some of that is vendored dependency code (QuickJS sources, LLVM IR templates), but the compiler frontend itself is massive and almost certainly AI-generated.
The concern is real. One independent analysis flagged that scriptc "shipped with confidence but without the kind of deep, skeptical review that a compiler this ambitious usually gets."
Design choices that go against decades of compiler wisdom, like skipping integer inference, landed without the scrutiny that shapes projects like LLVM or V8 over years.
The counterweight is the testing methodology. scriptc runs 800+ differential tests that execute the same programs through both Node and the compiled native binary, then check byte-for-byte output matching.
Differential testing against a reference implementation is a well-established compiler validation technique. That is a genuine quality signal, not a vanity metric.
Both of these things are true simultaneously. The test suite catches behavioral divergence. It does not catch performance regressions, security issues, or edge cases in code paths the tests do not exercise.
Open GitHub issues already show real boundary failures at the FFI and npm re-export layers.
scriptc is five days old and evolving rapidly. Treat it the way you would treat any pre-1.0 compiler. Test on your code.
Read the differential test suite. And if you have been auditing AI-generated code, you already know the drill. Do not bet production on it yet.