scriptc: TypeScript Goes Native
Vercel Labs just dropped scriptc, a compiler that turns TypeScript into native binaries via C. No Node, no V8, no JavaScript engine. Here's what it means for your CLI tools, serverless functions, and the future of the TypeScript ecosystem.
TypeScript has a runtime tax that we have all collectively agreed to pretend does not exist. Every ts-node script, every Lambda function, every CLI tool you ship carries Node.js along for the ride -- a 60-100MB JavaScript engine just to execute your 200 lines of typed business logic. We accepted this because the alternative was rewriting in Go or Rust, and nobody has time for that. This week, Vercel Labs released something that changes the equation entirely: scriptc, a compiler that takes TypeScript and emits native binaries through C. No Node. No V8. No JavaScript engine in the final artifact. Just your code, compiled to machine instructions.
The repo hit the front page of Hacker News with 212+ points, and after spending a few days actually using it, I understand why. This is not another bundler or another build tool. This is TypeScript becoming a systems language.
What scriptc actually is
scriptc is a TypeScript-to-native compiler from Vercel Labs (github.com/vercel-labs/scriptc). The pipeline is straightforward: your TypeScript goes through the real TypeScript compiler for type-checking, then scriptc lowers it to C, then clang compiles that C to a native executable. The result is a standalone binary that runs without any runtime dependencies.
The numbers are what got my attention:
- Startup time: ~2.4ms vs Node's ~47ms. That is a 20x improvement.
- Binary size: 170-200KB vs Node SEA (Single Executable Application) at 60-100MB. Three orders of magnitude smaller.
- Memory usage: 1-4MB RSS vs Node's 67-116MB. Your CLI tool uses less RAM than a browser tab.
- No runtime dependencies. The binary is the entire program. Copy it, ship it, done.
If you have ever tried to distribute a Node.js CLI tool and watched users struggle with nvm, node version mismatches, or npm install failures -- you understand why a 200KB static binary is compelling.
How it works: the three tiers
scriptc does not try to compile everything. It uses a tiered system that is refreshingly honest about its boundaries:
- Tier 1: Compiled statically (default) -- your TypeScript is lowered to C and compiled to native code. This is the fast path. Classes, closures, generics (monomorphized), async/await (implemented as stackful fibers), discriminated unions, and a growing set of Node APIs (fs, path, http, net, crypto) all compile natively.
- Tier 2: Runs dynamically -- code that cannot be statically compiled gets routed through an embedded quickjs-ng interpreter (~620KB overhead). This handles npm dependencies that use dynamic patterns. You opt into this with the --dynamic flag, and the JS is embedded into the binary at build time.
- Tier 3: Rejected -- code that uses patterns scriptc cannot handle at all. You get clear error codes explaining what failed and why.
This tiered approach is smart engineering. Instead of trying to boil the ocean and support every JavaScript pattern ever conceived, scriptc gives you a clear coverage model. You can run scriptc coverage on your project to see exactly what percentage of your code compiles statically, what falls to the dynamic tier, and what gets rejected.
# Build a native binary from your TypeScript
scriptc build src/cli.ts -o dist/mycli
# Check what percentage of your code compiles natively
scriptc coverage src/cli.ts
# Output:
# Static: 87.3% (412/472 expressions)
# Dynamic: 11.2% (53/472 expressions)
# Rejected: 1.5% (7/472 expressions)
# Run it directly (compile + execute)
scriptc run src/cli.ts -- --flag value
# Build with npm dependencies (embedded dynamically)
scriptc build src/app.ts --dynamic -o dist/myappWhat actually compiles natively
I was skeptical that real-world TypeScript would hit high static compilation rates. After testing several of my own scripts, I was surprised. The patterns I use daily almost all land in Tier 1:
- Classes with methods, inheritance, and private fields
- Closures and higher-order functions
- Generics -- monomorphized at compile time, like Rust does it
- Async/await -- implemented as stackful fibers, not callbacks
- Discriminated unions and exhaustive pattern matching
- Node APIs: fs (readFile, writeFile, stat), path, http/net servers, crypto
- fetch -- yes, native HTTP client in a 200KB binary
- Template literals, destructuring, spread operators
What does NOT compile statically includes things like eval(), dynamic property access with computed keys, certain reflection patterns, and heavily dynamic npm packages. But here is the thing -- if you are writing well-typed TypeScript (which you should be anyway), most of your code is already in the static-friendly zone.
Where this fits in my stack
I write TypeScript every day for Next.js apps, CLI tools, and AWS Lambda functions. Here is where scriptc immediately becomes interesting for my work:
CLI tooling is the obvious win. I maintain several internal CLI tools that currently require Node.js installed on the target machine. With scriptc, I can distribute a single binary. No installation step. No version conflicts. Just curl the binary and run it. The 2.4ms startup means the tool feels instant in a way that Node CLIs never do -- that 47ms startup adds up when you are running commands in a loop.
Serverless cold starts are the killer use case I keep coming back to. Lambda cold starts with Node.js are 100-300ms depending on bundle size. A scriptc-compiled function with 2.4ms startup and 1-4MB memory footprint could fundamentally change what serverless economics look like. Imagine your Lambda functions using 128MB allocation instead of 512MB because the runtime itself needs almost nothing.
Edge computing gets interesting too. Cloudflare Workers and similar platforms already constrain what you can do -- limited APIs, size limits, startup time budgets. A 200KB native binary that starts in 2ms fits those constraints perfectly. Whether edge platforms will support scriptc binaries is another question, but the profile is right.
Memory safety and correctness
Compiling to C immediately raises the question: what about memory safety? The scriptc team took this seriously. Memory management uses reference counting with a cycle collector -- similar to Swift's approach. Every CI run includes an AddressSanitizer lane that catches use-after-free, buffer overflows, and other memory corruption bugs.
For correctness, they run differential testing that ensures byte-for-byte identical output between scriptc binaries and Node.js. Same input, same output, every time. This is the right approach -- TypeScript developers should not have to think about memory management or worry that their compiled binary behaves differently than it did under Node.
scriptc does not ask you to learn a new language or rewrite your code. It asks you to write the TypeScript you are already writing, and rewards you with the performance characteristics of a compiled systems language. That is a genuinely new trade-off in the ecosystem.
The caveats you need to know
I am excited about scriptc, but I am not going to pretend it is production-ready for everything. Here is the honest picture:
- Platform support is limited. macOS arm64 is the primary target right now. Linux and Windows work via cross-compilation but are secondary. If you are deploying to Linux containers (which most of us are), test thoroughly.
- Not everything compiles. The coverage command helps, but if your project hits 60% static compilation, the dynamic tier adds 620KB of quickjs-ng overhead and those paths will not see the same performance gains.
- It is early. The repo just went public. APIs will change, bugs will surface, and edge cases will bite you. This is not something I would put behind a production API serving customers today.
- npm ecosystem compatibility varies. Well-typed packages with minimal dynamic patterns work great. Packages that rely on require(), eval(), or runtime code generation will fall to the dynamic tier or get rejected entirely.
- Debugging story is still developing. When your compiled binary crashes, you are debugging C-level issues, not TypeScript-level ones. Source maps and debugger integration are on the roadmap but not fully there yet.
Should you adopt now?
My take: start experimenting immediately, but do not ship to production yet.
Here is what I am actually doing:
- Running scriptc coverage on my existing CLI tools to see what percentage compiles statically. If it is above 85%, that tool is a candidate for native compilation once scriptc stabilizes.
- Writing new CLI scripts with scriptc in mind -- avoiding patterns that land in Tier 3, keeping dependencies minimal, using well-typed code throughout.
- Watching the GitHub issues and release notes. The pace of development at Vercel Labs is fast, and the gap between 'experimental' and 'reliable' could close quickly.
- Not rewriting anything. The beauty of scriptc is that it compiles existing TypeScript. When it is ready, the migration cost is approximately zero for compatible code.
The projects where I will adopt first are internal CLI tools and one-off scripts -- places where a crash is annoying but not catastrophic, and where the distribution benefits (single binary, no dependencies) provide immediate value.
What this means for TypeScript's future
Here is the bigger picture that I think most coverage of scriptc is missing: this is not just a performance tool. It is a signal about where TypeScript is headed as a language ecosystem.
TypeScript already won the language war for web development. But it lost in spaces where performance and binary distribution matter -- CLIs, system tools, embedded applications, edge computing. Those spaces went to Go and Rust because JavaScript runtimes were too heavy. scriptc changes that calculus. If TypeScript can produce 200KB binaries with 2ms startup, the reason to reach for Go or Rust for a CLI tool disappears for many use cases.
Combined with Bun's native compilation efforts and Deno's compile command, we are seeing a clear trend: the TypeScript ecosystem is collectively deciding that JavaScript engines are optional, not required. The language is being decoupled from its runtime. That is a fundamental shift, and scriptc is the most aggressive version of it yet.
I will be writing more about scriptc as I integrate it into my workflow and as the project matures. If you are building TypeScript CLI tools, serverless functions, or anything where startup time and binary size matter -- this is worth your attention right now, even if full adoption is a few months away.
If you are thinking about where native TypeScript compilation fits in your architecture, or want to talk through whether scriptc makes sense for your use case, I would love to chat about it.
Found this useful? Let's talk about your project.
Get in touch