-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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] Inline const enum values in TypeScript #128
Comments
+1 in a big, big way. I have const enums all over my automatically generated typescript definitions for my back end. Our backend uses enums in most APIs since the back end is in C#. All of these enums are in namespaces that contain interface definitions and const enum definitions. In our current implementation, all of these things are compile time only entities because the const enums get compiled to const numbers. |
This comment has been minimized.
This comment has been minimized.
@zhusjfaker The problem you mentioned is not related to const enums, so it's irrelevant for this issue. Please file a new issue if you have a problem instead of adding on to an existing one. Also for what it's worth this looks to me like a configuration issue with esbuild-loader and Webpack, not with esbuild itself. |
+1 This would be really cool 😊 Any idea when it will be implemented or still not planned? Thanks! |
I put some time into this and managed to come up with #534 (helps in case of local const enums) |
Would it be better to to always inline enums regardless of const-ness? This might disincentivize people from using const enums in the first place. |
@DanielRosenwasser why would always inlining enums dis-incentivize people from using const enums? |
also came up recently here: leeoniya/uPlot#444 (comment) |
When building for production? Sure, it's smaller and more efficient to inline a non-const enum when possible. The issue here is that inlining enums (const or not) is hard. There were some efforts and discussion in #534. You can go have a look but the short version is:
I personally think there's a lot of value in 2. and I hope it will eventually be done. |
I agree inlining enums (const or not) could have benefits in certain situations. It requires probably a deeper analysis, though. For anyone considering that, my initial thoughts are:
Thus, this gets complicated quickly, and requires additional checks for the type of the enum value |
Why would you consider inlining case you just showed? It's not const and like you said it is not safe. export const enum Flags {
One = 1,
Two = 2,
Three = 4,
} I can say that my use case would be that case alone and nothing else and it would have a large benefit. This is the single construct that is stopping me from using esbuild. |
@gregveres My comment is a general comment on cases to consider when thinking about inlining enums (const or not, as mentioned by previous commenters in this issue). The snippet is only to show the idea that inlining values that are function calls is unsafe Personally, I have not used You're saying that the benefit would be huge - did you happen to run any benchmarks to support that opinion? |
@Gelio No I have not. But even from the amount of code generated and sent down the client there are good wins. Using enums is great for improved code readability. Using const enums allows you to have long, meaningful identifiers in your source code, knowing that they will be replaced with a single character whenever they are used in your code. And that there is no code generation for the actual const enum itself. This is not the case with a non-const enum. There is a bunch of code generated for the definition of the enum itself and then there is the identifier left in your code every where you use them. I have a fairly large app with hundreds of const enums and thousands of references to those enum values. |
@Gelio I wanted to say the same thing as gregveres. enum FileMode { Read, Write, ReadWrite }
// Becomes at declaration:
var FileMode;
(function (FileMode) {
FileMode[FileMode["Read"] = 0] = "Read";
FileMode[FileMode["Write"] = 1] = "Write";
FileMode[FileMode["ReadWrite"] = 2] = "ReadWrite";
})(FileMode || (FileMode = {}));
// Becomes at use site:
import { FileMode } from "./other-file";
var x = FileMode.ReadWrite;
// On the other hand, const enum:
// Becomes _nothing_ at declaration
// Becomes at use:
var x = 3; // Might be further inlined or constant-folded by minifier In my app I have hundreds of const enums (they're generated automatically), so I'd love if the production codegen could use the latter pattern. Not because I think the code will be faster but because the codegen will be much, much smaller. |
@jods4 are your enums generated automatically from the backend? |
@gregveres yes, exactly. We generate a lot of TS front-end stuff from back-end C# sources. |
Just noticed the crappy output from the official TypeScript compiler for string enums. 🤨 enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
} Compiles to: var Direction;
(function(Direction2) {
Direction2["Up"] = "UP";
Direction2["Down"] = "DOWN";
Direction2["Left"] = "LEFT";
Direction2["Right"] = "RIGHT";
})(Direction || (Direction = {})); When this would do: var Direction = {"Up":"UP","Down":"DOWN","Left":"LEFT","Right":"RIGHT"}; Man, enums in TS are wonk. I'm inclined to just typing out the whole thing and manually declaring the types instead. 🙄 |
@mindplay-dk Can I ask you a question? I have hundreds of enums in my code. The majority of them are defined in the back end (c#) and then automatically converted to TS and defined as const numeric enums. I have never understood the real attraction of string based enums. I guess I understand that in JS that doesn't have enum support, strings were a reasonable way to create readable code because the string was essentially the name of the enum value. But with TS, where the enum value is an identifier that can be compiled away to a single number, what do you see as the value of using a TS enum with string values? I am trying to understand so I can be more enlightened and possibly code better. Thx. |
@gregveres Debugging. 🙂 If your program breaks, and your entire program state is just integers - yikes. I wouldn't prioritize micro-performance or memory usage of enums, not by default; not unless we're talking about highly optimized code, like a game or something. It's also possibly useful for run-time type-checking - like parameter assertions in your functions. If every |
@mindplay-dk That makes sense. Thanks. |
+1 |
Working with a client with a large TS codebase, converting their babel-based system to esbuild and we ran into an issue that turned out to be caused by this behavior. Essentially they have a number of modules, e.g. "common-stuff" and "app1" where "app1" imports things from "common-stuff" which in turn uses They are using some features that prohibit Getting inlining of const enum values into esbuild would be |
In general esbuild requires the I believe that // enum.ts
export declare const enum Foo { A, B } // entry.ts
import { Foo } from "./enum"
console.log(Foo.A) If I compile this using I'm wondering how this worked with that client since Babel doesn't seem to support |
perhaps via https://github.com/dosentmatter/babel-plugin-const-enum ? |
There was/is a mix of ts-loader, tsc -b and babel (with plugins) in use within the codebase I'm referring to. |
FWIW Babel has now implemented this directly: babel/babel#13324 |
From that PR:
AFAIR this is essentially what I did in #534. Sadly, there is no attention paid to that PR. |
And they went a little bit further:
It's not perfect but it's a welcome improvement over the crazy default |
Yup, good point. I suppose I could include that in #534 if there's interest in that. |
I was about to file an issue and I found this. So it seems that |
Is any update on |
I just shipped cross-module enum inlining. Details are in the release notes: https://github.com/evanw/esbuild/releases/tag/v0.14.7. I have not yet updated tree shaking to account for this though, so using enums this way will currently still result in potentially-unused generated enum definition objects. I will consider this feature to be complete once tree shaking is updated. |
@evanw I just upgrade the GitLens codebase to 0.14.7 to take advantage of the new I've seen it with numeric enums like: export const enum ReferencesQuickPickIncludes {
Branches = 1 << 0,
Tags = 1 << 1,
WorkingTree = 1 << 2,
HEAD = 1 << 3,
BranchesAndTags = Branches | Tags,
} And string enums: export const enum ViewBranchesLayout {
List = 'list',
Tree = 'tree',
} Am I missing something, or is my setup getting in the way (using webpack and the |
This benefit is only for people using esbuild as a bundler. It won't work if you're using another bundler since the inlining happens during bundling. |
Damn, OK thanks 😄 |
Tree shaking for cross-module inlined enum values has been implemented. This feature should now work the way I intend for it to work. Specifically:
|
I've found an edge case for this (not filing a separate issue as I'm not sure you want to support it): namespace A {
export enum Foo { A, B }
export const enum Bar { C, D }
}
declare namespace B {
export enum Foo { A, B }
export const enum Bar { C, D }
}
console.log(A.Foo.A, A.Bar.C, /* not defined: `B`: B.Foo.A, */ B.Bar.C); The code above compiled with TS successfully runs (as |
I believe the problem there is You can see this for yourself on the TypeScript playground. If you turn on
You can use esbuild and TypeScript without |
Got it, thanks! Thank you also for the pointer to |
Right now
const enum
statements in TypeScript are treated like regularenum
statements. This is consistent with how the TypeScript compiler itself behaves when theisolatedModules
setting is enabled.But it'd be nice if the values were inlined, since that would be better for run-time performance and code size. It should be possible for esbuild to do this since it bundles everything end-to-end and has the original TypeScript source code.
I'm not going to do this right now but I'm adding this issue now so I don't forget.
The text was updated successfully, but these errors were encountered: