owned this note
owned this note
Published
Linked with GitHub
# Why are const enums bad?
I always forget why, so time to write it down!
They interfere with `isolatedModules` support and transpilers. Imagine that a transplier will convert your TypeScript code into Javascript, without parsing the typings of the dependencies. That means if you `import { SomeConstEnum } from 'dependency'` in your code, it doesn't know it's a const enum, or an interface, or a js object, and as such the transpiler leaves the import there.
The problem with this is that now, if you parse the javascript you just emitted, the named import will fail. In the raw JavaScript ES module produced, you expected TS concepts like `type` and `interface`, and of course `const enum` to be dropped, and all your `SomeConstEnum.foo` references to be replaced with literals, but that can't happen without knowing what the thing was in the first place.
The point of isolated transpilation is quick development. You can have thousands of packages in your repo. If you don't need to parse dependency types, you can transpile massive quantities of code, all in parallel, in milliseconds rather than minutes.
## But they've never caused me problems before!
Here are the rules - you can use them in your project; just don't export them. You might have been following the rules all along.
Or, you might have been breaking them, and just never heard about issues. Often consumers build their typescript project without using `isolatedModules`, so typings are fully parsed and well known.
## When alternatives are there?
Various advances in TypeScript have made const enums less interesting.
For example, rather than enums, use union types.
Instead of this:
```ts=
const enum Foo {
bar = 'bar',
baz = 'baz'
}
function myFunc(foo: Foo) {
}
myFunc(Foo.bar);
```
Do this:
```ts=
type Foo = 'bar' | 'baz';
function myFunc(foo: Foo) {
}
myFunc('bar');
```
It has the same effect and same type safety, without requiring an extra import.
In cases where the literal values are important, export/import individual values:
Using the const enum above you might do this:
```ts=
import { Foo } from '...';
console.log(Foo.bar);
```
Instead, export them as individual values:
```
export const FooBar = 'bar';
export const FooBaz = 'baz';
```
...and import what you need:
```ts=
import { FooBar } from '...';
console.log(FooBar);
```
This means that thanks to tree shaking, your bundle is able to trim unused values, leading to smaller sizes.
## What is forbidden
Exporting const enums! Do not export them! They block downstream dependencies from transpiling things individually and require them to know the full typings of all dependencies. This creates headaches for large scale apps.
If you really need to export a collection of values, you have options:
1. Export individual names. Best tree shaking. Bad ergonomics because they aren't nicely tucked into a namespace.
```ts=
export const bar = 'bar';
export const baz = 'baz';
```
2. Export dictionaries. Good ergonomics, but minimizers will likely not remove unused keys.
```ts=
export const Foo = {
bar: 'bar',
baz: 'baz'
};
```
## Additional resources
Even the Typescript team will agree on this one.
https://github.com/microsoft/TypeScript/issues/5219
https://github.com/Microsoft/TypeScript/issues/30590
https://github.com/facebook/create-react-app/pull/4837#issuecomment-430107471
Quote from Daniel Rosenwasser:
> `const enums` are often misused. Sure, they can be great for the internals of a library to get some speed improvements, but they generally shouldn't be used outside of that scope.