Back to Blog

I Migrated a Client Project to TypeScript 7's Native Compiler: What Actually Changed

#typescript#compiler#tooling
I Migrated a Client Project to TypeScript 7's Native Compiler: What Actually Changed

TypeScript 7.0 went GA this month with the rewritten Go-native compiler — the project internally calls it tsgo — and the number everyone keeps repeating is “10x faster.” I’ve been burned before by tooling announcements that read great in a blog post and then fall apart the moment they touch a real codebase with three years of accumulated weirdness in it. So instead of writing a hot take from the changelog, I actually put it on one of my client projects and tracked what happened.

The project I picked was a mid-size admin dashboard, a few hundred TypeScript files, React on the frontend, a decent chunk of shared types with the backend. Nothing exotic, but old enough to have picked up some cruft — a couple of custom type utilities, some any escape hatches I’ve been meaning to clean up, a build pipeline nobody’s touched much since it was set up. Exactly the kind of project where a compiler rewrite either quietly pays for itself every day, or breaks in some annoying corner I didn’t anticipate.

The upgrade itself was less dramatic than I expected. npm install typescript@7 plus swapping the hardcoded tsc binary reference, and most of the day-to-day commands just worked. The bigger adjustment was mental: tsgo isn’t a flag on the existing compiler, it’s a from-scratch reimplementation of the type checker in Go, so the honest expectation going in should be “this behaves like TypeScript, not that it’s byte-for-byte identical to the old compiler in every edge case.” That framing mattered for how I read the errors that came up.

The build time difference is real, the kind you notice without a stopwatch. A clean build on this project went from a little over 40 seconds to under 6. Incremental builds during active development were the bigger win in practice — the gap between saving a file and seeing type errors in the editor went from “long enough to glance at Slack” to basically instant. If you work across several projects in the same day like I do, that difference stacks up into a meaningfully less frustrating afternoon, not just a nicer benchmark chart.

The rough edges showed up in the tooling ecosystem around the compiler, not in the compiler itself. A couple of ESLint rules leaning on the TypeScript language service behaved slightly differently until I bumped typescript-eslint to a version that explicitly supports 7.0. One custom transformer I’d written years ago for a code-generation step didn’t have a native-compiler equivalent yet and had to keep running through the classic JS-based compiler as a separate step — mildly annoying but workable as an interim state. The lesson here isn’t “don’t upgrade,” it’s “check your plugin versions before touching a client project, not after.”

Editor experience was where I was most skeptical and ended up most convinced. VS Code’s built-in TS server has historically been the thing that grinds to a crawl on a big monorepo, and I expected the native compiler to help there too, but not by this much. Autocomplete on a file with a deep import chain went from a noticeable half-second stutter to feeling instant. Not a metric anyone benchmarks, but it’s the thing that actually changes how it feels to work in the editor for eight hours a day.

I hit one thing nervy enough to slow down for: a couple of narrowing edge cases in generic-heavy utility types resolved slightly differently between the old and new compiler. Nothing that changed runtime behavior, nothing arguably less correct in the new compiler — but exactly the kind of subtle diff that can silently mask a real bug if you’re not paying attention during the transition. I ran a full tsc --noEmit diff between old and new compiler output before merging, not just “does the build pass,” and I’d tell anyone else migrating to do the same.

I didn’t roll this out everywhere at once, and I wouldn’t recommend anyone else does either. My own order: smaller side projects first, since the blast radius of something going wrong there is just me being annoyed for an afternoon. Then this client dashboard, on a branch, with the old compiler still available as a fallback in CI until I was confident. Only after that did I start planning it for the bigger, more type-heavy client codebase I maintain, deliberately slower since that one has more custom tooling wired into the build.

The thing I keep coming back to: this is the good kind of tooling improvement, the kind that doesn’t ask you to change how you write code. A lot of the AI tooling and framework churn I write about on this blog asks for a real behavior change, a new mental model. This one just makes the thing you were already doing faster, with a genuinely small adoption cost if you check plugin compatibility first. Rare enough in this industry to be worth calling out on its own, separate from whether the exact 10x number matches what you’ll see.

My honest recommendation for anyone considering this: try it on your smallest real project first, not a toy repo and not your biggest client codebase. A toy repo won’t surface the plugin compatibility issues that actually bite, and your biggest codebase is the wrong place to be surprised. Somewhere in between gives you a fast, low-risk read on whether your build tools, linters, and custom transformers are ready.

Would I go back to the old compiler for this project? No — the build time win alone justifies keeping it, even accounting for the plugin friction. But I’m glad I approached it this way: as a real migration with a rollback plan, not just swapping a version number in package.json and hoping nothing broke. On a codebase whose blast radius you don’t fully control, “probably fine” isn’t a plan.