· 6 min read ·

TypeScript 6.0 and the Slow Erasure of the Build Step

Source: typescript

TypeScript 6.0 is officially out, and the version number alone signals something worth paying attention to. The TypeScript team spent years on the 5.x series, incrementally shipping features like isolatedDeclarations in 5.5, verbatimModuleSyntax in 5.0, and the using keyword in 5.2. A major version bump means breaking changes and a deliberate shift in direction. The direction, increasingly clear from the team’s public discussions and the surrounding ecosystem, is TypeScript as a type annotation layer that runtimes can strip, rather than a language you must compile.

The Erasable Syntax Problem

For most of TypeScript’s history, running a .ts file required a compilation step. You fed it to tsc, ts-node, esbuild, swc, or some bundler plugin, and out came JavaScript. That worked fine, but it created friction: tooling costs, source map complexity, and a conceptual gap between the code you write and the code that runs.

Node.js changed the equation in v22.6 with --experimental-strip-types, which uses the amaro package (a thin wrapper around oxc-transform) to remove TypeScript annotations without type-checking. Deno and Bun had been doing this for years. The appeal is obvious: you write TypeScript, you run the file directly, no build step.

The problem is that not all TypeScript syntax is safely strippable. Type annotations, interfaces, type aliases, and generics are pure additions, no transformation required. But several TypeScript features actually emit JavaScript:

  • Enums compile to an IIFE that creates an object
  • Namespaces (namespace Foo {}) compile to an IIFE as well
  • Parameter properties (constructor(private x: number)) expand to field declarations and assignments
  • Legacy decorators (the pre-TC39 kind) require transformation

A stripper that only removes type syntax will produce broken output for code that uses any of these. Node’s --experimental-strip-types explicitly refuses to process files containing enums or namespaces for exactly this reason.

TypeScript 6.0 addresses this directly with a new --erasableSyntaxOnly compiler option. When enabled, the compiler will error on any syntax that cannot be safely erased without transformation. The point is not to ban enums forever, but to let you opt into a stricter subset of TypeScript that is compatible with native runtime stripping.

// With --erasableSyntaxOnly, this is an error
enum Direction {
  Up,
  Down,
  Left,
  Right
}

// This is fine -- interface is pure type syntax, erased completely
interface Point {
  x: number;
  y: number;
}

// This is fine -- type annotation is erased
function move(p: Point, d: Direction): Point {
  return p;
}

The practical effect is that --erasableSyntaxOnly becomes the recommended flag for any project that wants to use Node.js’s built-in TypeScript support, Deno, or Bun without a dedicated build step.

What This Means for Enums

Enums in TypeScript have always been a source of contention. They are useful, they have clear semantics, and a lot of existing code uses them. But they are also one of the few TypeScript features that cannot be explained purely in terms of type-level operations, which makes them awkward for tools that want to treat TypeScript as a typed superset of JavaScript.

The const enum variant is already handled differently: it inlines values at usage sites and emits no runtime code. --isolatedModules has long disallowed regular const enum across file boundaries for this reason. TypeScript 5.0 made --verbatimModuleSyntax the standard recommendation, pushing developers toward explicit import type for anything that shouldn’t appear in emitted output.

The 6.0 --erasableSyntaxOnly flag is the logical continuation of this pressure. The TypeScript team is not removing enums from the language, but they are drawing a clearer line between “TypeScript as types” and “TypeScript as a language that compiles.” Projects that use enums can continue to use tsc or esbuild. Projects that want native runtime support need to migrate away from them.

For new code, the practical recommendation is to replace most enum usage with as const objects:

// Instead of:
enum Status {
  Pending = 'pending',
  Active = 'active',
  Closed = 'closed'
}

// Use:
const Status = {
  Pending: 'pending',
  Active: 'active',
  Closed: 'closed'
} as const;

type Status = typeof Status[keyof typeof Status];

This is slightly more verbose, but it is plain JavaScript with a type alias on top, erasable without any transformation.

isolatedDeclarations and Build Performance

TypeScript 5.5 shipped isolatedDeclarations, which requires every exported symbol in a file to have an explicit type annotation, enabling parallel .d.ts generation without cross-file inference. TypeScript 6.0 builds on this by making isolatedDeclarations work more smoothly with the new module resolution improvements and tightening the rules around what counts as an inferrable declaration.

For monorepos, this matters a lot. The traditional tsc --build mode processes packages serially, waiting for upstream .d.ts files before type-checking downstream packages. Tools like Bazel, Turborepo, and Nx have long wanted a way to generate declaration files in parallel, without running full type-checking. isolatedDeclarations makes that possible, and build tools have been integrating it rapidly since 5.5.

Module Resolution Cleanup

One of the quieter but meaningful changes in 6.0 is the deprecation of older --module and --moduleResolution modes. Specifically, --module CommonJS combined with --moduleResolution node (the old default, sometimes called node10) is now formally deprecated in favor of --moduleResolution bundler or --moduleResolution nodenext.

This has been coming for a long time. The node10 resolution algorithm does not understand exports fields in package.json, does not support subpath patterns, and does not distinguish between ESM and CJS imports. Keeping it as the default created an enormous amount of confusion around dual-package shipping and .js extension requirements in ESM.

Projects still using the old defaults will see new deprecation warnings under 6.0. The migration is usually straightforward, but it surfaces real issues in codebases that have been implicitly relying on the old resolution behavior.

Breaking Changes Worth Auditing

A few other breaking changes land in 6.0 that are worth knowing:

Stricter class field behavior: TypeScript now more closely aligns class field declarations with the native JavaScript spec, which causes behavioral differences when targeting older runtimes that pre-date native class fields. If you are still targeting ES2021 or earlier and using class fields, audit your output carefully.

--target ES3 and --target ES5 are removed: These targets required the compiler to emit polyfills for features like for...of and Symbol. In 2026, almost nobody is targeting ES3 or ES5 directly; that transformation is better handled by dedicated tools like Babel. Removing them simplifies the compiler internals meaningfully.

Stricter index signature behavior: Accessing a property through an index signature now more consistently returns T | undefined when --noUncheckedIndexedAccess is enabled, closing some gaps in the previous implementation.

The Bigger Picture

TypeScript 6.0 is not a dramatic reinvention of the language. The type system keeps getting smarter in the ways it always has, inference improves, edge cases close. But the structural shift in 6.0 is about the relationship between TypeScript and the JavaScript runtimes that increasingly want to consume it directly.

The --erasableSyntaxOnly flag is the clearest signal. The TypeScript team is acknowledging that there are two valid ways to use TypeScript: as a fully compiled language with its own runtime constructs, and as a typed annotation layer over plain JavaScript. For years, you could mix both in a single project without friction. The 6.0 tooling makes that distinction explicit and gives you the tools to opt into the stricter, more portable subset.

If you are starting a new project in 2026, the combination of --erasableSyntaxOnly, --verbatimModuleSyntax, --isolatedDeclarations, and --moduleResolution bundler gives you a TypeScript configuration that is compatible with native runtime support, fast parallel builds, and clean ESM output. That is a significantly better starting point than the defaults from three years ago.

For existing projects, the migration cost depends almost entirely on enum usage. If your codebase is heavy on enums, you have work to do. If you have been following the as const pattern or using string union types, the upgrade is largely mechanical.

The version number 6.0 is earned. The breaking changes are real, the direction is clear, and the tooling around TypeScript’s place in the runtime ecosystem is finally coherent.

Was this interesting?