TypeScript 6.0: Legacy Removals, Module Cleanup, and the Erasable Syntax Story
Source: typescript
TypeScript versioning has always been conservative. The language went from 1.x to 2.0 in 2016, 2.x to 3.0 in 2018, 3.x to 4.0 in 2020, and 4.x to 5.0 in 2023. Each major bump signaled accumulated breaking changes that could not fit into minor releases, and each one was smaller than users feared. TypeScript 6.0, announced today, follows that same pattern while landing at an unusually significant moment for the broader ecosystem.
What Major Versions Mean in TypeScript
Unlike many projects that treat semver loosely, the TypeScript team has been disciplined about what constitutes a major release. Minor releases (5.1, 5.2, and so on) introduce new language features while preserving backward compatibility with existing TypeScript code. Major releases are specifically reserved for changes that break existing codebases, even when most users won’t notice.
TypeScript 6.0 is a cleanup release. It takes three years of accumulated “we want to remove this eventually” items and schedules their removal. The type system continues growing. The tooling keeps improving. The semantics most users rely on today remain intact.
Erasable Syntax and the Node.js Convergence
The most structurally significant story in TypeScript right now is the convergence with runtimes that execute TypeScript directly. Node.js 22.6 shipped --experimental-strip-types, and Node.js 23 stabilized native .ts file execution by stripping type annotations at load time. Deno and Bun have offered this longer.
This creates a practical tension: runtimes that strip types can only remove annotations. TypeScript features that require code generation, like enum, namespace merging, and legacy parameter properties, cannot be handled by a simple stripping pass. They require an actual TypeScript compilation step.
TypeScript added --erasableSyntaxOnly in the 5.x series to address this. When enabled, the compiler errors on any construct that is not pure erasable syntax, giving you a guarantee that your code can run natively in Node.js 23+ or Deno without a build step. A tsconfig.json configured for this might look like:
{
"compilerOptions": {
"erasableSyntaxOnly": true,
"module": "nodenext",
"moduleResolution": "nodenext"
}
}
With this in place, code like the following produces a compile error:
// Error: 'enum' is not allowed with --erasableSyntaxOnly
enum Direction {
Up,
Down,
}
TypeScript 6.0 builds on this foundation. The legacy constructs that conflict with erasable syntax receive clearer deprecation status, and the compiler surfaces better diagnostics when code written for Node.js native execution uses non-erasable patterns. The practical goal is that a project can commit to the erasable constraint at the config level and have the compiler enforce it consistently, rather than relying on documentation and convention.
Module Resolution: The Long Cleanup
TypeScript’s module resolution has accumulated years of legacy behavior. The original node resolution strategy was designed for CommonJS, before ESM, conditional exports, and the exports field in package.json existed. Newer strategies like node16, nodenext, and bundler are substantially more accurate to what modern runtimes do.
TypeScript 6.0 treats the modern resolution strategies as the expected starting point for new configurations. The old node strategy (sometimes called node10 in internal documentation) becomes explicitly opt-in legacy behavior rather than a common default path. This matters because incorrect module resolution is one of the most frequent sources of TypeScript bugs that only surface at runtime: you import something, TypeScript finds it through the legacy resolver, the actual runtime cannot locate it, and you receive an error the compiler never warned about.
The moduleResolution: bundler option, introduced in TypeScript 5.0, is worth paying attention to here. It matches what bundlers like webpack, Vite, and esbuild do, which aligns with neither CJS Node resolution nor strict ESM Node resolution. For frontend projects, it is the most accurate model available, and TypeScript 6.0 reduces some of the flag combination confusion that made reaching for it awkward in 5.x.
Breaking Changes
Major TypeScript releases come with a deprecation and removal list. The categories that affect the most users:
Legacy decorators. TypeScript shipped experimental decorator support in 2015, years before the TC39 decorators proposal stabilized. The experimental version (--experimentalDecorators) and the Stage 3 decorators that shipped in TypeScript 5.0 produce different runtime behavior and different metadata shapes:
// Legacy (--experimentalDecorators)
function log(target: any, key: string, descriptor: PropertyDescriptor) {
// ...
}
class Service {
@log
fetch() {}
}
// Standard (TC39 Stage 3, TypeScript 5.0+)
function log(value: Function, context: ClassMethodDecoratorContext) {
// ...
}
class Service {
@log
fetch() {}
}
The two systems are not interchangeable. Metadata emitted under --experimentalDecorators with --emitDecoratorMetadata uses a different format from the standardized DecoratorMetadata interface. Code that reads decorator metadata at runtime will break if you switch systems without updating the consuming code.
TypeScript 6.0 continues moving the ecosystem toward standard decorators. Legacy decorator support remains accessible but receives explicit deprecation status. Projects on frameworks like Angular or NestJS that relied on experimental decorators should track their framework’s migration path; most major frameworks have been working on standard decorator support through the 5.x cycle.
Old emit targets. ES3 and ES5 targets have become maintenance burdens. The population of TypeScript users targeting IE-era environments has converged near zero for most production use cases. TypeScript 6.0 narrows the supported emit targets, with the expectation that teams still needing ES5 output run tsc for type checking and use a dedicated transpiler like SWC or esbuild for the actual downleveling step. That split is faster and more reliable for that use case.
Tightened implicit any inference. Several contexts where TypeScript previously inferred any silently under --noImplicitAny in edge cases get corrected. These are cases where users expected an error and did not get one. The fixes are narrow but will surface real issues in codebases that have been accumulating quietly unsound types.
What Stays the Same
The type system itself continues expanding. TypeScript 6.0 carries forward improvements to control flow analysis, inference in conditional types, and narrowing behavior developed through the 5.x cycle. These features are unaffected by the major bump.
The compiler API surface, which powers editors and build tools, maintains compatibility for common patterns. Projects built on ts.createProgram, the language server integration, and transformer APIs won’t require rewrites. The investment tooling authors made during the 5.x era transfers cleanly.
The Go Compiler Context
TypeScript 6.0 arrives while the TypeScript team is separately building a native port of the compiler in Go. Announced in early 2025, the native port targets build times roughly ten times faster than the current Node.js-based implementation. This is a compiler implementation effort; the language version and the implementation are separate tracks.
The two projects are connected in a practical sense. Getting to TypeScript 6.0 with a cleaner surface area, fewer legacy modes, and tighter module semantics makes the Go compiler’s task of reaching full compatibility more tractable. A compiler with fewer special-case behaviors is easier to port accurately, and the cleanup work in 6.0 directly reduces the number of edge cases the Go implementation has to handle. Shipping a major version before the native compiler reaches production is not coincidental timing.
Migration
Most TypeScript 5.x code compiles cleanly under 6.0. The breaking changes are scoped enough that the majority of projects won’t encounter them. The TypeScript team has maintained a strong track record on migration guides, and 6.0 includes tooling to surface the specific patterns that need attention.
The places to audit first: explicit --target es5 or --target es3 in tsconfig.json, heavy use of --experimentalDecorators without a plan to migrate to standard decorators, and --moduleResolution node in projects where ESM correctness matters. For most codebases, that audit comes back short.
TypeScript 6.0 clears accumulated ground, positions the language for the runtime ecosystem forming around native TypeScript execution, and lays a cleaner foundation for the performance improvements the Go compiler port will bring. For most teams, the upgrade is a single-afternoon job.