Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Add "preserveConstEnums" option to transform-typescript #6476

Closed
ForbesLindesay opened this issue Oct 13, 2017 · 33 comments · Fixed by #13324
Closed

[Feature Request] Add "preserveConstEnums" option to transform-typescript #6476

ForbesLindesay opened this issue Oct 13, 2017 · 33 comments · Fixed by #13324
Labels
area: typescript outdated A closed issue/PR that is archived due to age. Recommended to make a new issue

Comments

@ForbesLindesay
Copy link
Contributor

When using the typescript compiler, there is an option to "preserve const enums", which essentially just treats const enums exactly the same as non-const enums. It would be really useful to have the same feature in the babel transform.

Input Code

export const enum E {
    A = 1
}

Babel Configuration (.babelrc, package.json, cli command)

{
  "plugins": [
    ["transform-typescript", {"preserveConstEnums": true}]
  ]
}

Expected Behavior

export let E;

(function (E) {
  E[E["A"] = 1] = "A";
})(E || (E = {}));

Current Behavior

Throws: 'const' enums are not supported

Possible Solution

Add an option that makes const enums behave exactly the same as regular enums.

Context

I want to be able to use babel to transform my typescript in development, as this will be much faster than doing a babel pass and a typescript pass, and babel is needed for other plugins. In production, I can use the slower typescript transform, which handles const enums properly.

@babel-bot
Copy link
Collaborator

Hey @ForbesLindesay! We really appreciate you taking the time to report an issue. The collaborators
on this project attempt to help as many people as possible, but we're a limited number of volunteers,
so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant Slack
community that typically always has someone willing to help. You can sign-up here
for an invite.

@Jessidhia
Copy link
Member

I'm not sure if this would be a good thing to support... in my opinion, export const enum {...} should actually be a compile error in TypeScript too, as it's impossible to use it correctly with --isolatedModules.

@azz
Copy link
Member

azz commented Oct 29, 2017

I've always been confused by that flag. Why not just remove const from the enum rather than using a compiler flag?

@ForbesLindesay
Copy link
Contributor Author

The reason is that const enums are a useful optimisation in production, and I can handle them by just using the full typescript compiler for the prod build.

In development. I want to make the build as fast as possible by using babel for everything and compiling each module independently.

@zheeeng
Copy link

zheeeng commented Feb 11, 2018

I use const enum in declaration files(not exporting it), it is useful for distinguishing custom obj type.

@tschoartschi
Copy link

tschoartschi commented Mar 28, 2018

The const enum feature of Typescript is really nice. If file size is a concern, it makes sense to use const enums where possible. Just compare the output of the Typescript compiler:

image
Try it yourself: https://www.typescriptlang.org/play/index.html

For us, loosing the const enum feature would be almost a dealbreaker. We use a lot of enums and if they would be all compiled to the "regular" enum syntax this would add a considerable amount of bytes to our code. Which is not neglectable in our case.

@nojvek
Copy link

nojvek commented Jun 9, 2018

const string enums are even more fantastic, because then you're essentially just dealing with strings.

@alloy
Copy link

alloy commented Jul 31, 2018

@andy-ms Do you see any possibility to support this? (Although our specific use case is to generate a unique type that doesn’t incur any runtime cost, i.e. const enum Foo {} would not emit anything and only be used at compile time, so that might be slightly different.)

EDIT: We went with unique symbols for our use-case.

@nicoburns
Copy link

@nojvek Is there any advantage to a string const enum type over a union of string literal types?

type COUNTRIES = "austria" | "germany" | "greece" | "brazil";

@fbartho
Copy link

fbartho commented Sep 10, 2018

@nicoburns Autocomplete / usability by the developers?

@mattkrick
Copy link
Contributor

@nicoburns

here's an example where it'd be useful:

const defaultCountry = COUNTRIES.GERMANY

in my own app, I use const enums for my color palette, where it's really nice to avoid hex strings

@nicoburns
Copy link

@mattkrick Can't you also do const defaultCountry = "germany";, which will typecheck just as strictly? Is it about keeping the code size down with an integer rather than a string?

@majelbstoat
Copy link

majelbstoat commented Sep 11, 2018

As @mattkrick says, sometimes the values are not as clear as "germany" and const string enums can be very clarifying, while still compiling to the raw string, avoiding any overhead.

But partly, it's an expectations thing. const string enums are part of typescript, they're useful and not having them is surprising to developers (even if the reason for their current absence is reasonable). It also means, for example, babel-node can't be a drop-in replacement for ts-node, which is a shame.

@xzilja
Copy link

xzilja commented Jan 6, 2019

I came across an issue related to this today. Imagine if you are in a monorepo project that has firebase, react-native and types packages.

Using normal enum causes issue inside firebase package if you use their serverless functions that run npm install during deployment. Since enums are transpiled to something like

const types_1 = require("@MyApp/types");
// ...
types_1.SIZE.BIG

and @MyApp/types is not available on npm, we get an error. Using const enum solves this as it is now transpiled to just "BIG"

But now, we get an issue in react-native project that uses babel to transpile typescript unless we add additional step to run typescript compiler, which feels like an overkill for this one feature. Falling back to type SIZE = "BIG" | "SMALL" | "MEDIUM" is only solution now, but we loose nice auto complete and devs will always need to spell these string correctly.

@karlhorky
Copy link

karlhorky commented Jun 24, 2019

Prior art (Sucrase): alangpierce/sucrase#423 (comment)

For example, ... const enums are treated as regular enums rather than inlining across files.

@dosentmatter
Copy link
Contributor

dosentmatter commented Jul 4, 2019

Is it possible to write a babel plugin to run before transform-typescript that removes the const before enum? You can have the plugin only run in development. I don't know how to write babel plugins, but converting all const enum into enum sounds simple. I'll try looking into in it my free time.

@dosentmatter
Copy link
Contributor

dosentmatter commented Jul 4, 2019

I went through the babel-handbook. It's pretty simple. The const is just controlled by a boolean in the AST:

