--erasableSyntaxOnly by RyanCavanaugh · Pull Request #61011 · microsoft/TypeScript (original) (raw)

I'm coming here from the 5.8 announcement, looking for how to migrate from enums when using erasableSyntaxOnly.

A popular solution that I see is using an as const object (e.g. as documented in the handbook).

// you can also use number literals const MyEnum = { Foo: "Foo", Bar: "Bar", } as const;

This is usually accompanied with export type (e.g. as in the as enum proposal): export type MyEnum = typeof MyEnum[keyof typeof NodeType];

This lets you use MyEnum as a type representing the union of the variant values (here type MyEnum = "Foo" | "Bar") and on the value side you can use MyEnum.Foo or MyEnum.Bar.
There are a few downsides though:

I don't really care about the last point, and it's easy to use the variant name anyway (it will most likely be interned by the JS engine, so perf should not be a big concern when comparing using a number VS a short string).

On the other hand, I'd like to have a way to keep nominal typing and lightweight type syntax for variants. Here is what I ended up moving to:

const Foo: unique symbol = Symbol("Foo"); const Bar: unique symbol = Symbol("Bar");

const MyEnum = { Foo, Bar, } as const;

type MyEnum = typeof MyEnum[keyof typeof MyEnum];

declare namespace MyEnum { type Foo = typeof MyEnum.Foo; type Bar = typeof MyEnum.Bar; }

export {MyEnum};

This uses unique symbol for nominal typing, which requires either a static readonly class property or a simple const. Using a class prevents you from using MyEnum as the union of all variant values, so constants must be used. I then combine it with a type namespace to provide type-level support for MyEnum.Foo.

Obviously, this approach is even more inconvenient at the implementation side, but I find it more convenient on the consumer side. The implementer side complexity is less relevant if using codegen. Symbol is also skipped in JSON.stringify for both keys and values, so if you rely on it then it won't work and you'd need a branded primitive type if you care about nominal typing. I use schema-guided serialization so it's not an issue for me, but it's worth mentioning.

The "record of symbols" approach addresses the complaints in this blog post about support variant annotations such as @deprecated: you can annotate in the namespace, or the symbol values.

I hope this message helps others when evaluating available solutions when migrating away from TS enums.