· 5 min read ·

TypeScript 6.0 RC and the End of the Self-Hosted Compiler

Source: typescript

Looking back at this from a few weeks out, the TypeScript 6.0 RC announcement on March 6 deserves more attention than it got as a version number bump. The release candidate carries an unusual distinction buried in its announcement text: the team described 6.0 as “the last release based on the current JavaScript” compiler implementation. That sentence is the real story.

The Self-Hosted Compiler Era Is Closing

TypeScript has been written in TypeScript since its earliest public releases in 2012. That self-hosting property was partly philosophical, partly pragmatic: dogfooding your own type system catches real bugs and builds credibility. The TypeScript compiler (tsc) has been a TypeScript project for its entire public life, growing from a relatively small codebase into something that handles millions of lines of user code daily.

The decision to move away from that is not a small one. In March 2025, Microsoft announced a native port of the TypeScript compiler to Go, a project that quickly surfaced on GitHub as microsoft/typescript-go. Anders Hejlsberg, TypeScript’s original architect, was directly involved, and the initial benchmarks were striking: the Go-based compiler ran whole-project builds somewhere between 10x and 15x faster than the current JavaScript implementation on representative real-world codebases.

TypeScript 6.0 exists in that transition window. It ships the current compiler, cleans up accumulated technical debt, drops deprecated options, and prepares the ecosystem for what comes after. Understanding the release that way changes how you read its changelog.

What 6.0 Actually Changes

The breaking changes in 6.0 follow a clear pattern of removing things the team had been soft-deprecating across the 5.x series.

Module resolution cleanup. The --module node16 and --module nodenext modes, which enforce ESM-correct import paths (requiring explicit .js extensions in TypeScript source), become the recommended targets for Node.js projects. The older --module commonjs with --moduleResolution node combination still works but the tooling nudges have shifted. If you’ve been fighting the .js extension requirement in nodenext projects, 6.0 does not relax it; it doubles down. The reason is that the Go compiler needs clean, unambiguous module semantics to resolve dependencies efficiently, and the old node resolution mode’s implicit extension search is expensive to emulate faithfully.

Erasable syntax becomes the baseline. The --erasableSyntaxOnly flag, which prevents TypeScript syntax that cannot be stripped by a simple erase-and-emit pass (things like const enum and namespace-qualified types that require emit-time transformation), moves toward being the recommended default. This matters because both the Go compiler and tools like Oxc and esbuild that strip TypeScript types without a full type check depend on single-pass erasure being sufficient. Decorators using the new ECMAScript decorator standard are fine; old experimental decorators that needed transformation are increasingly on the exit ramp.

Target floor raised. ES3 and ES5 as --target options are removed in 6.0. In practice, almost no project still targets ES3, and ES5 support via tools like Babel or browserslist-integrated bundlers is better handled outside tsc anyway. Removing these simplifies the emit code paths and, again, reduces what the Go compiler needs to replicate.

isolatedDeclarations integration. Introduced in TypeScript 5.5, isolatedDeclarations requires that exported declarations have explicit type annotations sufficient to produce .d.ts files without cross-file type inference. In 6.0, the language server and project references infrastructure treat this flag as a first-class signal for enabling parallel declaration emit. Large monorepos with project references see real compile-time improvements here even before the Go compiler ships, because declaration files for unchanged packages can be skipped without re-checking them.

Why Go and Why Now

The JavaScript runtime is not the right host for a compiler at TypeScript’s current scale. V8 is excellent but it carries GC pauses, JIT warmup costs, and memory overhead that compound badly when you’re checking a 500,000-line codebase. The TypeScript team has worked around this with incremental compilation, project references, and caching, but these are mitigation strategies, not solutions.

Go was chosen over Rust, C++, or a self-hosted native compiler for reasons the team has discussed directly. Go’s garbage collector is good enough for compiler workloads and much simpler to work with than Rust’s ownership model when you’re porting a large existing codebase. The structural concurrency primitives in Go also map reasonably well to the parallel work a type checker wants to do: checking files independently, resolving declarations concurrently, emitting output in parallel. Rust would likely produce a faster binary but at substantially higher porting cost and complexity.

The Go port is not a new language feature; it is a reimplementation of the existing type system. The type-checking semantics are supposed to be identical. When the Go compiler ships as stable, switching to it should be a drop-in: same tsconfig.json, same diagnostics, same output. The performance difference is the entire point.

What This Means for the Ecosystem

Tool authors who build on the TypeScript compiler API (typescript package, Language Service, ts.createProgram) are in an uncertain position. The Go compiler exposes a different API surface, and the current TypeScript Language Service API is deeply tied to the JavaScript runtime. Tools like ts-morph, typescript-eslint, and any language server built on ts.LanguageService will need updates. The TypeScript team has signaled they will maintain the existing JavaScript API through a compatibility shim for some period, but the long-term direction is the Go-native API.

For application developers, 6.0 is a good forcing function to clean up old configuration. If your tsconfig.json still has "module": "commonjs" and "moduleResolution": "node" for a project targeting modern Node.js, now is the time to migrate to node16 or nodenext. If you have experimental decorators enabled and haven’t migrated to the ECMAScript decorator standard that landed in TypeScript 5.0, the clock is running. TypeScript 6.0 does not remove these things overnight, but they are no longer the path forward.

The Long View

TypeScript went from a Microsoft research project in 2012 to one of the most widely adopted language tools in existence. The self-hosted compiler was a reasonable and principled choice for most of that journey. At the scale TypeScript operates now, with millions of projects depending on it and CI pipelines waiting on tsc, the JavaScript runtime is a genuine bottleneck.

Version 6.0 is the compiler team doing the necessary housekeeping before a significant architectural transition. The features and removals are not arbitrary; they each reduce the surface area that the Go port needs to handle and move the ecosystem toward idioms that compose better with the tools that are going to matter in 2027 and beyond.

Installing the RC is straightforward:

npm install -D typescript@rc

The upgrade path for most projects will mean addressing a handful of configuration deprecations and possibly explicit type annotations on exported declarations if you enable isolatedDeclarations. Neither is dramatic work. The payoff, once the Go compiler ships and your CI drops from ninety seconds to eight, will seem obvious in retrospect.

Was this interesting?