-
Fix ordering issue with private class methods (#901)
This release fixes an ordering issue with private class fields where private methods were not available inside class field initializers. The issue affected code such as the following when the compilation target was set to
es2020
or lower:class A { pub = this.#priv; #priv() { return 'Inside #priv'; } } assert(new A().pub() === 'Inside #priv');
With this release, code that does this should now work correctly.
-
Fix
--keep-names
for private class membersNormal class methods and class fields don't need special-casing with esbuild when the
--keep-names
option is enabled because esbuild doesn't rename property names and doesn't transform class syntax in a way that breaks method names, so the names are kept without needing to generate any additional code.However, this is not the case for private class methods and private class fields. When esbuild transforms these for
--target=es2020
and earlier, the private class methods and private class field initializers are turned into code that uses aWeakMap
or aWeakSet
for access to preserve the privacy semantics. This ends up breaking the.name
property and previously--keep-names
didn't handle this edge case.With this release,
--keep-names
will also preserve the names of private class methods and private class fields. That means code like this should now work with--keep-names --target=es2020
:class Foo { #foo() {} #bar = () => {} test() { assert(this.#foo.name === '#foo') assert(this.#bar.name === '#bar') } }
-
Fix cross-chunk import paths (#899)
This release fixes an issue with the
--chunk-names=
feature where import paths in between two different automatically-generated code splitting chunks were relative to the output directory instead of relative to the importing chunk. This caused an import failure with the imported chunk if the chunk names setting was configured to put the chunks into a subdirectory. This bug has been fixed. -
Remove the guarantee that direct
eval
can access imported symbolsUsing direct
eval
when bundling is not a good idea because esbuild must assume that it can potentially reach anything in any of the containing scopes. Using directeval
has the following negative consequences:-
All names in all containing scopes are frozen and are not renamed during bundling, since the code in the direct
eval
could potentially access them. This prevents code in all scopes containing the call to directeval
from being minified or from being removed as dead code. -
The entire file is converted to CommonJS. This increases code size and decreases performance because exports are now resolved at run-time instead of at compile-time. Normally name collisions with other files are avoided by renaming conflicting symbols, but direct
eval
prevents symbol renaming so name collisions are prevented by wrapping the file in a CommonJS closure instead. -
Even with all of esbuild's special-casing of direct
eval
, referencing an ESMimport
from directeval
still doesn't necessarily work. ESM imports are live bindings to a symbol from another file and are represented by referencing that symbol directly in the flattened bundle. That symbol may use a different name which could break directeval
.
I recently realized that the last consequence of direct
eval
(the problem about not being able to referenceimport
symbols) could cause subtle correctness bugs. Specifically esbuild tries to prevent the imported symbol from being renamed, but doing so could cause name collisions that make the resulting bundle crash when it's evaluated. Two files containing directeval
that both import the same symbol from a third file but that import it with different aliases create a system of unsatisfiable naming constraints.So this release contains these changes to address this:
-
Direct
eval
is no longer guaranteed to be able to access imported symbols. This means imported symbols may be renamed or removed as dead code even though a call to directeval
could theoretically need to access them. If you need this to work, you'll have to store the relevant imports in a variable in a nested scope and move the call to directeval
into that nested scope. -
Using direct
eval
in a file in ESM format is now a warning. This is because the semantics of directeval
are poorly understood (most people don't intend to use directeval
at all) and because the negative consequences of bundling code with directeval
are usually unexpected and undesired. Of the few valid use cases for directeval
, it is usually a good idea to rewrite your code to avoid using directeval
in the first place.For example, if you write code that looks like this:
export function runCodeWithFeatureFlags(code) { let featureFlags = {...} eval(code) // "code" should be able to access "featureFlags" }
you should almost certainly write the code this way instead:
export function runCodeWithFeatureFlags(code) { let featureFlags = {...} let fn = new Function('featureFlags', code) fn(featureFlags) }
This still gives
code
access tofeatureFlags
but avoids all of the negative consequences of bundling code with directeval
.
-