Releases: evanw/esbuild
v0.11.18
-
Add support for OpenBSD on x86-64 (#1235)
Someone has asked for OpenBSD to be supported on x86-64. It should now be supported starting with this release.
-
Fix an incorrect warning about top-level
this
This was introduced in the previous release, and happens when using a top-level
async
arrow function with a compilation target that doesn't support it. The reason is that doing this generates a shim that preserves the value ofthis
. However, this warning message is confusing because there is not necessarily anythis
present in the source code. The warning message has been removed in this case. Now it should only show up ifthis
is actually present in the source code.
v0.11.17
-
Fix building with a large
stdin
string with Deno (#1219)When I did the initial port of esbuild's node-based API to Deno, I didn't realize that Deno's
write(bytes)
function doesn't actually write the provided bytes. Instead it may only write some of those bytes and needs to be repeatedly called again until it writes everything. This meant that calling esbuild's Deno-based API could hang if the API request was large enough, which can happen in practice when using thestdin
string feature. Thewrite
API is now called in a loop so these hangs in Deno should now be fixed. -
Add a warning about replacing
this
withundefined
in ESM code (#1225)There is existing JavaScript code that sometimes references top-level
this
as a way to access the global scope. However, top-levelthis
is actually specified to beundefined
inside of ECMAScript module code, which makes referencing top-levelthis
inside ESM code useless. This issue can come up when the existing JavaScript code is adapted for ESM by addingimport
and/orexport
. All top-level references tothis
are replaced withundefined
when bundling to make sure ECMAScript module behavior is emulated correctly regardless of the environment in which the resulting code is run.With this release, esbuild will now warn about this when bundling:
> example.mjs:1:61: warning: Top-level "this" will be replaced with undefined since this file is an ECMAScript module 1 │ export let Array = (typeof window !== 'undefined' ? window : this).Array ╵ ~~~~ example.mjs:1:0: note: This file is considered an ECMAScript module because of the "export" keyword here 1 │ export let Array = (typeof window !== 'undefined' ? window : this).Array ╵ ~~~~~~
This warning is not unique to esbuild. Rollup also already has a similar warning:
(!) `this` has been rewritten to `undefined` https://rollupjs.org/guide/en/#error-this-is-undefined example.mjs 1: export let Array = (typeof window !== 'undefined' ? window : this).Array ^
-
Allow a string literal as a JSX fragment (#1217)
TypeScript's JSX implementation allows you to configure a custom JSX factory and a custom JSX fragment, but requires that they are both valid JavaScript identifier member expression chains. Since esbuild's JSX implementation is based on TypeScript, esbuild has the same requirement. So
React.createElement
is a valid JSX factory value but['React', 'createElement']
is not.However, the Mithril framework has decided to use
"["
as a JSX fragment, which is not a valid JavaScript identifier member expression chain. This meant that using Mithril with esbuild required a workaround. In this release, esbuild now lets you use a string literal as a custom JSX fragment. It should now be easier to use esbuild's JSX implementation with libraries such as Mithril. -
Fix
metafile
inonEnd
withwatch
mode enabled (#1186)This release fixes a bug where the
metafile
property was incorrectly undefined inside pluginonEnd
callbacks ifwatch
mode is enabled for all builds after the first build. Themetafile
property was accidentally being set after callingonEnd
instead of before.
v0.11.16
-
Fix TypeScript
enum
edge case (#1198)In TypeScript, you can reference the inner closure variable in an
enum
within the inner closure by name:enum A { B = A }
The TypeScript compiler generates the following code for this case:
var A; (function (A) { A[A["B"] = A] = "B"; })(A || (A = {}));
However, TypeScript also lets you declare an
enum
value with the same name as the inner closure variable. In that case, the value "shadows" the declaration of the inner closure variable:enum A { A = 1, B = A }
The TypeScript compiler generates the following code for this case:
var A; (function (A) { A[A["A"] = 1] = "A"; A[A["B"] = 1] = "B"; })(A || (A = {}));
Previously esbuild reported a duplicate variable declaration error in the second case due to the collision between the
enum
value and the inner closure variable with the same name. With this release, the shadowing is now handled correctly. -
Parse the
@-moz-document
CSS rule (#1203)This feature has been removed from the web because it's actively harmful, at least according to this discussion. However, there is one exception where
@-moz-document url-prefix() {
is accepted by Firefox to basically be an "if Firefox" conditional rule. Because of this, esbuild now parses the@-moz-document
CSS rule. This should result in better pretty-printing and minification and no more warning when this rule is used. -
Fix syntax error in TypeScript-specific speculative arrow function parsing (#1211)
Because of grammar ambiguities, expressions that start with a parenthesis are parsed using what's called a "cover grammar" that is a super-position of both a parenthesized expression and an arrow function parameter list. In JavaScript, the cover grammar is unambiguously an arrow function if and only if the following token is a
=>
token.But in TypeScript, the expression is still ambiguously a parenthesized expression or an arrow function if the following token is a
:
since it may be the second half of the?:
operator or a return type annotation. This requires speculatively attempting to reduce the cover grammar to an arrow function parameter list.However, when doing this esbuild eagerly reported an error if a default argument was encountered and the target is
es5
(esbuild doesn't support lowering default arguments to ES5). This is problematic in the following TypeScript code since the parenthesized code turns out to not be an arrow function parameter list:function foo(check, hover) { return check ? (hover = 2, bar) : baz(); }
Previously this code incorrectly generated an error since
hover = 2
was incorrectly eagerly validated as a default argument. With this release, the reporting of the default argument error when targetinges5
is now done lazily and only when it's determined that the parenthesized code should actually be interpreted as an arrow function parameter list. -
Further changes to the behavior of the
browser
field (#1209)This release includes some changes to how the
browser
field inpackage.json
is interpreted to better match how Browserify, Webpack, Parcel, and Rollup behave. The interpretation of this map in esbuild is intended to be applied if and only if it's applied by any one of these bundlers. However, there were some cases where esbuild applied the mapping and none of the other bundlers did, which could lead to build failures. These cases have been added to my growing list ofbrowser
field test cases and esbuild's behavior should now be consistent with other bundlers again. -
Avoid placing a
super()
call inside areturn
statement (#1208)When minification is enabled, an expression followed by a return statement (e.g.
a(); return b
) is merged into a single statement (e.g.return a(), b
). This is done because it sometimes results in smaller code. If the return statement is the only statement in a block and the block is in a single-statement context, the block can be removed which saves a few characters.Previously esbuild applied this rule to calls to
super()
inside of constructors. Doing that broke esbuild's class lowering transform that tries to insert class field initializers after thesuper()
call. This transform isn't robust and only scans the top-level statement list inside the constructor, so inserting thesuper()
call inside of thereturn
statement means class field initializers were inserted before thesuper()
call instead of after. This could lead to run-time crashes due to initialization failure.With this release, top-level calls to
super()
will no longer be placed insidereturn
statements (in addition to various other kinds of statements such asthrow
, which are now also handled). This should avoid class field initializers being inserted before thesuper()
call. -
Fix a bug with
onEnd
and watch mode (#1186)This release fixes a bug where
onEnd
plugin callbacks only worked with watch mode when anonRebuild
watch mode callback was present. NowonEnd
callbacks should fire even if there is noonRebuild
callback. -
Fix an edge case with minified export names and code splitting (#1201)
The names of symbols imported from other chunks were previously not considered for renaming during minified name assignment. This could cause a syntax error due to a name collision when two symbols have the same original name. This was just an oversight and has been fixed, so symbols imported from other chunks should now be renamed when minification is enabled.
-
Provide a friendly error message when you forget
async
(#1216)If the parser hits a parse error inside a non-asynchronous function or arrow expression and the previous token is
await
, esbuild will now report a friendly error about a missingasync
keyword instead of reporting the parse error. This behavior matches other JavaScript parsers including TypeScript, Babel, and V8.The previous error looked like this:
> test.ts:2:8: error: Expected ";" but found "f" 2 │ await f(); ╵ ^
The error now looks like this:
> example.js:2:2: error: "await" can only be used inside an "async" function 2 │ await f(); ╵ ~~~~~ example.js:1:0: note: Consider adding the "async" keyword here 1 │ function f() { │ ^ ╵ async
v0.11.15
-
Provide options for how to handle legal comments (#919)
A "legal comment" is considered to be any comment that contains
@license
or@preserve
or that starts with//!
or/*!
. These comments are preserved in output files by esbuild since that follows the intent of the original authors of the code.However, some people want to remove the automatically-generated license information before they distribute their code. To facilitate this, esbuild now provides several options for how to handle legal comments (via
--legal-comments=
in the CLI andlegalComments
in the JS API):none
: Do not preserve any legal commentsinline
: Preserve all statement-level legal commentseof
: Move all statement-level legal comments to the end of the filelinked
: Move all statement-level legal comments to a.LEGAL.txt
file and link to them with a commentexternal
: Move all statement-level legal comments to a.LEGAL.txt
file but to not link to them
The default behavior is
eof
when bundling andinline
otherwise. -
Add
onStart
andonEnd
callbacks to the plugin APIPlugins can now register callbacks to run when a build is started and ended:
const result = await esbuild.build({ ... incremental: true, plugins: [{ name: 'example', setup(build) { build.onStart(() => console.log('build started')) build.onEnd(result => console.log('build ended', result)) }, }], }) await result.rebuild()
One benefit of
onStart
andonEnd
is that they are run for all builds including rebuilds (relevant for incremental mode, watch mode, or serve mode), so they should be a good place to do work related to the build lifecycle.More details:
-
build.onStart()
You should not use an
onStart
callback for initialization since it can be run multiple times. If you want to initialize something, just put your plugin initialization code directly inside thesetup
function instead.The
onStart
callback can beasync
and can return a promise. However, the build does not wait for the promise to be resolved before starting, so a slowonStart
callback will not necessarily slow down the build. AllonStart
callbacks are also run concurrently, not consecutively. The returned promise is purely for error reporting, and matters when theonStart
callback needs to do an asynchronous operation that may fail. If your plugin needs to wait for an asynchronous task inonStart
to complete before anyonResolve
oronLoad
callbacks are run, you will need to have youronResolve
oronLoad
callbacks block on that task fromonStart
.Note that
onStart
callbacks do not have the ability to mutatebuild.initialOptions
. The initial options can only be modified within thesetup
function and are consumed once thesetup
function returns. All rebuilds use the same initial options so the initial options are never re-consumed, and modifications tobuild.initialOptions
that are done withinonStart
are ignored. -
build.onEnd()
All
onEnd
callbacks are run in serial and each callback is given access to the final build result. It can modify the build result before returning and can delay the end of the build by returning a promise. If you want to be able to inspect the build graph, you should setbuild.initialOptions.metafile = true
and the build graph will be returned as themetafile
property on the build result object.
-
v0.11.14
-
Implement arbitrary module namespace identifiers
This introduces new JavaScript syntax:
import {'🍕' as food} from 'file' export {food as '🧀'}
The proposal for this feature appears to not be going through the regular TC39 process. It is being done as a subtle direct pull request instead. It seems appropriate for esbuild to support this feature since it has been implemented in V8 and has now shipped in Chrome 90 and node 16.
According to the proposal, this feature is intended to improve interop with non-JavaScript languages which use exports that aren't valid JavaScript identifiers such as
Foo::~Foo
. In particular, WebAssembly allows any valid UTF-8 string as to be used as an export alias.This feature was actually already partially possible in previous versions of JavaScript via the computed property syntax:
import * as ns from './file.json' console.log(ns['🍕'])
However, doing this is very un-ergonomic and exporting something as an arbitrary name is impossible outside of
export * from
. So this proposal is designed to fully fill out the possibility matrix and make arbitrary alias names a proper first-class feature. -
Implement more accurate
sideEffects
behavior from Webpack (#1184)This release adds support for the implicit
**/
prefix that must be added to paths in thesideEffects
array inpackage.json
if the path does not contain/
. Another way of saying this is ifpackage.json
contains asideEffects
array with a string that doesn't contain a/
then it should be treated as a file name instead of a path. Previously esbuild treated all strings in this array as paths, which does not match how Webpack behaves. The result of this meant that esbuild could consider files to have no side effects while Webpack would consider the same files to have side effects. This bug should now be fixed.
v0.11.13
-
Implement ergonomic brand checks for private fields
This introduces new JavaScript syntax:
class Foo { #field static isFoo(x) { return #foo in x // This is an "ergonomic brand check" } } assert(Foo.isFoo(new Foo))
The TC39 proposal for this feature is currently at stage 3 but has already been shipped in Chrome 91 and has also landed in Firefox. It seems reasonably inevitable given that it's already shipping and that it's a very simple feature, so it seems appropriate to add this feature to esbuild.
-
Add the
--allow-overwrite
flag (#1152)This is a new flag that allows output files to overwrite input files. It's not enabled by default because doing so means overwriting your source code, which can lead to data loss if your code is not checked in. But supporting this makes certain workflows easier by avoiding the need for a temporary directory so doing this is now supported.
-
Minify property accesses on object literals (#1166)
The code
{a: {b: 1}}.a.b
will now be minified to1
. This optimization is relatively complex and hard to do safely. Here are some tricky cases that are correctly handled:var obj = {a: 1} assert({a: 1, a: 2}.a === 2) assert({a: 1, [String.fromCharCode(97)]: 2}.a === 2) assert({__proto__: obj}.a === 1) assert({__proto__: null}.a === undefined) assert({__proto__: null}.__proto__ === undefined) assert({a: function() { return this.b }, b: 1}.a() === 1) assert(({a: 1}.a = 2) === 2) assert(++{a: 1}.a === 2) assert.throws(() => { new ({ a() {} }.a) })
-
Improve arrow function parsing edge cases
There are now more situations where arrow expressions are not allowed. This improves esbuild's alignment with the JavaScript specification. Some examples of cases that were previously allowed but that are now no longer allowed:
1 + x => {} console.log(x || async y => {}) class Foo extends async () => {} {}
v0.11.12
-
Fix a bug where
-0
and0
were collapsed to the same value (#1159)Previously esbuild would collapse
Object.is(x ? 0 : -0, -0)
intoObject.is((x, 0), -0)
during minification, which is incorrect. The IEEE floating-point value-0
is a different bit pattern than0
and while they both compare equal, the difference is detectable in a few scenarios such as when usingObject.is()
. The minification transformation now checks for-0
vs.0
and no longer has this bug. This fix was contributed by @rtsao. -
Match the TypeScript compiler's output in a strange edge case (#1158)
With this release, esbuild's TypeScript-to-JavaScript transform will no longer omit the namespace in this case:
namespace Something { export declare function Print(a: string): void } Something.Print = function(a) {}
This was previously omitted because TypeScript omits empty namespaces, and the namespace was considered empty because the
export declare function
statement isn't "real":namespace Something { export declare function Print(a: string): void setTimeout(() => Print('test')) } Something.Print = function(a) {}
The TypeScript compiler compiles the above code into the following:
var Something; (function (Something) { setTimeout(() => Print('test')); })(Something || (Something = {})); Something.Print = function (a) { };
Notice how
Something.Print
is never called, and what appears to be a reference to thePrint
symbol on the namespaceSomething
is actually a reference to the global variablePrint
. I can only assume this is a bug in TypeScript, but it's important to replicate this behavior inside esbuild for TypeScript compatibility.The TypeScript-to-JavaScript transform in esbuild has been updated to match the TypeScript compiler's output in both of these cases.
-
Separate the
debug
log level intodebug
andverbose
You can now use
--log-level=debug
to get some additional information that might indicate some problems with your build, but that has a high-enough false-positive rate that it isn't appropriate for warnings, which are on by default. Enabling thedebug
log level no longer generates a torrent of debug information like it did in the past; that behavior is now reserved for theverbose
log level instead.
v0.11.11
-
Initial support for Deno (#936)
You can now use esbuild in the Deno JavaScript environment via esbuild's official Deno package. Using it looks something like this:
import * as esbuild from 'https://deno.land/x/[email protected]/mod.js' const ts = 'let hasProcess: boolean = typeof process != "null"' const result = await esbuild.transform(ts, { loader: 'ts', logLevel: 'warning' }) console.log('result:', result) esbuild.stop()
It has basically the same API as esbuild's npm package with one addition: you need to call
stop()
when you're done because unlike node, Deno doesn't provide the necessary APIs to allow Deno to exit while esbuild's internal child process is still running. -
Remove warnings about non-bundled use of
require
andimport
(#1153, #1142, #1132, #1045, #812, #661, #574, #512, #495, #480, #453, #410, #80)Previously esbuild had warnings when bundling about uses of
require
andimport
that are not of the formrequire(<string literal>)
orimport(<string literal>)
. These warnings existed because the bundling process must be able to statically-analyze all dynamic imports to determine which files must be included. Here are some real-world examples of cases that esbuild doesn't statically analyze:-
From
mongoose
:require('./driver').set(require(global.MONGOOSE_DRIVER_PATH));
-
From
moment
:aliasedRequire = require; aliasedRequire('./locale/' + name);
-
From
logform
:function exposeFormat(name) { Object.defineProperty(format, name, { get() { return require(`./${name}.js`); } }); } exposeFormat('align');
All of these dynamic imports will not be bundled (i.e. they will be left as-is) and will crash at run-time if they are evaluated. Some of these crashes are ok since the code paths may have error handling or the code paths may never be used. Other crashes are not ok because the crash will actually be hit.
The warning from esbuild existed to let you know that esbuild is aware that it's generating a potentially broken bundle. If you discover that your bundle is broken, it's nice to have a warning from esbuild to point out where the problem is. And it was just a warning so the build process still finishes and successfully generates output files. If you didn't want to see the warning, it was easy to turn it off via
--log-level=error
.However, there have been quite a few complaints about this warning. Some people seem to not understand the difference between a warning and an error, and think the build has failed even though output files were generated. Other people do not want to see the warning but also do not want to enable
--log-level=error
.This release removes this warning for both
require
andimport
. Now when you try to bundle code with esbuild that contains dynamic imports not of the formrequire(<string literal>)
orimport(<string literal>)
, esbuild will just silently generate a potentially broken bundle. This may affect people coming from other bundlers that support certain forms of dynamic imports that are not compatible with esbuild such as the Webpack-specific dynamicimport()
with pattern matching. -
v0.11.10
-
Provide more information about
exports
map import failures if possible (#1143)Node has a new feature where you can add an
exports
map to yourpackage.json
file to control how external import paths map to the files in your package. You can change which paths map to which files as well as make it impossible to import certain files (i.e. the files are private).If path resolution fails due to an
exports
map and the failure is not related to import conditions, esbuild's current error message for this just says that the import isn't possible:> example.js:1:15: error: Could not resolve "vanillajs-datepicker/js/i18n/locales/ca" (mark it as external to exclude it from the bundle) 1 │ import ca from 'vanillajs-datepicker/js/i18n/locales/ca' ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ node_modules/vanillajs-datepicker/package.json:6:13: note: The path "./js/i18n/locales/ca" is not exported by package "vanillajs-datepicker" 6 │ "exports": { ╵ ^
This error message matches the error that node itself throws. However, the message could be improved in the case where someone is trying to import a file using its file system path and that path is actually exported by the package, just under a different export path. This case comes up a lot when using TypeScript because the TypeScript compiler (and therefore the Visual Studio Code IDE) still doesn't support package
exports
.With this release, esbuild will now do a reverse lookup of the file system path using the
exports
map to determine what the correct import path should be:> example.js:1:15: error: Could not resolve "vanillajs-datepicker/js/i18n/locales/ca" (mark it as external to exclude it from the bundle) 1 │ import ca from 'vanillajs-datepicker/js/i18n/locales/ca' ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ node_modules/vanillajs-datepicker/package.json:6:13: note: The path "./js/i18n/locales/ca" is not exported by package "vanillajs-datepicker" 6 │ "exports": { ╵ ^ node_modules/vanillajs-datepicker/package.json:12:19: note: The file "./js/i18n/locales/ca.js" is exported at path "./locales/ca" 12 │ "./locales/*": "./js/i18n/locales/*.js", ╵ ~~~~~~~~~~~~~~~~~~~~~~~~ example.js:1:15: note: Import from "vanillajs-datepicker/locales/ca" to get the file "node_modules/vanillajs-datepicker/js/i18n/locales/ca.js" 1 │ import ca from 'vanillajs-datepicker/js/i18n/locales/ca' │ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ╵ "vanillajs-datepicker/locales/ca"
Hopefully this should enable people encountering this issue to fix the problem themselves.
v0.11.9
-
Fix escaping of non-BMP characters in property names (#977)
Property names in object literals do not have to be quoted if the property is a valid JavaScript identifier. This is defined as starting with a character in the
ID_Start
Unicode category and ending with zero or more characters in theID_Continue
Unicode category. However, esbuild had a bug where non-BMP characters (i.e. characters encoded using two UTF-16 code units instead of one) were always checked againstID_Continue
instead ofID_Start
because they included a code unit that wasn't at the start. This could result in invalid JavaScript being generated when using--charset=utf8
becauseID_Continue
is a superset ofID_Start
and contains some characters that are not valid at the start of an identifier. This bug has been fixed. -
Be maximally liberal in the interpretation of the
browser
field (#740)The
browser
field inpackage.json
is an informal convention followed by browser-specific bundlers that allows package authors to substitute certain node-specific import paths with alternative browser-specific import paths. It doesn't have a rigorous specification and the canonical description of the feature doesn't include any tests. As a result, each bundler implements this feature differently. I have tried to create a survey of how different bundlers interpret thebrowser
field and the results are very inconsistent.This release attempts to change esbuild to support the union of the behavior of all other bundlers. That way if people have the
browser
field working with some other bundler and they switch to esbuild, thebrowser
field shouldn't ever suddenly stop working. This seemed like the most principled approach to take in this situation.The drawback of this approach is that it means the
browser
field may start working when switching to esbuild when it was previously not working. This could cause bugs, but I consider this to be a problem with the package (i.e. not using a more well-supported form of thebrowser
field), not a problem with esbuild itself.