· 6 min read ·

TypeScript 6.0 and the Case for Never Compiling Again

Source: typescript

TypeScript 6.0 is out, and the major version bump carries more meaning than usual. The 4.0 and 5.0 releases added large feature sets but did not fundamentally change what TypeScript was. This release formalizes a direction the team has been signaling for several years: TypeScript as a layer of erasable syntax on top of JavaScript, rather than a language that compiles to it.

The central addition is the --erasableSyntaxOnly compiler flag. Enable it and TypeScript will reject any syntax that requires code generation to function. That means no enum declarations, no value-bearing namespace blocks, no parameter properties in constructors, and no legacy decorators via experimentalDecorators. What remains is the subset of TypeScript that a simple type-stripping tool can remove, leaving behind valid JavaScript with zero behavior change.

What Erasable Actually Means

Most TypeScript syntax is already erasable. Type annotations, interface declarations, type aliases, generic parameters, as casts, and the ! non-null assertion operator all vanish cleanly when stripped. The JavaScript that remains is identical to what you would have written without them.

// Fully erasable — strip the types and you have valid JS
function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

const result = clamp(15 as number, 0, 10);

A handful of TypeScript constructs do not work this way. They generate JavaScript that did not exist in the source. enum is the most prominent:

// This generates a JavaScript object — not erasable
enum Status {
  Pending,
  Active,
  Closed,
}

The compiler emits something like this:

var Status;
(function (Status) {
  Status[(Status["Pending"] = 0)] = "Pending";
  Status[(Status["Active"] = 1)] = "Active";
  Status[(Status["Closed"] = 2)] = "Closed";
})(Status || (Status = {}));

That generated IIFE has no correspondence to anything in the original TypeScript. It is code that TypeScript invented. The same applies to namespace blocks that contain values, and to parameter properties:

// Parameter property — generates assignment code in the constructor
class Service {
  constructor(private readonly db: Database) {}
}
// Emits: this.db = db; inside the constructor body

With --erasableSyntaxOnly, these patterns become compile errors. The compiler enforces that your TypeScript code stays within the erasable boundary.

Why Node.js Changed the Equation

This flag did not emerge from aesthetic preferences about type-level purity. It emerged from a concrete runtime change: Node.js 22.6.0 shipped --experimental-strip-types in mid-2024, and subsequent Node.js releases stabilized it. You can now run .ts files directly in Node without a build step.

node --experimental-strip-types server.ts

Under the hood, Node delegates to Amaro, a wrapper around oxc-transform. Amaro is a type stripper, not a compiler. It removes type annotations and does nothing else. It does not understand enums. It cannot emit the IIFE that enum requires. If your TypeScript file contains enum Status { ... }, Node will throw a parse error or silently mishandle it.

Tools like tsx (which uses esbuild) and ts-node handle enums by fully compiling, but they carry overhead and do not reflect the direction the ecosystem is moving. The native Node.js approach is deliberate about being a stripper, not a compiler. TypeScript 6.0’s --erasableSyntaxOnly is the language’s way of meeting that runtime halfway.

Replacing What You Lose

Dropping enums does not have to mean losing the intent behind them. The modern pattern is a const object with an inferred type:

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

type Status = typeof Status[keyof typeof Status];
// type Status = 'pending' | 'active' | 'closed'

This pattern is fully erasable. The const object is plain JavaScript. The type alias disappears at strip time. The resulting JavaScript is exactly what you wrote. You get the same exhaustive narrowing benefits and the same auto-complete experience in editors, with the bonus that the values are actual strings rather than numeric indices you have to remember not to rely on.

For namespaces, the answer has always been modules. ES module syntax has been the right tool for encapsulation since Node.js and browsers standardized on it, and TypeScript’s own documentation has acknowledged this for years. The --erasableSyntaxOnly flag just makes the preference enforceable.

Parameter properties require a bit more ceremony:

// Before
class Service {
  constructor(private readonly db: Database) {}
}

// After
class Service {
  readonly #db: Database;
  constructor(db: Database) {
    this.#db = db;
  }
}

The verbose form is longer, but it is also more readable for anyone not already fluent in TypeScript’s constructor shorthand. The explicit declaration and assignment leave no ambiguity about what is happening.

The TC39 Connection

The TC39 Type Annotations proposal is the deeper context here. It proposes adding type annotation syntax to JavaScript itself, as syntax that JavaScript engines would parse and ignore. If that proposal advances, you would eventually be able to write typed JavaScript in the browser or in Node without any tool involvement. The proposal has been shaped significantly by TypeScript’s syntax, but it only covers erasable forms. It has no path to supporting enum or namespace because those require behavior, not just types.

TypeScript 6.0’s --erasableSyntaxOnly is effectively a TC39 compatibility mode. Code written within that constraint will be syntactically valid under the Type Annotations proposal if it ever reaches the language. Whether that proposal reaches Stage 4 is uncertain, but the alignment between TypeScript’s erasable subset and what TC39 can realistically standardize is not a coincidence.

Module Resolution Cleanup

Alongside the erasable syntax work, TypeScript 6.0 continues cleaning up module resolution options that have accumulated over the language’s twelve-year history. The classic --moduleResolution node option, which has been soft-deprecated for a while, is gone. The supported modes are node16, nodenext, and bundler.

This matters for any project that has put off updating its tsconfig.json. The node resolution mode predates ES modules and has incorrect behavior for packages that ship both CJS and ESM. Removing it forces codebases to pick a correct resolution strategy. If you are writing an application bundled by Vite or webpack, bundler is the right choice. If you are writing a Node.js library, node16 or nodenext handles dual-format packages correctly.

{
  "compilerOptions": {
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "erasableSyntaxOnly": true
  }
}

Isolated Declarations Graduates

isolatedDeclarations, which shipped as experimental in TypeScript 5.5, reaches stable status in 6.0. The flag requires that every exported declaration has enough type information to generate a .d.ts file without cross-file inference. That constraint enables build tools to generate type declarations in parallel without waiting on the full type checker, which is a meaningful speedup for large monorepos.

The practical effect is that you cannot export something like this:

// Not allowed under isolatedDeclarations
export const config = buildConfig({ env: 'production' });
// TypeScript cannot infer the return type of buildConfig without checking that file

You have to annotate:

export const config: AppConfig = buildConfig({ env: 'production' });

This is a discipline that pays dividends in large codebases. Build tools like Turborepo and Nx can take advantage of the parallelism it enables, and the explicit annotations reduce the chances of accidentally exporting a wide inferred type that locks in implementation details.

What This Release Signals

The arc of TypeScript’s development from 1.0 in 2014 to 6.0 in 2026 has consistently moved toward better integration with the JavaScript ecosystem rather than deeper divergence from it. The early years added the non-erasable features that gave TypeScript expressive power. The middle years added the module system alignment that made TypeScript usable in modern Node.js and ESM contexts. The recent years have been about tightening the relationship between TypeScript and whatever JavaScript actually does at runtime.

--erasableSyntaxOnly is opt-in. Nobody is removing enum support from the language. Projects with large existing enum usage do not need to migrate immediately. But for new projects, the flag is worth enabling from day one. It keeps your TypeScript code compatible with native stripping tools, with the TC39 proposal if it lands, and with whatever comes after.

The practical message of TypeScript 6.0 is that the more your TypeScript looks like annotated JavaScript rather than a different language, the less work you will do when the ecosystem shifts under you. That has been true for a while. This release makes it easier to enforce.

Was this interesting?