-
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
Is TDZ avoidance still needed? #2889
Comments
Part of the reason for doing this is indeed TDZ avoidance. It was originally added to work around a severe bug in Safari, but it sounds like it's still relevant despite that bug being fixed. When the TypeScript compiler's build process was switched to esbuild, it was reported that if esbuild did TDZ avoidance at all levels instead of at the top level, the TypeScript compiler would make their parser 5-9% faster: #297 (comment). So I assume that removing esbuild's TDZ would have very real performance consequences. There is also an argument for doing even more TDZ avoidance to improve performance further. Another reason that this exists is that esbuild's linker may need to turn a module into a lazily-evaluated one in certain situations such as if it's dynamically imported. To avoid introducing binding overhead between modules when this happens, the linker represents module exports as variables outside of the lazy evaluation closure instead of inside. So this: export class Foo {} turns into this when the file needs to be lazily-evaluated: var Foo;
var init_a = __esm(() => {
Foo = class {
};
}); instead of something to this effect: var a_exports = {};
var init_a = __esm(() => {
__export(a_exports, {
Foo: () => Foo
});
class Foo {
}
}); That way external files can reference So exported top-level variables have to be hoisted out of the lazy evaluation closure. This is straightforward for variable declarations: This means classes must be rewritten to store themselves outside of their scope. It's not necessarily trivial to expose the class to the outer scope. You might thing you could just do Due to esbuild's parallel and performance-oriented design, esbuild does this transformation at the parsing stage. However, it's not yet known at that point whether or not the current module will need to be lazily-evaluated or not, so esbuild always does this transformation when bundling is active. It turns out this transformation is the same thing as TDZ avoidance. This transformation could likely be improved. It's something I'll probably get around to doing at some point. I've been tracking several large areas of upcoming work (e.g. |
Just to link back here, I re-tried applying babel's let/const transformation to the TypeScript compiler (which I did many months ago while working on the module conversion, but not on its own), and the results are really stunning: microsoft/TypeScript#52656 (comment) Back on #297 (comment) I mentioned that I was willing to ignore this problem because my impression was that eventually we'd have ESM and I'd really love for engines to get better at this, but at this point in time the performance bump is so great that I am sort of flipping back to wanting to propose a minimal form of this downleveling (i.e. just the easy ones ignoring loops) in esbuild solely for performance (so not the entirety of #297), which is unfortunately the opposite direction @robpalme is looking for. |
Just to make it clear what this proposal would be, a new option like "--avoid-tdz" would simply replace For the TypeScript compiler, I would enable this option for all of our outputs except for our test bundle; that combined with type checking would allow us to catch any real TDZ violations while improving performance. But, I know that this is exactly the opposite of this issue's premise, which is definitely unfortunate. |
Thanks for following up. Yeah I'd like to do this in esbuild too (to help make If it's just to improve performance, I'm wondering if it would be a better idea to only transform I've been thinking of a new flag |
Semi-jinx? 😄 Yeah, this is sort of what I was thinking; I'm definitely happy to test something like this out (I was going to try and implement it myself but I'm sure you can do it a lot faster than me). The reality is that TS before 5.0 already downleveled down to ES5 specifically for this performance win; and I gave it up by switching to esbuild (which still made things better despite this regression), so from my perspective, I'm okay with the "undefined" behavior, especially now that we actually do run tests with TDZ enabled (which caught two bugs: microsoft/TypeScript#50151). |
FYI I've created a bug report for this in upstream V8. |
To clarify, would this flag prevent the case where Slightly related to #2695 (comment). |
If I may, I'd like to throw support behind this idea. We at PeacockTV and SkyShowtime care deeply about performance and about how our apps run on older hardware. We need to support all kinds of TVs and set-top boxes, some of which were launched more than 10 years ago. For those, we transpile everything to ES5, but there are some middle-tier devices that can use ES6 (or even ES2018) that are based on older versions of Chromium, and are likely never going to be updated. While @robpalme's V8 ticket will certainly help us in the future, for those of us who are "stuck in the past" giving us the option to So here's my support for a |
FWIW #3167 should resolve this particular issue. |
I have written up a FAQ entry about this: https://esbuild.github.io/faq/#top-level-var. I'm closing this issue because a) yes, it's still needed, b) the performance issue regarding private fields has been fixed, and c) it's now described in the documentation. |
When bundling with
ESNext
output, classes can still get downleveled, due to an intentional performance-workaround called TDZ avoidance. This creates a consequential performance hazard when private fields end up using WeakMaps.It looks like TDZ avoidance workaround originated in ESBuild as a response to this issue which highlighted a performance bug in Safari. That bug was fixed over two years ago and has long since rolled out to all stable versions of Safari. So it looks like Safari performance is no longer a justification for this feature.
ESBuild still has the
avoidTDZ
code branches and the attached comment explains it exists due to Safari.Is it ok to remove this feature now? Or should the comment be updated to describe some other reason?
The text was updated successfully, but these errors were encountered: