Why we rewrote our build pipeline in Rust
Migrating 180k lines of TypeScript tooling to Rust cut our cold builds from 47s to 4.2s. Here is what we got wrong on the first try, and the patterns that finally stuck.
Migrating 180k lines of TypeScript tooling to Rust cut our cold builds from 47s to 4.2s. Here is what we got wrong on the first try, and the patterns that finally stuck.
Eighteen months ago we were staring at a 47-second cold build time on a repository with 180,000 lines of TypeScript. The business cost was real: every developer lost roughly 40 minutes per day waiting. The opportunity cost was worse — people stopped iterating as freely because the feedback loop felt punishing.
We did not jump straight to Rust. We spent three months trying to make the existing TypeScript build faster. We moved to esbuild, then to Vite, then to a custom bundler written in Go. Each helped, but none got us below 12 seconds. The problem was not the bundler — it was the type-checking pass, which had to run the full TypeScript compiler over the entire graph.
The decision to rewrite in Rust came down to a single constraint: we needed a type-checker that could cache aggressively and run incremental checks in under 100ms. No existing tool did this. We had to build it.
Our first attempt failed for a reason that should have been obvious in retrospect: we tried to replicate TypeScript semantics exactly. TypeScript's type system is famously unsound — it has intentional holes, structural subtyping, and decades of pragmatic design decisions that make it both powerful and unpredictable to reimplement.
We spent six weeks building a type checker that passed 94% of our test suite. That last 6% took another four months and nearly killed the project.
The breakthrough came when we stopped trying to be a full TypeScript compiler and instead became a fast path validator. We check the 80% of TypeScript patterns that appear in our codebase, and fall back to the official compiler for the long tail.
Three architectural patterns made the Rust rewrite viable:
The result: 47 seconds to 4.2 seconds on a cold build. 800ms on an incremental build. We have been running this in production for eight months and have not had a correctness regression.
Explicabo Deserunt doloribus esse itaque nobis aut enim vitae consequatur consectetur porro cupiditate qui maxime in
Inferred const generics and the new satisfies-everywhere ergonomics are not subtle. Half our shared types collapsed into one-liners.