v0.11.7
-
Fix incorrect chunk reference with code splitting, css, and dynamic imports (#1125)
This release fixes a bug where when you use code splitting, CSS imports in JS, and dynamic imports all combined, the dynamic import incorrectly references the sibling CSS chunk for the dynamic import instead of the primary JS chunk. In this scenario the entry point file corresponds to two different output chunks (one for CSS and one for JS) and the wrong chunk was being picked. This bug has been fixed.
-
Split apart tree shaking and code splitting (#1123)
The original code splitting algorithm allowed for files to be split apart and for different parts of the same file to end up in different chunks based on which entry points needed which parts. This was done at the same time as tree shaking by essentially performing tree shaking multiple times, once per entry point, and tracking which entry points each file part is live in. Each file part that is live in at least one entry point was then assigned to a code splitting chunk with all of the other code that is live in the same set of entry points. This ensures that entry points only import code that they will use (i.e. no code will be downloaded by an entry point that is guaranteed to not be used).
This file-splitting feature has been removed because it doesn't work well with the recently-added top-level await JavaScript syntax, which has complex evaluation order rules that operate at file boundaries. File parts now have a single boolean flag for whether they are live or not instead of a set of flags that track which entry points that part is reachable from (reachability is still tracked at the file level).
However, this change appears to have introduced some subtly incorrect behavior with code splitting because there is now an implicit dependency in the import graph between adjacent parts within the same file even if the two parts are unrelated and don't reference each other. This is due to the fact each entry point that references one part pulls in the file (but not the whole file, only the parts that are live in at least one entry point). So liveness must be fully computed first before code splitting is computed.
This release splits apart tree shaking and code splitting into two separate passes, which fixes certain cases where two generated code splitting chunks ended up each importing symbols from the other and causing a cycle. There should hopefully no longer be cycles in generated code splitting chunks.
-
Make
this
work in static class fields in TypeScript filesCurrently
this
is mis-compiled in static fields in TypeScript files if theuseDefineForClassFields
setting intsconfig.json
isfalse
(the default value):class Foo { static foo = 123 static bar = this.foo } console.log(Foo.bar)
This is currently compiled into the code below, which is incorrect because it changes the value of
this
(it's supposed to refer toFoo
):class Foo { } Foo.foo = 123; Foo.bar = this.foo; console.log(Foo.bar);
This was an intentionally unhandled case because the TypeScript compiler doesn't handle this either (esbuild's currently incorrect output matches the output from the TypeScript compiler, which is also currently incorrect). However, the TypeScript compiler might fix their output at some point in which case esbuild's behavior would become problematic.
So this release now generates the correct output:
const _Foo = class { }; let Foo = _Foo; Foo.foo = 123; Foo.bar = _Foo.foo; console.log(Foo.bar);
Presumably the TypeScript compiler will be fixed to also generate something like this in the future. If you're wondering why esbuild generates the extra
_Foo
variable, it's defensive code to handle the possibility of the class being reassigned, since class declarations are not constants:class Foo { static foo = 123 static bar = () => Foo.foo } let bar = Foo.bar Foo = { foo: 321 } console.log(bar())
We can't just move the initializer containing
Foo.foo
outside of the class body because in JavaScript, the class name is shadowed inside the class body by a special hidden constant that is equal to the class object. Even if the class is reassigned later, references to that shadowing symbol within the class body should still refer to the original class object. -
Various fixes for private class members (#1131)
This release fixes multiple issues with esbuild's handling of the
#private
syntax. Previously there could be scenarios where references tothis.#private
could be moved outside of the class body, which would cause them to become invalid (since the#private
name is only available within the class body). One such case is when TypeScript'suseDefineForClassFields
setting has the valuefalse
(which is the default value), which causes class field initializers to be replaced with assignment expressions to avoid using "define" semantics:class Foo { static #foo = 123 static bar = Foo.#foo }
Previously this was turned into the following code, which is incorrect because
Foo.#foo
was moved outside of the class body:class Foo { static #foo = 123; } Foo.bar = Foo.#foo;
This is now handled by converting the private field syntax into normal JavaScript that emulates it with a
WeakMap
instead.This conversion is fairly conservative to make sure certain edge cases are covered, so this release may unfortunately convert more private fields than previous releases, even when the target is
esnext
. It should be possible to improve this transformation in future releases so that this happens less often while still preserving correctness.