Vercel Labs' ScriptC trades sustained TypeScript throughput for millisecond startups
Vercel Labs' experimental ScriptC compiles TypeScript to native binaries that started in 1.78ms against Node's 61.78ms in one benchmark. The gain holds for short, statically typed programs, while framework code and sustained compute both ran slower than Bun or Node.
The Engineer · Build desk

What happened
- Code lands in one of three tiers: compiled statically by default, run in an embedded quickjs-ng engine of about 620KB under --dynamic, or rejected at compile time with an SC code and a rewrite hint.
- The Hono framework needed --dynamic in the same tests, pushing 62% of the server into QuickJS and cutting throughput to 18.4k requests per second against Bun's 70.5k.
- The documented divergences from Node include UTF-8 strings, reference counting instead of garbage collection, Object.keys in declaration order, and process.argv[0] returning "scriptc".
- The vercel-labs/scriptc repository was created on 22 July 2026 and has gathered around 4.9k stars.
Why it matters
- Who has to decide decision Moving a Hono service to scriptc today would give up roughly three quarters of Bun's throughput to save startup time that a long-running server pays once.
- What it limits constraint Before any of the startup numbers apply, a team has to move any-typed and framework code into the static tier, so adoption begins as a rewrite.
- Who's exposed exposure Programs that read Object.keys order or print process.argv[0] can produce different output compiled than they do under Node.
- What it costs cost Commenters cited Vercel's zerolang, whose commits stopped weeks after launch, so a team shipping on scriptc carries the cost of a fork if this project stalls too.
Next story · Build · 1h ago
Cloudflare's Deno deal gives the runtime one more year of monthly fixesConfirmed2 publishers
`scriptc build` passes the source to the official TypeScript compiler for parsing and type checking, lowers the checked program to a typed IR, and then emits C, LLVM IR, assembly, object files or WebAssembly for WASI Preview 1 [1]. Each stage can be dumped with `--emit` [1]. A fully static build still links a small runtime pack for memory management and core primitives [2]. The binary has no Node, no V8 and no JIT [2]. A `node:http` server compiles to an event loop that calls native socket primitives directly [7].
In the benchmark InfoQ reported, scriptc 0.0.16 started a CLI about 12 times faster than Bun 1.3.12 and about 35 times faster than Node 24.18.0 [10]. A framework-free `node:http` server idled at 1.9MiB [8]. For those numbers to transfer to another workload, two things have to be true: the program has to compile without `--dynamic`, and most of its run time has to be startup.
One developer on Hacker News tested the second condition. A byte-array workload ran about 7.5 times slower than Node 24, while the 370KB executable started in 1.5ms against 18.6ms and used 2.5MiB against 181MiB [11]. The dev.to write-up puts typical single-binary bundles at 40MB to 90MB, because they carry V8 and the Node runtime [24]. On the developer's figures, the binary saves 17.1ms at startup and spends 6.5 extra milliseconds for every millisecond Node spends working. It comes out ahead only when the Node version does under about 2.6ms of work per run [23]. That assumes the slowdown applies evenly to everything after startup [23]. Remo Jansen's attempt to compile the TypeScript 6 compiler failed at an internal compiler error. He measured a 3.6ms cold start against Node's 48.9ms and a much slower 2.33s on compute, according to InfoQ [13].
Filip Pizlo said treating every number as a float and deferring integer inference skips "half the problem of fast JS" [12]. He also argued that `any` is common enough that real programs will keep falling into the QuickJS fallback [12]. `scriptc coverage app.ts` is the check to run first. It reports what share of the AST compiles statically and gives a diagnostic code for each dynamic site [14]. One developer ran it on every local project and wrote: "For every single of them the coverage generates hundreds of errors and so it is basically useless." [15] An experimental `--npm-static` flag compiles named packages out of the fallback [17].
The testing discipline deserves credit. scriptc targets macOS, Linux, Windows and WASI Preview 1, and differential tests compare stdout, stderr and exit codes byte for byte against Node [6]. Because the compiler emits readable C, anyone doubting a performance claim can read the code it actually produced [1].
Simon Willison said building small, fast binaries without writing C or Rust "seems like a valuable capability" [20]. I agree, for a narrow case. In my view the first candidate is an internal CLI with no npm dependencies and a clean coverage report. Where an edge target needs it, that CLI can be cross-compiled to wasm32-wasi with Zig as the linker driver [21]. The compiler driver itself needs Node.js 24 or newer [5], so the build machine keeps the runtime the binary leaves out.
What to watch
- Whether a later scriptc release adds integer inference, the gap Filip Pizlo named, and whether the 7.5x byte-array slowdown narrows as a result.
- Whether --npm-static can compile a framework like Hono out of QuickJS and bring its throughput near Bun's 70.5k requests per second.
- Commit activity on vercel-labs/scriptc over the coming months, measured against the zerolang precedent commenters raised.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence55
- Adoption15
- Hype gap+25
- Incentives35
- Confidence55
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
ScriptC uses the official TypeScript compiler for parsing and type checking, lowers the AST to a typed IR, and emits readable C, LLVM IR, native assembly, object files, native executables and WebAssembly (WASI Preview 1); stages can be inspected with --emit=ir, --emit=c, --emit=llvm, --emit=asm and --emit=obj.
- [2]
In a fully static build, ScriptC links a minimal runtime pack providing memory management and core primitives; there is no Node.js runtime, no V8 engine and no JIT compiler embedded in the binary.
- [3]
Every construct lands in one of three tiers: compiled statically by default, run dynamically inside an embedded quickjs-ng engine of roughly 620KB when --dynamic is passed for npm packages and any-typed code, or rejected at compile time with an SC code and a rewrite hint.
- [4]
A benchmark of scriptc 0.0.16 against Bun 1.3.12 and Node 24.18.0 recorded median CLI startup of 1.78ms versus 21.29ms for Bun and 61.78ms for Node.
- [5]
ScriptC requires Node.js 24+ for its compiler driver and installs globally with npm install -g scriptc.
- [6]
scriptc targets macOS, Linux, Windows and WASI Preview 1, and is enforced by differential tests that compare stdout, stderr and exit codes byte for byte against Node.
- [7]
When a node:http server is compiled with scriptc build, ScriptC links the HTTP event loop directly against native socket primitives rather than passing through V8.
- [8]
The same benchmark recorded idle memory of 1.9MiB for a framework-free node:http server compiled with scriptc.
- [9]
The same tests found Hono required --dynamic, pushing 62% of the server into QuickJS and cutting throughput to 18.4k requests per second against Bun's 70.5k.
- [10]
scriptc's median CLI startup was about 12 times faster than Bun's and about 35 times faster than Node's in the reported benchmark.
- [11]
A Hacker News developer reported that on byte-array results scriptc was about 7.5x slower than Node 24, but the executable started 12x faster (1.5 ms vs 18.6 ms), used 72x less memory (2.5 MiB vs 181 MiB), and was a single 370 KB executable with no runtime dependencies.
- [12]
Filip Pizlo argued that using floats for all numbers and deferring integer inference skips "half the problem of fast JS", and that leaning on QuickJS is a poor fit for a performance project, since any is common enough that real programs will keep falling into the island.
- [13]
Remo Jansen's attempt to compile the TypeScript 6 compiler itself failed at an internal compiler error, though he measured a 3.6ms cold start against Node's 48.9ms and a much slower 2.33s on compute.
- [14]
scriptc coverage app.ts reports what percentage of the AST compiles statically and provides specific diagnostic codes for dynamic sites requiring reflection.
- [15]
"For every single of them the coverage generates hundreds of errors and so it is basically useless."
- [16]
The limitations page documents deliberate divergences: strings are stored as UTF-8, memory is reference counted rather than garbage collected, Object.keys reports declaration order, and process.argv[0] is "scriptc".
- [17]
The npm dependencies guide covers an experimental --npm-static flag that compiles named packages out of the QuickJS island.
- [18]
The scriptc repository was created on 22 July 2026 and has since gathered around 4.9k stars.
- [19]
A recurring worry is longevity, with commenters pointing at Vercel's zerolang, which stopped receiving commits weeks after launch.
- [20]
Simon Willison said building small, fast binaries without writing C or Rust "seems like a valuable capability".
- [21]
Using Zig as a cross-target linker driver (SCRIPTC_CC=zigcc), ScriptC can cross-compile TypeScript into portable wasm32-wasi modules.
- [22]
The Hono server under scriptc reached about 26% of Bun's throughput, giving up roughly three quarters.
- [23]
On the Hacker News developer's figures, the scriptc binary saves 17.1ms at startup and comes out ahead overall only when the Node version does under about 2.6ms of work per run, assuming the 7.5x slowdown applies evenly to all post-startup work.
- [24]
Single-binary bundlers routinely produce 40MB to 90MB executables simply to bundle the V8 engine and Node runtime.
Sources
2 independent publishers whose own reporting we read for this story.
- dev.toScriptC: Vercel's Experimental Native TypeScript Compiler
2 articles · October 6, 2026
- infoq.comVercel Labs Ships scriptc, a TypeScript-to-Native Compiler That Leaves the JavaScript Engine Behind
1 article · September 24, 2026