Scriptc: Vercel's TypeScript-to-Native Compiler Explained

Scriptc: Vercel's TypeScript-to-Native Compiler Explained


There is a new tool on GitHub called scriptc, and its pitch is the kind of thing that makes developers stop scrolling. Write ordinary TypeScript. Compile it. Get a native binary that starts in about two milliseconds, weighs under 200 kilobytes, and runs with no Node, no V8, and no JavaScript engine sitting inside it at all.

If that sounds like the end of a decade long complaint about JavaScript’s weight and slowness, you are reading it the way the project’s own README wants you to. Vercel Labs, the experimental arm of the company behind Next.js, published scriptc as an open source repository, and it climbed onto Hacker News within hours. It picked up over a hundred points and dozens of comments in a single afternoon. Some of those comments were excited. A lot of them were not.

This is the story of what scriptc claims to do, what it actually does under the hood, and why several serious language implementers looked at it and said, essentially, the numbers do not add up and the whole thing was probably built by AI agents that nobody carefully reviewed.

What scriptc actually promises

The core idea is simple enough to fit on a T shirt. Most TypeScript, the project argues, is far more static than the ecosystem treats it. A function with typed parameters, a class with known fields, a loop over an array of numbers: none of that needs an interpreter standing by at runtime. It can be compiled straight down to machine code, the same way C or Rust would be.

So that is what scriptc tries to do. You write your TypeScript exactly as you would for Node. No special dialect, no new annotations, no decorators to opt into “native mode.” The compiler runs your code through the real TypeScript type checker, converts it into an internal typed representation, and then lowers that into C, which gets handed off to clang for the final native build. The project’s own example is a recursive Fibonacci function that compiles into a binary just under 180 kilobytes and prints its result before you can blink.

What makes this more than a toy is the coverage command. Point scriptc at a real file and it will tell you, statement by statement, how much of your program it could turn into pure native code and which lines defeated it. In the README’s own example, run against a small application, it reports something like 99 percent of the statements compiling statically, with the rest flagged individually: optional parameters used as values, a stray Promise.reject, each one tagged with its own diagnostic code.

Anything that cannot be proven safe at compile time, whether that is an untyped npm dependency or code marked any, gets pushed into a second tier. There, scriptc quietly embeds quickjs-ng, a small JavaScript engine, and runs that portion the old fashioned way, with runtime checks validating every value that crosses back into the native side. A third tier exists too: outright rejection, with an error code and, usually, a suggested rewrite. Nothing is supposed to fail silently or get miscompiled behind your back.

On paper, that is a genuinely clever design. Three explicit tiers instead of one blurry compromise. A default that keeps you fully native unless you ask for the escape hatch. A coverage report that tells you exactly where your program is paying the interpreter tax.

The numbers that got everyone’s attention

Vercel published a performance table comparing scriptc against Node, Go, Rust, and Zig on the same workloads, all run on Apple silicon. The headline figures are dramatic. Startup time around 2.4 milliseconds versus roughly 47 milliseconds for Node. Binary size in the 170 to 200 kilobyte range for static builds, compared to 60 to 100 megabytes for a Node single executable application. Memory use in the low single digit megabytes instead of Node’s usual 70 to 100 plus.

Those are the kinds of numbers that make a certain kind of engineer forward a link to their whole team with three exclamation points. They also happen to be the kind of numbers that make a certain other kind of engineer, the ones who spend their careers making dynamic languages fast, immediately reach for a calculator.

And that second group showed up fast.

Why the experts started picking it apart

Within hours of the launch hitting Hacker News, the thread filled with pointed technical objections, not just vague skepticism. One commenter who described making dynamic languages fast for a living called the architecture out directly, arguing that scriptc’s numbers were not credible and promising to benchmark it independently using standard methods.

The specific complaints are worth walking through, because they are not just noise. They target real design choices in the project.

The first is about numbers. Looking at the C code scriptc actually generates for that Fibonacci example, every value, including a simple loop counter, comes out as a double. That is JavaScript's own semantics: all numbers are 64 bit floats under the hood, and scriptc is deliberately staying faithful to that so compiled programs behave identically to running the same code in Node. But critics pointed out that skipping integer inference is one of the oldest and best understood tricks in making a numeric language fast, going back to JIT compiler work from the 1980s and 1990s. A compiler chasing this kind of performance story, they argued, shouldn't be punting that problem to "the roadmap."

The second target is quickjs-ng, the embedded engine that handles anything scriptc cannot prove is static. Critics called it slow relative to more modern engines and questioned whether leaning on it for the dynamic tier undercuts the whole “no JavaScript engine” pitch the moment real world code, with real dependencies, hits the compiler. Defenders in the same thread pushed back, noting that quickjs-ng only gets linked in when it is actually needed, and that its small footprint, around 620 kilobytes, was presumably chosen for exactly that reason.

The third, and arguably the sharpest, objection is about what “no JavaScript engine” quietly assumes about how people actually write TypeScript. One commenter ran a rough estimate and found that a huge share of real world TypeScript code uses the any type explicitly, with a further large chunk of inferred types landing on any as well. If that estimate is anywhere close to accurate, a meaningful percentage of ordinary, unremarkable TypeScript would fall out of the static tier and into the embedded interpreter by default, which is a very different story from the zero engine binary featured at the top of the README.

Related to that: TypeScript’s relationship with npm has always been messy. Publishing packages as raw TypeScript source is explicitly discouraged by Node’s own documentation, because TypeScript is not backward compatible across minor versions and compiler settings do not travel well between projects. In practice, that means most published packages ship as plain, untyped JavaScript with a .d.ts file bolted on for editor support. Since scriptc's static tier depends on real type information to prove safety, this ecosystem reality pushes a lot of ordinary dependency usage straight into the dynamic, interpreter backed path scriptc was trying to avoid.

The AI elephant in the repository

Here is where the story stops being purely technical and gets a little uncomfortable.

Someone in the Hacker News thread pointed out that the project’s contributor graph shows roughly 918,000 lines of code added in a single week. For a compiler and its accompanying runtime, that is an almost unbelievable pace for human engineers working alone. The likely explanation, floated in the same thread, is that Vercel used coding agents, quite possibly its own AI tooling, to generate the bulk of the codebase.

That is not automatically a scandal. Plenty of serious software now gets written with heavy AI assistance, and speed by itself proves nothing about quality. But it changed the tone of the conversation. Multiple commenters said the README itself reads like it was written by an AI model, not a human engineer, right down to the rhythm of certain phrases. One person compared the situation to giving a genuinely thoughtful codebase a bad, human written README versus giving a slick AI generated README to a codebase nobody actually reviewed line by line, and said they would take the former every time.

The sharpest version of this critique came from the same commenter who questioned the performance numbers, who argued that a huge volume of AI generated code landing in days, combined with design choices that go against decades of settled wisdom about making dynamic languages fast, adds up to a project that was shipped with confidence but without the kind of deep, skeptical review that a compiler this ambitious usually gets before its numbers go on a public benchmark table.

It is worth being fair here too. Not every response was that harsh. Several commenters pointed out that this is a genuinely old, genuinely hard idea that a lot of experienced systems programmers have thought about and quietly abandoned, and that getting as far as scriptc has, even with rough edges, is still a real achievement worth taking seriously rather than dismissing outright. The maintainers themselves appear to be responsive. Within the same discussion thread, at least one outside contributor’s pull request was merged into the project in close to real time.

How scriptc compares to the competition

Scriptc is not the first attempt at this problem, and it is useful to know what else is out there before deciding how impressed to be.

Porffor, a much smaller solo project built by a developer known online as CanadaHonk, has been chasing a similar goal for longer, compiling JavaScript toward native and WebAssembly targets from something closer to first principles. It is, by the project’s own numbers, still working through the Test262 conformance suite, sitting around 68 percent passing. That is slower, more conservative progress, but critics in the Hacker News thread argued it comes from a place of carefully considered tradeoffs they found easier to trust, precisely because it has been built the hard way over a longer stretch of time.

There is also AssemblyScript, a much older and more established project that compiles a TypeScript flavored language to WebAssembly. It solves an adjacent problem and has years of production use behind it, though it is not really aiming at the same “drop in ordinary TypeScript, get a tiny native binary” pitch scriptc is making.

And separately, Microsoft has its own native compiler effort underway for the TypeScript compiler itself, a Go based port of tsc nicknamed tsc-go, aimed at making the compiler faster rather than compiling arbitrary TypeScript programs down to native binaries. It is a different project solving a different problem, but it shows that “make TypeScript faster by getting further from JavaScript’s own runtime” is very much an idea with momentum across the industry right now, not something Vercel invented in isolation.

Against that backdrop, scriptc’s pitch of near total static coverage and near instant startup looks less like a breakthrough nobody thought of and more like an aggressive new entry into a field where several serious efforts have already spent years running into the same hard problems: untyped dependencies, JavaScript’s float based number model, and the sheer size of the language’s dynamic surface area.

The escape hatches, and why they matter more than the demo

Buried below the benchmark table is a section that tells you more about how seriously to take this project than the Fibonacci demo does. Scriptc ships with a feature called comptime, which lets you wrap a piece of ordinary TypeScript in a function and have it run once, at build time, inside an isolated sandbox. Whatever it returns gets baked into the final binary as a literal value. That is a reasonable, well precedented idea. Rust has something similar with const evaluation, and Zig leans on comptime heavily as a core language feature.

More telling is how the project handles the moment code fails to compile statically. Rather than a generic error, scriptc is built to hand back a specific numbered diagnostic, a code frame pointing at the exact offending line, and, in many cases, a suggested rewrite that would get the same code onto the fast path. That is the kind of detail that separates a demo built to impress a Hacker News crowd for one afternoon from a tool somebody actually intends to maintain. It also, notably, is one of the areas where even the project’s harshest critics in that Hacker News thread did not have much to say. The complaints clustered almost entirely around the numeric model, the reliance on quickjs-ng, and the speed of the AI assisted development process, not around the diagnostics or the tiering system itself.

There is also a --dynamic flag, and it is worth being honest about what it actually does rather than what the marketing implies. Turning it on does not gracefully degrade a handful of edge cases. It embeds the entire quickjs-ng engine into your binary and hands off execution of anything scriptc could not prove safe, meaning npm packages with untyped internals and any code you marked any yourself. Given how common any turned out to be in the wild, according to the estimate floated on Hacker News, a realistic production project reaching for --dynamic even once should expect that flag to get exercised often, not rarely.

Who might actually want this, right now

Strip away the hype and the backlash, and there is a genuinely narrow but real use case underneath scriptc.

If you are writing a small, dependency light command line tool or utility in TypeScript because that is the language you already know well, and you want something faster to start and smaller to ship than a Node bundle, scriptc’s static tier is aimed squarely at you. The coverage report becomes a useful tool in that world: it tells you exactly which lines are keeping you out of the fast path, and you can often just rewrite them.

If your project leans on a typical npm dependency tree full of untyped packages, the picture looks different. You are likely to trip the dynamic tier often enough that the “no JavaScript engine in the binary” headline stops being strictly true for your build, even if it remains true for the parts of your own code that are cleanly typed.

And if what you actually want is proven, predictable native performance with a mature toolchain, the Hacker News crowd’s blunt advice keeps showing up in the thread for a reason: just write it in Go or Rust. Scriptc is betting that TypeScript fluency is valuable enough, for enough developers, that a compiler willing to meet them where they already are is worth the tradeoffs. Whether that bet pays off depends less on the two millisecond startup number on the README and more on how the static coverage percentage holds up once real, messy, dependency heavy TypeScript projects start running through it in volume.

The bigger question this raises

Set scriptc’s specific numbers aside for a moment, because the more interesting story here might not be about compilers at all.

It is about how fast a well funded team can now stand up a large, technically ambitious open source project, publish benchmark numbers that read as authoritative, and get meaningful attention within hours, all while a significant share of the underlying code was apparently produced by AI agents working at a pace no human team could match. That is genuinely new. It was not really possible even two years ago.

Whether that is a promising sign for how software gets built, or a warning about benchmarks nobody has independently verified attached to code nobody had time to deeply review, depends a lot on who you ask. The Hacker News thread itself split roughly along those lines, and it is likely to keep splitting as more projects like this show up.

For now, scriptc is young, it is Apache 2.0 licensed, and its differential testing setup, which runs the same programs through both Node and the compiled native binary and checks the output matches byte for byte, is a genuinely sound way to catch correctness bugs as the project matures. Independent benchmarks, promised by at least one skeptical commenter, have not landed yet. Until they do, the honest read is that scriptc is an interesting, aggressively marketed experiment with real engineering ideas inside it, not yet the settled verdict its own README wants you to walk away believing.


Post a Comment

Previous Post Next Post