Skip to content
ExponaBLOG
JAVASCRIPT & TYPESCRIPT · DAY 063 / 100

Erasable syntax only: a practical guide for developers

Why many teams are moving away from enums.

Expona Admin6 min read

Over the past few years, I've watched TypeScript teams—from startups to enterprises—gradually shift away from certain language features simply because modern tooling changed the calculus. One phrase has become increasingly relevant in those discussions: erasable syntax only. It's not just a technical constraint; it's a philosophy that's reshaping how we structure TypeScript code.

Node can now strip types and run TS directly

For most of TypeScript's history, running a .ts file required a build step. You'd compile to JavaScript first, then execute. That friction was just part of the workflow—a cost we accepted for type safety.

Node.js versions 20.6.0 and later changed this equation. With the --experimental-strip-types flag (and now production-ready in newer versions), Node can parse TypeScript syntax, strip away all type annotations, and execute the result directly. No separate build, no intermediate compilation step.

I first tested this on a small CLI tool in one of our projects. Instead of setting up ts-node or a build script, I simply ran:

typescript
node --experimental-strip-types src/index.ts

It worked. The types vanished at runtime; the code executed. This felt genuinely different from what we'd done for years. But this capability opened a door to a harder question: what syntax can actually be stripped?

Type stripping supports erasable syntax only

Not all TypeScript syntax can be erased. Type annotations, generics, interfaces, and type aliases exist purely for the compiler. When you strip them, nothing breaks because they never run anyway.

But certain features—enums and namespaces most notably—are not purely type-level constructs. They generate actual JavaScript code. An enum becomes an object at runtime. A namespace becomes a variable scope. These are transformations, not erasures.

When Node strips types, it can handle this:

typescript
interface User {
  id: number;
  name: string;
  role: "admin" | "user";
}

function greet(user: User): string {
  return `Hello, ${user.name}`;
}

const person: User = { id: 1, name: "Alice", role: "user" };
console.log(greet(person));

All the type annotations simply disappear, leaving valid JavaScript. But this fails:

typescript
enum Role {
  Admin = "admin",
  User = "user"
}

const myRole: Role = Role.Admin;
console.log(myRole);

The enum needs to be transformed into an object literal. Node's type stripper can't do that—it doesn't have a transformation engine, only erasure logic. The same applies to namespaces, which need to be converted to module patterns or immediately invoked function expressions.

This distinction matters because teams building tools, libraries, or services on Node now face a choice: stick with erasable syntax only, or maintain a separate build step for non-erasable features.

Enums and namespaces need transformation

Enums have been controversial in TypeScript circles for years. Some communities treat them as an anti-pattern. Others rely on them heavily. But one truth is undeniable: they're not erasable.

Here's what happens during a normal TypeScript compilation:

typescript
enum Status {
  Pending = 1,
  Active = 2,
  Archived = 3
}

const current: Status = Status.Active;
console.log(current);

TypeScript's compiler transforms this to:

typescript
var Status;
(function (Status) {
  Status[Status["Pending"] = 1] = "Pending";
  Status[Status["Active"] = 2] = "Active";
  Status[Status["Archived"] = 3] = "Archived";
})(Status || (Status = {}));

const current = Status.Active;
console.log(current);

This is intentional design. Enums provide both forward and reverse mapping, enabling patterns like converting a number back to a string. But that magic requires code generation. A stripper can't do this; only a transformer can.

I've seen teams migrate away from enums specifically to use erasable alternatives. Instead of:

typescript
enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue"
}

let myColor: Color = Color.Red;

They now write:

typescript
const Color = {
  Red: "red",
  Green: "green",
  Blue: "blue"
} as const;

type Color = typeof Color[keyof typeof Color];

let myColor: Color = "red";

It's more verbose, but it's erasable. The object disappears at runtime, leaving just the string value. TypeScript's const assertion ensures the type is narrowed correctly.

Namespaces face the same issue. While many teams have moved to ES modules entirely, older codebases sometimes use namespaces for organization. Modern tools and the desire for simpler, faster type stripping has nudged teams toward modules instead.

TS 5.8 added --erasableSyntaxOnly

In response to this landscape, TypeScript 5.8 introduced the --erasableSyntaxOnly compiler option. This flag enforces at the type-checker level that your code uses only erasable syntax.

When you enable this flag in your tsconfig.json:

typescript
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "erasableSyntaxOnly": true
  }
}

The TypeScript compiler will reject any enum or namespace definitions. If you try to compile the Color enum example above, you'll get an error immediately during type checking, before you even attempt to run the code.

This is a circuit breaker. It prevents you from accidentally using non-erasable syntax, especially useful in teams where consistency matters. I've seen it adopted in projects that are either:

  • Planning to use Node's native type stripping in production soon
  • Targeting strict, fast compilation pipelines where predictability is critical
  • Building libraries that need to support both traditional bundling and modern type stripping

The flag doesn't change how TypeScript works—it just makes the constraint explicit. It's similar to noImplicitAny: it tightens a rule and forces you to think about it.

Common mistakes

Mistake 1: Assuming enums will work with Node's type stripper. Many developers encounter the runtime error after assuming an enum would "just work" like type annotations do. Test early if you're relying on direct Node execution.

Mistake 2: Forgetting about reverse mapping. If you're migrating an enum to a const object for erasability, remember that numeric enums have reverse mapping. You'll need to handle that manually or accept that the pattern changes.

typescript
// Old enum with reverse mapping
enum Status {
  Pending = 0,
  Active = 1
}
console.log(Status[0]); // "Pending"

// const object doesn't have this
const Status = { Pending: 0, Active: 1 } as const;
// Status[0] is undefined—you need to build reverse mapping if you need it

Mistake 3: Not checking tsconfig.json.settings. If you enable erasableSyntaxOnly, existing code with enums will fail type checking. Make sure the entire team understands the change and the migration path.

Mistake 4: Mixing striping and bundling strategies. If part of your codebase is type-stripped and part is bundled, you can end up with subtle inconsistencies. Be deliberate about which parts use which approach.

Key takeaways

  • Node.js can now execute TypeScript directly using type stripping, removing the need for a separate compilation step for codebases that use only erasable syntax.
  • Erasable syntax means anything that exists purely at the type level—annotations, interfaces, generics, type aliases—can be safely discarded without affecting runtime behavior.
  • Enums and namespaces are not erasable; they generate runtime code and require transformation during compilation, making them incompatible with simple type stripping.
  • Teams avoiding enums often replace them with const objects paired with type inference, trading some ergonomics for the benefit of working with stripper-friendly tooling.
  • The --erasableSyntaxOnly flag, added in TypeScript 5.8, enforces erasable syntax at the type-checker level, helping teams maintain consistency and preparing code for modern execution environments.
  • Choosing erasable syntax only is not a technical requirement today, but it's becoming a practical preference for teams optimizing for faster, simpler tooling and direct Node.js execution.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles