JH← Back to blog

Vercel's scriptc Compiles TypeScript to Native Binaries, Skipping JS Entirely

Vercel Labs' scriptc is a TypeScript to native binary compiler that skips Node.js and V8 entirely, cutting startup from 47ms to 2.4ms. Here's what it means for serverless and CLI tools.


Every few years, a project shows up that quietly reframes what "running TypeScript" is even supposed to mean. On July 27, 2026, Vercel Labs published scriptc, an open-source TypeScript to native binary compiler that doesn't transpile TypeScript into JavaScript at all — it compiles it straight into a self-contained native executable, with no Node.js runtime bundled and no JavaScript engine like V8 embedded anywhere in the output. For anyone weighing runtime and deployment strategy in 2026 — serverless architects chasing cold-start latency, teams shipping CLI tools, engineers deploying to memory-constrained edge hardware — that architectural choice is worth understanding in some detail, because it changes the cost model in ways a faster JavaScript engine never could.

What scriptc actually does mechanically

The standard mental model for "compiling" TypeScript is transpilation: tsc, esbuild, swc, and friends all strip types and lower syntax, but the output is still JavaScript, still destined to run inside V8, Node.js, Bun, or some other engine. Bun and Deno made that pipeline dramatically faster, but they didn't change what's at the end of it — you're still handing JavaScript to an engine that has to parse it, JIT-compile hot paths, and manage a garbage-collected heap at runtime.

scriptc's pipeline breaks that chain at the second step. TypeScript source is first run through the real tsc type checker — not a reimplementation or a subset, the actual TypeScript compiler's type system — so type errors are caught exactly as they would be in any standard TypeScript project. From there, instead of emitting JavaScript, scriptc lowers the checked program into a typed intermediate representation (IR). That IR is then emitted as C code, which clang compiles into a native binary targeting the host platform directly.

The practical consequence is that the resulting executable has no interpreter loop, no bytecode, and no JIT warming up in the background. It's machine code from the start, produced by a mature, battle-tested C compiler toolchain that has spent decades being optimized for exactly this job. Type checking still happens with TypeScript's actual semantics, which matters for teams who don't want to trade correctness for speed — you get the same compile-time guarantees you already rely on, just with a completely different execution target on the other end.

Why the startup and memory numbers matter beyond a benchmark chart

Vercel Labs' published figures for a simple TypeScript CLI program are stark: the compiled binary comes in around 178KB, starts in about 2.4 milliseconds, and tops out at roughly 4MB of RAM. Run the equivalent workload on Node.js and startup lands around 47 milliseconds — using up to 116MB of memory. That's roughly a 20x reduction in startup latency and close to a 30x reduction in peak memory, in exchange for giving up the JavaScript engine entirely.

Those numbers stop being abstract the moment you map them onto specific workloads:

Serverless and edge functions. Cold-start time is not a vanity metric in serverless pricing — it's billed latency, and on some platforms it's billed compute time too. A function that initializes in 2.4ms instead of 47ms doesn't just feel snappier to a caller; it changes the actual invocation cost and the tail-latency profile under bursty traffic. For edge functions specifically, where the whole pitch is "run close to the user with minimal delay," a runtime that adds tens of milliseconds of engine startup on every cold invocation is working against the platform's own value proposition. A native binary with no engine to warm up removes that tax outright.

CLI tools. Developer experience for command-line tools lives or dies on perceived instant response. A Node.js-based CLI that takes 47ms just to get to your code — before it's done anything useful — is noticeable in a way that adds up across hundreds of daily invocations in a build script or git hook. A 2.4ms native binary is effectively indistinguishable from a compiled Go or Rust tool in responsiveness, while letting the tool's source stay in TypeScript.

Resource-constrained environments. Memory footprint has a direct line to hosting cost and density in containerized and edge deployments. A process that needs 116MB versus one that needs 4MB isn't just "more efficient" in the abstract — it determines how many instances fit on a given node, how aggressively you can bin-pack a Kubernetes cluster, or whether a workload fits on IoT-class hardware at all. For teams paying per-container or per-instance in cloud billing, that 30x memory difference translates fairly directly into a lower bill at the same traffic level.

The comparison to Go is also telling in the other direction: scriptc's ~170-200KB binaries are notably smaller than a comparable Go-compiled binary, which typically lands around 2MB. That's a reminder that "native binary" doesn't automatically mean "small binary" — Go's runtime and standard library bring their own weight, while scriptc's C-based output is lean specifically because it isn't carrying a garbage collector or goroutine scheduler along with it.

The detail that decides whether this is adoptable at all

None of the performance numbers matter if adopting scriptc means rewriting existing code in a different language or a restricted TypeScript dialect. This is where the project's practical design choice matters most: scriptc natively reimplements large parts of the Node.js API surface, including modules like fs, path, crypto, http, https, net, dns, timers, and fetch.

That's a deliberate bet on compatibility over purity. Rather than asking developers to learn a new standard library or restructure their code around scriptc-specific APIs, the project is aiming for Node.js-shaped code to plausibly compile with minimal changes. For engineering teams, that's the difference between "an interesting curiosity for greenfield toy projects" and "something you could realistically pilot against an existing internal tool." A compiler that requires a rewrite before you can even measure the benefit rarely gets past the proof-of-concept stage in a real organization; one that compiles code you already have gets evaluated on its actual merits.

That said, "large parts of the Node.js API surface" is not "the entire Node.js API surface," and teams should treat that gap literally rather than optimistically. Before pointing scriptc at an existing service, the practical first step is compiling the actual dependency tree and application code, not just a synthetic example, and treating any missing or subtly different module behavior as an expected discovery step rather than a surprise blocker.

Honest limitations before you plan around this

It's worth being direct about where scriptc sits in its lifecycle: this is a freshly published open-source project, not a mature runtime with years of production hardening behind it. The numbers above come from Vercel Labs' own example workload — a simple CLI program — and real-world applications with heavier dependency trees, more exotic Node.js API usage, or dynamic patterns that lean on JavaScript's runtime flexibility will need their own testing before anyone should trust them in production.

Native compilation approaches have a long, consistent history of trading dynamic-language flexibility for performance, and there's no reason to expect scriptc to be an exception. Code that relies heavily on eval, dynamic require paths determined at runtime, monkey-patching built-in objects, or reflection-heavy metaprogramming patterns common in some JavaScript frameworks is the kind of code most likely to hit friction in a compile-to-native model, because the compiler needs enough static structure to lower a typed IR into C in the first place. That's not a flaw so much as the inherent cost of the approach — you can't get ahead-of-time native compilation without giving something up on the dynamic end, and any team evaluating scriptc should budget time to find out which of their existing patterns fall into that category.

For teams considering a pilot, the sensible sequencing is the same one that's worked for every other early-stage runtime shift: pick a self-contained, non-critical piece — a CLI tool, an internal script, a single Lambda-style function — compile it, and measure both correctness and the actual startup/memory numbers against your own workload before extending the bet any further.

Where this fits in the 2026 tooling trend

scriptc isn't happening in isolation. Through 2026, a clear pattern has emerged of JavaScript and TypeScript tooling itself being rewritten in, or compiled toward, lower-level languages for performance reasons — Rust- and Go-based tooling has already reshaped bundlers, linters, and package managers across the ecosystem, trading some of JavaScript's flexibility in the tool's own implementation for speed the end user feels directly. scriptc pushes that same instinct one step further: instead of just building faster tools for JavaScript developers, it changes what the application itself compiles down to.

That's a meaningfully different move than a faster bundler or a Rust-based linter, because it touches the deployment artifact, not just the build step. It sits alongside Bun and Deno as part of the broader effort to make JavaScript/TypeScript workloads faster and lighter, but it takes the most radical version of that bet: not a faster engine to run JavaScript in, but no engine at all.

What this means for your stack right now

scriptc is not a drop-in replacement for Node.js today, and treating it as one before it has matured would be a mistake. But the architecture is a legitimate signal worth acting on in a measured way: if your team ships CLI tools, serverless functions with cold-start sensitivity, or services running on memory-constrained infrastructure, it's worth a small, contained pilot now rather than a wait-and-see approach later. The mechanics — real tsc type checking, a typed IR, C code, clang — mean the correctness guarantees you already depend on carry over, and the Node.js API compatibility layer means the barrier to trying it against real code is lower than most native-compilation efforts have offered before. Watch how the project handles the gaps in its API coverage over the next few release cycles; that's the signal that will tell you whether this becomes production infrastructure or stays a fast, promising experiment.