Node {
  type: 'TSEnumDeclaration',
  start: 159,
  end: 203,
  loc:
   SourceLocation {
     start: Position { line: 7, column: 0 },
     end: Position { line: 10, column: 1 } },
  const: true, // BOOLEAN HERE
...

Just traverse the whole AST and set them all to false.

Here is what I have so far (you guys can run it in node). I still need to read the part on how to create a plugin.

$ npm install @babel/parser @babel/traverse @babel/types @babel/generator
// const-enum.js
const parser = require('@babel/parser');
const { default: traverse } = require('@babel/traverse');
const types = require('@babel/types');
const { default: generate } = require('@babel/generator');

const code = `
const enum Enum {
  A = 1,
  B = A * 2
}
`.trim();
console.log(`Before:
${code}
`);

const ast = parser.parse(code, {
  sourceType: 'module',
  plugins: ['typescript']
});

traverse(ast, {
  enter(path) {
    if (types.isTSEnumDeclaration(path.node, { const: true })) {
      path.node.const = false;
    }
  }
});

console.log(`
After:
${generate(ast).code}
`.trim());
$ node const-enum.js

@nicolo-ribaudo
Copy link
Member

You can experiment here: https://astexplorer.net/
It's a playground where you can create your own plugin and use it online in real time.

@dosentmatter
Copy link
Contributor

dosentmatter commented Jul 4, 2019

@nicolo-ribaudo I saw that site, but I don't see TypeScript support, only JavaScript. The plugin has to be a TypeScript to TypeScript transpiler. So then you can feed that into @babel/preset-typescript.

@nicolo-ribaudo
Copy link
Member

You can click on the gear next to the parser (in the header), and enable TypeScript support for Babylon 7. (which is the old name of @babel/parser)

@dosentmatter
Copy link
Contributor

dosentmatter commented Jul 5, 2019

@nicolo-ribaudo, using JavaScript + babylon7, I get Unexpected reserved word 'enum' (1:6) for the following code:

const enum Enum {
  A = 1,
  B = A * 2
}

I tried on the babel site and noticed that enabling preset-typescript doesn't do anything. I need to manually add plugin-syntax-typescript to allow enum syntax and plugin-transform-typescript to transform it to JS.

The only combinations that work on AST explorer are JavaScript + one of the following:

  • flow
  • typescript-eslint-parser
  • @typescript-eslint/parser
  • typescript

The only transformer that works is tslint. babelv7 transformer doesn't work with any parser. It complains about enum.

I wonder why I don't have any problems in node, without having to enable plugin-syntax-typescript.

image

@nicolo-ribaudo
Copy link
Member

Did you click on the gear next to "babylon7" and enable it's TypeScript plugin?

@dosentmatter
Copy link
Contributor

dosentmatter commented Jul 5, 2019

Oh, my mistake. I misunderstood the UI and what you said earlier. I thought parser dropdown and the gear was the same thing. Didn't notice the gear was separate settings for the parser. Thanks!

Hmm, now I have the parser working, but not the transformer:
image

Seems like their list of plugins for their babelv7 transformer doesn't have 'typescript'. Their parser has 'typescript' as an available plugin, as seen in the settings gear of the UI.

@dosentmatter
Copy link
Contributor

dosentmatter commented Jul 5, 2019

Actually, I figured out a workaround. I just cloned their repo to run locally with modified code. I changed 'flow' to 'typescript' since both can't be enabled together.

Now everything works:
image

@dosentmatter
Copy link
Contributor

dosentmatter commented Jul 7, 2019

Yay! I finished my plugin, babel-plugin-const-enum.

Add to your .babelrc:

{
  "plugins": [
    "const-enum"
  ]
}

The default is to remove the const keyword.
It also has a transform for #8741 if you use the option "transform": "constObject".

The README has more information.

You can test at the online repl by adding the plugin on bottom left.

Feel free to file issues or send in PRs at the GitHub Repo. This is my first time writing a plugin so there are things that can be improved.

@karlhorky
Copy link

karlhorky commented Jul 8, 2019

Really awesome @dosentmatter !

@ForbesLindesay, considering there are some tradeoffs / footguns with the approach in your original description, is this plugin enough to close this issue?

Maybe along with some documentation about const enums and this plugin in some FAQ or something in the Babel TypeScript docs?

@nicolo-ribaudo
Copy link
Member

Yes, we should definitely link it in our docs.

@orta
Copy link
Contributor

orta commented Aug 27, 2019

Docs are updated in master now

@qm3ster
Copy link

qm3ster commented Mar 20, 2020

Why is removeConst the default? What are the tradeoffs? (considering the transform-typescript doesn't typecheck anyway)

@dosentmatter
Copy link
Contributor

dosentmatter commented Apr 12, 2020

Why is removeConst the default? What are the tradeoffs? (considering the transform-typescript doesn't typecheck anyway)

@qm3ster, the reason I chose removeConst to be default is because it relies on regular enum transpilation of @babel/plugin-transform-typescript and it is just the simpler solution.
I didn't want the default to be constObject, because that may be unexpected for developers. They have to wrap their head around why the constObject works, when they may just want const enum to work and not care about how or why. I left constObject for more "advanced" users.
Although, constObject should still work correctly without a minifier to inline enum access.

@vegerot
Copy link

vegerot commented May 5, 2020

What's the status on this? Would really like to use const enums in my TypeORM enum columns

@dosentmatter
Copy link
Contributor

@vegerot, I saw you commented #8741 (comment) and you got a reply on that issue to use babel-plugin-const-enum. That issue is talking about using constObject option of babel-plugin-const-enum. If you want to removeConst, babel-plugin-const-enum allows you to do that. See #6476 (comment).

If you need the plugin to only run on TS files, you can use the babel-preset-const-enum. I am the author of the plugin so you can file an issue if you have any problems.

@vegerot
Copy link

vegerot commented May 6, 2020

Thank you. I'll look into it

@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Nov 3, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: typescript outdated A closed issue/PR that is archived due to age. Recommended to make a new issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.