TypeScript 6.0 Completes What Five Years of Minor Versions Started
Source: typescript
TypeScript’s major versions arrive roughly every two to three years. Version 4.0 shipped in August 2020, 5.0 in March 2023, and now 6.0 lands in early 2026. Each major bump has historically been a chance to clean house: drop deprecated settings, change defaults that were too conservative to change mid-series, and mark a new chapter in the language’s direction.
TypeScript 6.0 does exactly that, but it arrives at a particularly meaningful moment. The JavaScript runtime ecosystem spent the last two years broadly adopting native TypeScript support, and 6.0 formalizes TypeScript’s response to that shift in ways that the 5.x series could only lay groundwork for.
What “Erasable Syntax” Actually Means
The most consequential concept in modern TypeScript is erasable syntax, and 6.0 brings it into explicit focus. The idea is straightforward: TypeScript syntax falls into two categories. Some of it can be mechanically stripped from source code without any transformation, leaving behind valid JavaScript. The rest requires actual code generation.
Type annotations, interface declarations, type aliases, and as expressions are erasable. Strip them out and the program’s runtime semantics are unchanged. Node.js’s --strip-types flag, Deno’s built-in TypeScript support, and Bun’s TypeScript runner all work this way: they remove type-level syntax without running the TypeScript compiler, so TypeScript runs nearly as fast as plain JavaScript with no build step in the way.
enum declarations are not erasable. Neither are TypeScript namespace blocks that contain values, nor parameter properties in class constructors. These require code generation:
// Not erasable — generates a runtime object
enum Direction {
Up,
Down,
Left,
Right,
}
// Direction.Up === 0 at runtime — this value had to be created somewhere
// Not erasable — the constructor body needs rewriting
class Point {
constructor(private x: number, private y: number) {}
// Becomes: constructor(x, y) { this.x = x; this.y = y; }
}
TypeScript 6.0 includes the --erasableSyntaxOnly compiler option, which restricts your codebase to the erasable subset. Enable it, and the compiler flags any enum, value-bearing namespace, or parameter property as an error. In exchange, your TypeScript files are directly executable by any runtime with native type-stripping support, no compilation pass required.
// tsconfig.json
{
"compilerOptions": {
"erasableSyntaxOnly": true
}
}
For Discord bot development or any Node.js 22+ project, this is the option that lets you drop the build step in development entirely. You keep full type checking in CI through tsc --noEmit, but the iteration loop becomes node --strip-types src/index.ts and nothing else.
Module Resolution Finally Gets Cleaned Up
TypeScript’s module resolution options accumulated like sediment over the years. classic was the original, largely incorrect mode. node mimicked Node.js CommonJS resolution from the early 2010s. Then node16, nodenext, and bundler were added as the ecosystem shifted toward ESM, each modeling how a specific class of tools actually resolves imports.
For years, node remained the default in many generated tsconfig.json files even though it does not correctly model how Node.js ESM works. TypeScript 6.0 removes the legacy node and classic module resolution modes. Projects using them need to migrate to bundler for build-tool-managed projects, node16 for Node.js with CommonJS, or nodenext for ESM-aware Node.js.
Alongside this, importsNotUsedAsValues and preserveValueImports are removed in 6.0. They were deprecated in TypeScript 5.0 with an explicit note that 6.0 would be the cutoff, replaced by verbatimModuleSyntax, which gives you a single cleaner model: write import type for types, import for values, and TypeScript will not silently elide or transform your imports in surprising ways.
// Before (TypeScript 5.x with legacy options)
{
"compilerOptions": {
"importsNotUsedAsValues": "error",
"preserveValueImports": true
}
}
// After (TypeScript 6.0)
{
"compilerOptions": {
"verbatimModuleSyntax": true
}
}
The verbatimModuleSyntax behavior is stricter and more predictable. When you import type, TypeScript guarantees it is gone at runtime. When you import a value, TypeScript will not strip it even if the imported binding only appears in type position. This makes TypeScript’s emit behavior transparent rather than clever.
isolatedDeclarations and the Two-Phase Build
TypeScript 5.5 shipped isolatedDeclarations as a way to generate .d.ts files for a module without type-checking the entire program first. The motivation was parallelism: tools like oxc and esbuild can strip types orders of magnitude faster than tsc, but they need declaration files to satisfy downstream consumers in a monorepo. With isolatedDeclarations, each file’s declarations can be computed file-by-file, which lets faster tools take over the emit step.
TypeScript 6.0 builds on this. The pattern it enables is now mature: use tsc --noEmit for correctness checking, use a fast stripper for development output, and use isolatedDeclarations-compliant code to generate declaration files without a full type-checking pass. The TypeScript compiler focuses on what it is uniquely good at, and fast tools do the rest.
For a project to opt into isolatedDeclarations, exported types must be explicitly annotated rather than inferred:
// Won't work with isolatedDeclarations — return type is inferred
export function parseConfig(raw: string) {
return JSON.parse(raw) as Config;
}
// Works — explicit return type allows per-file declaration emit
export function parseConfig(raw: string): Config {
return JSON.parse(raw) as Config;
}
This is a concrete coding discipline change, but one that pays off in type clarity anyway. The inference you lose is usually inference you did not want end consumers depending on.
The Version History in Context
Looking at when TypeScript cuts major versions is useful. Version 2.0 in 2016 established the strict null checks era. Version 3.0 in 2018 brought project references and proper tuple improvements. Version 4.0 in 2020 introduced variadic tuple types and template literal types. Version 5.0 in 2023 was largely about const type parameters, the decorators overhaul, and planting the deprecation flags that 6.0 is now acting on.
The 5.0 release notes explicitly listed importsNotUsedAsValues and preserveValueImports as deprecated with a note that they would be removed in a future major version. TypeScript 6.0 is that version. This is how the TypeScript team handles breaking changes: announce early, deprecate loudly, remove at the major boundary. The approach is conservative to the point of sometimes feeling slow, but it means major version migrations are rarely as painful as the version bump implies.
What This Means in Practice
For projects running TypeScript 5.4 or later with verbatimModuleSyntax already enabled, migration to 6.0 is likely mechanical. For older codebases still using "module": "commonjs" with the default "moduleResolution": "node", there is more work. The migration path is well-documented and the errors are actionable, but it requires understanding what your build target actually is.
The bigger shift is ergonomic. TypeScript 6.0 is the first version where building a new Node.js project without a separate compilation step is a fully supported, explicitly blessed workflow rather than a workaround enabled by third-party loaders. That changes how you structure a project from scratch, particularly for tooling, scripts, and services where the build overhead was always friction rather than value.
The TypeScript compiler is not going away. Type checking at the level tsc provides is not something a fast stripper can replicate, and catching type errors before shipping remains the core value proposition. But 6.0 acknowledges that the compiler’s role in the development loop is optional in ways it was not three years ago, and it equips the language to operate in that world without compromising what makes it worth using.