Scriptc by Vercel: TypeScript-to-Native Compiler
Scriptc by Vercel: TypeScript-to-Native Compiler
Overview
Scriptc compiles TypeScript to native binaries that contain no JavaScript engine, producing self‑contained executables with startup times around 2 ms and binary sizes of 170‑200 KB for static builds.
How it works
Scriptc decides, construct by construct, what can be compiled to native code and reports the result via scriptc coverage. The default mode compiles statically to native code; a --dynamic flag embeds a QuickJS‑NG engine (~620 KB) for code that cannot be static, such as npm dependencies or any‑typed code; anything else is rejected with a precise error code and rewrite hint.
What compiles
The static surface includes the TypeScript language (classes with single inheritance, closures, generics, discriminated unions, async/await on stackful fibers, exceptions, destructuring, spread, optional/default/rest parameters, getters/setters, iterators, template literals, regular expressions), the standard library (strings, arrays, Maps, Sets, JSON, Math, typed arrays, Buffer, Error hierarchies), Node’s API surface (fs, path, process, child_process, os, crypto, url/URL, zlib, timers, signal handlers, net, http, https, tls with vendored mbedTLS, dgram, dns, fs.watch, readline), the WHATWG fetch subset (streams, Headers, AbortSignal) over the same native net/TLS stack, and npm dependencies when --dynamic is used (packages resolve via Node’s algorithm, type‑check against shipped .d.ts, and their JS is embedded at build time). Programs type‑check against TypeScript’s real es2025 lib (plus @types/node if present) and respect the project’s tsconfig.json.
Correctness
Two enforcement mechanisms run on every change: differential testing compares the output of each corpus program under Node and as a native binary, requiring byte‑for‑byte match of stdout, stderr, and exit codes; a memory‑safety lane reruns the corpus under AddressSanitizer with a reference‑count audit, treating leaks and use‑as‑free as build failures. Deliberate divergences from Node are documented and numbered; nothing diverges silently.
Performance
Measured on Apple M‑series against Node, Go, Rust, and Zig with byte‑identical output:
- Startup: ~2.4 ms (Node ~47 ms; comparable to Zig, ahead of Go/Rust)
- Binary size: 170‑200 KB static, ~3 MB with
--dynamicplus embedded dependencies (Go ~2 MB; Node SEA 60‑100 MB) - Memory (RSS): 1‑4 MB typical (Node 67‑116 MB)
- Runtime: JS‑faithful f64 semantics; competitive with systems languages on most workloads; integer inference and ownership analysis are on the roadmap.
Escape hatches
comptime(() => ...)runs TypeScript at build time in an isolated VM and bakes the result 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. --dynamicembeds the QuickJS engine for npm deps andanycode;scriptc coverage --dynamicreports exactly which statements run where and what blockers remain.- Checked casts (
JSON.parse(...) as Config) insert runtime validation that throws a catchable error naming the offending path.
Architecture
The pipeline is: TypeScript → (tsc parse + typecheck) → lowering → typed IR → C → clang → native executable.
packages/compiler: frontend (tsc API → IR), IR with validator/serializer, LLVM and C backends (LLVM default, C reference).packages/runtime: C runtime with refcounted values, cycle collector, stackful fibers, event loop (kqueue), server stack, JS‑exact number formatting; feature units are link‑gated.packages/cli: implementsscriptc build | run | coverage.
Development
To work on the project:
pnpm install && pnpm build
pnpm test # differential corpus + diagnostics snapshots
SCRIPTC_SAN=1 pnpm test # 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 must be green to merge.
Community reaction
Comments on the Hacker News post reflect a mix of curiosity, skepticism, and cautious optimism.
"178kb?! What are you putting in there, a JVM?" – @aabhay
"A lot of people are trying this now with AI, a native TypeScript compiler…" – @satvikpendem
"Porforr has been working towards the same goal for a while… I’m more than a little suspicious of how Vercel has made so much progress so fast…" – @acmnrs
"One of the strengths of TypeScript besides its expressiveness is that it’s compatible with the massive npm ecosystem. Most packages only ship untyped JavaScript with type declarations… so realistically you’d still need a JavaScript engine if you use any packages." – @sheept
"how can you compile it if javascript is a valid subset of typescript? confused." – @xiaodai
"Hasn’t quickjs and GraalVM been able to do this for a while now? Also, does “Linux and Windows binaries build by cross‑compilation” mean that you can’t run it on non‑MacOS? If so, that’s pretty lame…" – @MrDrMcCoy
"This look very promising… I am curious as to whether this will compile to a static lib which can be linked to an existing cpp app to be run on android/ios targets." – @elendilm
"I desperately want to see some of these projects used in production by the companies or people making them…" – @notsylver
"It’s nice they acknowledge and confirm the need for small, fast native executables… I’m skeptical about the practicality…" – @weinzierl
"finally someone did this" – @relug
"Does this mean I can turn my JS‑based backend into single executable and easily ship it, like in Go?" – @anta40
"this is enough to keep them on the first page of HN… Its a growth strategy: invest tokens, build some “nice” project nobody wants…" – @piterrro
"I’ve heard stories like this before… can you name even one that actually works properly in production? Not a single one…" – @ianberdin
"Excellent problem space. Related problem: Using AI to write compilers that optimize runtime code…" – @gajus
"This project is 5 days old going by the commit history and seems to be entirely vibecoded." – @davexunit
"Vercel doing Vercel: 5 days old, entirely vibecoded, already 1.5k stars for no reason, solves no one’s issue and will stop being maintained in a few months…" – @h1fra
"Interesting, I wonder what does no‑runtime option mean, no event loop and no async code then?" – @mapcars
"It is technically possible to drink soup with a shovel but I don’t really wanna do it. I might be wrong here. What’s the use case?" – @bLorax
"no JavaScript engine in the binary, but optionally bundles a 620KB quickjs runtime. giving up JavaScript but keeping a flask in the desk drawer." – @luciana1u
"Despite the hate it is receiving, I thought lets test it at least… For every single of them the coverage generates hundreds of errors and so it is basically useless." – @pmkary
"I had Claude run a benchmark on scriptc vs node… scriptc is about 7.5x slower than Node 24… But the executable starts up 12x faster (1.5 ms vs 18.6 ms), uses 72x less memory (2.5 MiB vs 181 MiB), and is a single 370 KB executable with no runtime dependencies." – @EliasWatson
"It has Comptime. Sweet. I can appreciate that as it seems zig has been successfully influencing things." – @hoppp
"I think Vercel should either collaborate with the TypeScript team or hire one of the active external TypeScript contributors…" – @chall
These comments highlight enthusiasm for the technical achievement, concerns about the reliance on a JavaScript engine for npm dependencies, questions about cross‑platform support, and debates over whether the project will see real‑world adoption or remain a short‑lived experiment.