NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #4118 most downloaded on npm
The cross-platform WebAssembly binary for esbuild, a JavaScript bundler.
Last release 1 months ago
08 Aug 2026
Ships fairly regularly
a new release about every 4 weeks
Nearly every release is documented
notes for 60 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
7 years old
490 releases · first in 2020
One column per quarter.
Implement arbitrary module namespace identifiers
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 the sideEffects array in package.json if the path does not contain /. Another way of saying this is if package.json contains a sideEffects 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.
Implement ergonomic brand checks for private fields
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 to 1. 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 () => {} {}
Fix a bug where -0 and 0 were collapsed to the same value
Fix a bug where -0 and 0 were collapsed to the same value (#1159)
Previously esbuild would collapse Object.is(x ? 0 : -0, -0) into Object.is((x, 0), -0) during minification, which is incorrect. The IEEE floating-point value -0 is a different bit pattern than 0 and while they both compare equal, the difference is detectable in a few scenarios such as when using Object.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 the Print symbol on the namespace Something is actually a reference to the global variable Print. 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 into debug and verbose
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 the debug log level no longer generates a torrent of debug information like it did in the past; that behavior is now reserved for the verbose log level instead.
You can now use esbuild in the Deno JavaScript environment via esbuild's official Deno package. Using it looks something like this:
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/esbuild@v0.11.11/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 and import (#1153, #1142, #1132, #1045, #812, #661, #574, #512, #495, #480, #453, #410, #80)
Previously esbuild had warnings when bundling about uses of require and import that are not of the form require(<string literal>) or import(<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 and import. Now when you try to bundle code with esbuild that contains dynamic imports not of the form require(<string literal>) or import(<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 dynamic import() with pattern matching.
Provide more information about exports map import failures if possible
Provide more information about exports map import failures if possible (#1143)
Node has a new feature where you can add an exports map to your package.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.
Fix escaping of non-BMP characters in property names
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 the ID_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 against ID_Continue instead of ID_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 because ID_Continue is a superset of ID_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 in package.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 the browser 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, the browser 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 the browser field), not a problem with esbuild itself.
Fix hash calculation for code splitting and dynamic imports
Fix hash calculation for code splitting and dynamic imports (#1076)
The hash included in the file name of each output file is intended to change if and only if anything relevant to the content of that output file changes. It includes:
import statementsHowever, this didn't include dynamic import() expressions due to an oversight. With this release, dynamic import() expressions are now also counted as transitive dependencies. This fixes an issue where the content of an output file could change without its hash also changing. As a side effect of this change, dynamic imports inside output files of other output files are now listed in the metadata file if the metafile setting is enabled.
Refactor the internal module graph representation
This release changes a large amount of code relating to esbuild's internal module graph. The changes are mostly organizational and help consolidate most of the logic around maintaining various module graph invariants into a separate file where it's easier to audit. The Go language doesn't have great abstraction capabilities (e.g. no zero-cost iterators) so the enforcement of this new abstraction is unfortunately done by convention instead of by the compiler, and there is currently still some code that bypasses the abstraction. But it's better than it was before.
Another relevant change was moving a number of special cases that happened during the tree shaking traversal into the graph itself instead. Previously there were quite a few implicit dependency rules that were checked in specific places, which was hard to follow. Encoding these special case constraints into the graph itself makes the problem easier to reason about and should hopefully make the code more regular and robust.
Finally, this set of changes brings back full support for the sideEffects annotation in package.json. It was previously disabled when code splitting was active as a temporary measure due to the discovery of some bugs in that scenario. But I believe these bugs have been resolved now that tree shaking and code splitting are done in separate passes (see the previous release for more information).
Fix incorrect chunk reference with code splitting, css, and dynamic imports
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 files
Currently this is mis-compiled in static fields in TypeScript files if the useDefineForClassFields setting in tsconfig.json is false (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 to Foo):
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 to this.#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's useDefineForClassFields setting has the value false (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.
Fix an incorrect minification transformation
Fix an incorrect minification transformation (#1121)
This release removes an incorrect substitution rule in esbuild's peephole optimizer, which is run when minification is enabled. The incorrect rule transformed if(a && falsy) into if(a, falsy) which is equivalent if falsy has no side effects (such as the literal false). However, the rule didn't check that the expression is side-effect free first which could result in miscompiled code. I have removed the rule instead of modifying it to check for the lack of side effects first because while the code is slightly smaller, it may also be more expensive at run-time which is undesirable. The size savings are also very insignificant.
Change how NODE_PATH works to match node (#1117)
Node searches for packages in nearby node_modules directories, but it also allows you to inject extra directories to search for packages in using the NODE_PATH environment variable. This is supported when using esbuild's CLI as well as via the nodePaths option when using esbuild's API.
Node's module resolution algorithm is well-documented, and esbuild's path resolution is designed to follow it. The full algorithm is here: https://nodejs.org/api/modules.html#modules_all_together. However, it appears that the documented algorithm is incorrect with regard to NODE_PATH. The documentation says NODE_PATH directories should take precedence over node_modules directories, and so that's how esbuild worked. However, in practice node actually does it the other way around.
Starting with this release, esbuild will now allow node_modules directories to take precedence over NODE_PATH directories. This is a deviation from the published algorithm.
Provide a better error message for incorrectly-quoted JSX attributes (#959, #1115)
People sometimes try to use the output of JSON.stringify() as a JSX attribute when automatically-generating JSX code. Doing so is incorrect because JSX strings work like XML instead of like JS (since JSX is XML-in-JS). Specifically, using a backslash before a quote does not cause it to be escaped:
// JSX ends the "content" attribute here and sets "content" to 'some so-called \\'
// v
let button = <Button content="some so-called \"button text\"" />
// ^
// There is no "=" after the JSX attribute "text", so we expect a ">"
It's not just esbuild; Babel and TypeScript also treat this as a syntax error. All of these JSX parsers are just following the JSX specification. This has come up twice now so it could be worth having a dedicated error message. Previously esbuild had a generic syntax error like this:
> example.jsx:1:58: error: Expected ">" but found "\\"
1 │ let button = <Button content="some so-called \"button text\"" />
╵ ^
Now esbuild will provide more information if it detects this case:
> example.jsx:1:58: error: Unexpected backslash in JSX element
1 │ let button = <Button content="some so-called \"button text\"" />
╵ ^
example.jsx:1:45: note: Quoted JSX attributes use XML-style escapes instead of JavaScript-style escapes
1 │ let button = <Button content="some so-called \"button text\"" />
│ ~~
╵ "
example.jsx:1:29: note: Consider using a JavaScript string inside {...} instead of a quoted JSX attribute
1 │ let button = <Button content="some so-called \"button text\"" />
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
╵ {"some so-called \"button text\""}
Add support for the override keyword in TypeScript 4.3
Add support for the override keyword in TypeScript 4.3 (#1105)
The latest version of TypeScript (now in beta) adds a new keyword called override that can be used on class members. You can read more about this feature in Microsoft's blog post about TypeScript 4.3. It looks like this:
class SpecializedComponent extends SomeComponent {
override show() {
// ...
}
}
With this release, esbuild will now ignore the override keyword when parsing TypeScript code instead of treating this keyword as a syntax error, which means esbuild can now support TypeScript 4.3 syntax. This change was contributed by @g-plane.
Allow async plugin setup functions
With this release, you can now return a promise from your plugin's setup function to delay the start of the build:
let slowInitPlugin = {
name: 'slow-init',
async setup(build) {
// Delay the start of the build
await new Promise(r => setTimeout(r, 1000))
},
}
This is useful if your plugin needs to do something asynchronous before the build starts. For example, you may need some asynchronous information before modifying the initialOptions object, which must be done before the build starts for the modifications to take effect.
Add some optimizations around hashing
This release contains two optimizations to the hashes used in output file names:
Hash generation now happens in parallel with other work, and other work only blocks on the hash computation if the hash ends up being needed (which is only if [hash] is included in --entry-names=, and potentially --chunk-names= if it's relevant). This is a performance improvement because --entry-names= does not include [hash] in the default case, so bundling time no longer always includes hashing time.
The hashing algorithm has been changed from SHA1 to xxHash (specifically this Go implementation) which means the hashing step is around 6x faster than before. Thanks to @Jarred-Sumner for the suggestion.
Disable tree shaking annotations when code splitting is active (#1070, #1081)
Support for Webpack's "sideEffects": false annotation in package.json is now disabled when code splitting is enabled and there is more than one entry point. This avoids a bug that could cause generated chunks to reference each other in some cases. Now all chunks generated by code splitting should be acyclic.
Avoid name collisions with TypeScript helper functions
Avoid name collisions with TypeScript helper functions (#1102)
Helper functions are sometimes used when transforming newer JavaScript syntax for older browsers. For example, let {x, ...y} = {z} is transformed into let _a = {z}, {x} = _a, y = __rest(_a, ["x"]) which uses the __rest helper function. Many of esbuild's transforms were modeled after the transforms in the TypeScript compiler, so many of the helper functions use the same names as TypeScript's helper functions.
However, the TypeScript compiler doesn't avoid name collisions with existing identifiers in the transformed code. This means that post-processing esbuild's output with the TypeScript compiler (e.g. for lowering ES6 to ES5) will cause issues since TypeScript will fail to call its own helper functions: microsoft/TypeScript#43296. There is also a problem where TypeScript's tslib library overwrites globals with these names, which can overwrite esbuild's helper functions if code bundled with esbuild is run in the global scope.
To avoid these problems, esbuild will now use different names for its helper functions.
Fix a chunk hashing issue (#1099)
Previously the chunk hashing algorithm skipped hashing entry point chunks when the --entry-names= setting doesn't contain [hash], since the hash wasn't used in the file name. However, this is no longer correct with the change in version 0.11.0 that made dynamic entry point chunks use --chunk-names= instead of --entry-names= since --chunk-names= can still contain [hash].
With this release, chunk contents will now always be hashed regardless of the chunk type. This makes esbuild somewhat slower than before in the common case, but it fixes this correctness issue.
Auto-define process.env.NODE_ENV when platform is set to browser
Auto-define process.env.NODE_ENV when platform is set to browser
All code in the React world has the requirement that the specific expression process.env.NODE_ENV must be replaced with a string at compile-time or your code will immediately crash at run-time. This is a common stumbling point for people when they start using esbuild with React. Previously bundling code with esbuild containing process.env.NODE_ENV without defining a string replacement first was a warning that warned you about the lack of a define.
With this release esbuild will now attempt to define process.env.NODE_ENV automatically instead of warning about it. This will be implicitly defined to "production" if minification is enabled and "development" otherwise. This automatic behavior only happens when the platform is browser, since process is not a valid browser API and will never exist in the browser. This is also only done if there are no existing defines for process, process.env, or process.env.NODE_ENV so you can override the automatic value if necessary. If you need to disable this behavior, you can use the neutral platform instead of the browser platform.
Retain side-effect free intermediate re-exporting files (#1088)
This fixes a subtle bug with esbuild's support for Webpack's "sideEffects": false annotation in package.json when combined with re-export statements. A re-export is when you import something from one file and then export it again. You can re-export something with export * from or export {foo} from or import {foo} from followed by export {foo}.
The bug was that files which only contain re-exports and that are marked as being side-effect free were not being included in the bundle if you import one of the re-exported symbols. This is because esbuild's implementation of re-export linking caused the original importing file to "short circuit" the re-export and just import straight from the file containing the final symbol, skipping the file containing the re-export entirely.
This was normally not observable since the intermediate file consisted entirely of re-exports, which have no side effects. However, a recent change to allow ESM files to be lazily-initialized relies on all intermediate files being included in the bundle to trigger the initialization of the lazy evaluation wrappers. So the behavior of skipping over re-export files is now causing the imported symbols to not be initialized if the re-exported file is marked as lazily-evaluated.
The fix is to track all re-exports in the import chain from the original file to the file containing the final symbol and then retain all of those statements if the import ends up being used.
Add a very verbose debug log level
This log level is an experiment. Enabling it logs a lot of information (currently only about path resolution). The idea is that if you are having an obscure issue, the debug log level might contain some useful information. Unlike normal logs which are meant to mainly provide actionable information, these debug logs are intentionally mostly noise and are designed to be searched through instead.
Here is an example of debug-level log output:
> debug: Resolving import "react" in directory "src" of type "import-statement"
note: Read 26 entries for directory "src"
note: Searching for "react" in "node_modules" directories starting from "src"
note: Attempting to load "src/react" as a file
note: Failed to find file "src/react"
note: Failed to find file "src/react.tsx"
note: Failed to find file "src/react.ts"
note: Failed to find file "src/react.js"
note: Failed to find file "src/react.css"
note: Failed to find file "src/react.svg"
note: Attempting to load "src/react" as a directory
note: Failed to read directory "src/react"
note: Parsed package name "react" and package subpath "."
note: Checking for a package in the directory "node_modules/react"
note: Read 7 entries for directory "node_modules/react"
note: Read 393 entries for directory "node_modules"
note: Attempting to load "node_modules/react" as a file
note: Failed to find file "node_modules/react"
note: Failed to find file "node_modules/react.tsx"
note: Failed to find file "node_modules/react.ts"
note: Failed to find file "node_modules/react.js"
note: Failed to find file "node_modules/react.css"
note: Failed to find file "node_modules/react.svg"
note: Attempting to load "node_modules/react" as a directory
note: Read 7 entries for directory "node_modules/react"
note: Resolved to "node_modules/react/index.js" using the "main" field in "node_modules/react/package.json"
note: Read 7 entries for directory "node_modules/react"
note: Read 7 entries for directory "node_modules/react"
note: Primary path is "node_modules/react/index.js" in namespace "file"
Fix missing symbol dependency for wrapped ESM files
Fix missing symbol dependency for wrapped ESM files (#1086)
An internal graph node was missing an edge, which could result in generating code that crashes at run-time when code splitting is enabled. Specifically a part containing an import statement must depend on the imported file's wrapper symbol if the imported file is wrapped, regardless of whether it's a wrapped CommonJS or ESM file. Previously this was only the case for CommonJS files but not for ESM files, which is incorrect. This bug has been fixed.
Fix an edge case with entry points and top-level await
If an entry point uses import() on itself, it currently has to be wrapped since import() expressions call the wrapper for the imported file. This means the another call to the wrapper must be inserted at the bottom of the entry point file to start the lazy evaluation of the entry point code (otherwise nothing will be evaluated, since the entry point is wrapped). However, if this entry point then contains a top-level await that means the wrapper is async and must be passed to await to catch and forward any exceptions thrown during the evaluation of the entry point code. This await was previously missing in this specific case due to a bug, but the await should now be added in this release.
Fix a missing space before internal import() when minifying
Fix a missing space before internal import() when minifying (#1082)
Internal import() of a CommonJS module inside the bundle turns into a call to Promise.resolve().then(() => require()). However, a space was not inserted before the Promise token when minifying, which could lead to a syntax error. This bug has been fixed.
Fix code generation for unused imported files without side effects (#1080)
When esbuild adds a wrapping closure around a file to turn it from a statically-initialized file to a dynamically-initialized file, it also needs to turn import statements in other files that import the wrapped file into calls to the wrapper so that the wrapped file is initialized in the correct ordering. However, although tree-shaking is disabled for wrapped CommonJS files because CommonJS exports are dynamic, tree-shaking is still enabled for wrapped ESM files because ESM exports are static.
This caused a bug when files that have been marked with "sideEffects": false end up being completely unused in the resulting bundle. In that case the file is removed entirely, but esbuild was still turning import statements to that file into calls to the ESM wrapper. These wrapper calls should instead be omitted if the file was completely removed from the bundle as dead code. This bug has been fixed.
Allow top-level await in supported environments
Top-level await (i.e. using the await keyword outside of an async function) is not yet part of the JavaScript language standard. The feature proposal is still at stage 3 and has not yet advanced to stage 4. However, V8 has already implemented it and it has shipped in Chrome 89 and node 14.8. This release allows top-level await to be used when the --target= flag is set to those compilation targets.
Convert import() to require() if import() is not supported (#1084)
This release now converts dynamic import() expressions into Promise.resolve().then(() => require()) expressions if the compilation target doesn't support them. This is the case for node before version 13.2, for example.
…to this release (see below for details). These breaking changes are as follows:
This release contains backwards-incompatible changes. Since esbuild is before version 1.0.0, these changes have been released as a new minor version to reflect this (as recommended by npm). You should either be pinning the exact version of esbuild in your package.json file or be using a version range syntax that only accepts patch upgrades such as ~0.10.0. See the documentation about semver for more information.
The changes in this release mostly relate to how entry points are handled. The way output paths are generated has changed in some cases, so you may need to update how you refer to the output path for a given entry point when you update to this release (see below for details). These breaking changes are as follows:
Change how require() and import() of ESM works (#667, #706)
Previously if you call require() on an ESM file, or call import() on an ESM file with code splitting disabled, esbuild would convert the ESM file to CommonJS. For example, if you had the following input files:
// cjs-file.js
console.log(require('./esm-file.js').foo)
// esm-file.js
export let foo = bar()
The previous bundling behavior would generate something like this:
var require_esm_file = __commonJS((exports) => {
__markAsModule(exports);
__export(exports, {
foo: () => foo
});
var foo = bar();
});
console.log(require_esm_file().foo);
This behavior has been changed and esbuild now generates something like this instead:
var esm_file_exports = {};
__export(esm_file_exports, {
foo: () => foo
});
var foo;
var init_esm_file = __esm(() => {
foo = bar();
});
console.log((init_esm_file(), esm_file_exports).foo);
The variables have been pulled out of the lazily-initialized closure and are accessible to the rest of the module's scope. Some benefits of this approach:
If another file does import {foo} from "./esm-file.js", it will just reference foo directly and will not pay the performance penalty or code size overhead of the dynamic property accesses that come with CommonJS-style exports. So this improves performance and reduces code size in some cases.
This fixes a long-standing bug (#706) where entry point exports could be broken if the entry point is a target of a require() call and the output format was ESM. This happened because previously calling require() on an entry point converted it to CommonJS, which then meant it only had a single default export, and the exported variables were inside the CommonJS closure and inaccessible to an ESM-style export {} clause. Now calling require() on an entry point only causes it to be lazily-initialized but all exports are still in the module scope and can still be exported using a normal export {} clause.
Now that this has been changed, import() of a module with top-level await (#253) is now allowed when code splitting is disabled. Previously this didn't work because import() with code splitting disabled was implemented by converting the module to CommonJS and using Promise.resolve().then(() => require()), but converting a module with top-level await to CommonJS is impossible because the CommonJS call signature must be synchronous. Now that this implemented using lazy initialization instead of CommonJS conversion, the closure wrapping the ESM file can now be async and the import() expression can be replaced by a call to the lazy initializer.
Adding the ability for ESM files to be lazily-initialized is an important step toward additional future code splitting improvements including: manual chunk names (#207), correct import evaluation order (#399), and correct top-level await evaluation order (#253). These features all need to make use of deferred evaluation of ESM code.
In addition, calling require() on an ESM file now recursively wraps all transitive dependencies of that file instead of just wrapping that ESM file itself. This is an increase in the size of the generated code, but it is important for correctness (#667). Calling require() on a module means its evaluation order is determined at run-time, which means the evaluation order of all dependencies must also be determined at run-time. If you don't want the increase in code size, you should use an import statement instead of a require() call.
Dynamic imports now use chunk names instead of entry names (#1056)
Previously the output paths of dynamic imports (files imported using the import() syntax) were determined by the --entry-names= setting. However, this can cause problems if you configure the --entry-names= setting to omit both [dir] and [hash] because then two dynamic imports with the same name will cause an output file name collision.
Now dynamic imports use the --chunk-names= setting instead, which is used for automatically-generated chunks. This setting is effectively required to include [hash] so dynamic import name collisions should now be avoided.
In addition, dynamic imports no longer affect the automatically-computed default value of outbase. By default outbase is computed to be the lowest common ancestor directory of all entry points. Previously dynamic imports were considered entry points in this calculation so adding a dynamic entry point could unexpectedly affect entry point output file paths. This issue has now been fixed.
Allow custom output paths for individual entry points
By default, esbuild will automatically generate an output path for each entry point by computing the relative path from the outbase directory to the entry point path, and then joining that relative path to the outdir directory. The output path can be customized using outpath, but that only works for a single file. Sometimes you may need custom output paths while using multiple entry points. You can now do this by passing the entry points as a map instead of an array:
CLI
esbuild out1=in1.js out2=in2.js --outdir=out
JS
esbuild.build({
entryPoints: {
out1: 'in1.js',
out2: 'in2.js',
},
outdir: 'out',
})
Go
api.Build(api.BuildOptions{
EntryPointsAdvanced: []api.EntryPoint{{
OutputPath: "out1",
InputPath: "in1.js",
}, {
OutputPath: "out2",
InputPath: "in2.js",
}},
Outdir: "out",
})
This will cause esbuild to generate the files out/out1.js and out/out2.js inside the output directory. These custom output paths are used as input for the --entry-names= path template setting, so you can use something like --entry-names=[dir]/[name]-[hash] to add an automatically-computed hash to each entry point while still using the custom output path.
Derive entry point output paths from the original input path (#945)
Previously esbuild would determine the output path for an entry point by looking at the post-resolved path. For example, running esbuild --bundle react --outdir=out would generate the output path out/index.js because the input path react was resolved to node_modules/react/index.js. With this release, the output path is now determined by looking at the pre-resolved path. For example, running esbuild --bundle react --outdir=out now generates the output path out/react.js. If you need to keep using the output path that esbuild previously generated with the old behavior, you can use the custom output path feature (described above).
Use the file namespace for file entry points (#791)
Plugins that contain an onResolve callback with the file filter don't apply to entry point paths because it's not clear that entry point paths are files. For example, you could potentially bundle an entry point of https://www.example.com/file.js with a HTTP plugin that automatically downloads data from the server at that URL. But this behavior can be unexpected for people writing plugins.
With this release, esbuild will do a quick check first to see if the entry point path exists on the file system before running plugins. If it exists as a file, the namespace will now be file for that entry point path. This only checks the exact entry point name and doesn't attempt to search for the file, so for example it won't handle cases where you pass a package path as an entry point or where you pass an entry point without an extension. Hopefully this should help improve this situation in the common case where the entry point is an exact path.
In addition to the breaking changes above, the following features are also included in this release:
Warn about mutation of private methods (#1067)
Mutating a private method in JavaScript is not allowed, and will throw at run-time:
class Foo {
#method() {}
mutate() {
this.#method = () => {}
}
}
This is the case both when esbuild passes the syntax through untransformed and when esbuild transforms the syntax into the equivalent code that uses a WeakSet to emulate private methods in older browsers. However, it's clear from this code that doing this will always throw, so this code is almost surely a mistake. With this release, esbuild will now warn when you do this. This change was contributed by @jridgewell.
Fix some obscure TypeScript type parsing edge cases
In TypeScript, type parameters come after a type and are placed in angle brackets like Foo<T>. However, certain built-in types do not accept type parameters including primitive types such as number. This means if (x as number < 1) {} is not a syntax error while if (x as Foo < 1) {} is a syntax error. This release changes TypeScript type parsing to allow type parameters in a more restricted set of situations, which should hopefully better resolve these type parsing ambiguities.
Fix a crash that was introduced in the previous release
Fix a crash that was introduced in the previous release (#1064)
This crash happens when code splitting is active and there is a CSS entry point as well as two or more JavaScript entry points. There is a known issue where CSS bundling does not work when code splitting is active (code splitting is still a work in progress, see #608) so doing this will likely not work as expected. But esbuild obviously shouldn't crash. This release fixes the crash, although esbuild still does not yet generate the correct CSS output in this case.
Fix private fields inside destructuring assignment (#1066)
Private field syntax (i.e. this.#field) is supported for older language targets by converting the code into accesses into a WeakMap. However, although regular assignment (i.e. this.#field = 1) was handled destructuring assignment (i.e. [this.#field] = [1]) was not handled due to an oversight. Support for private fields inside destructuring assignment is now included with this release.
Fix an issue with direct eval and top-level symbols
It was previously the case that using direct eval caused the file containing it to be considered a CommonJS file, even if the file used ESM syntax. This was because the evaluated code could potentially attempt to interact with top-level symbols by name and the CommonJS closure was used to isolate those symbols from other modules so their names could be preserved (otherwise their names may need to be renamed to avoid collisions). However, ESM files are no longer convertable to CommonJS files due to the need to support top-level await.
This caused a bug where scope hoisting could potentially merge two modules containing direct eval and containing the same top-level symbol name into the same scope. These symbols were prevented from being renamed due to the direct eval, which caused a syntax error at run-time due to the name collision.
Because of this, esbuild is dropping the guarantee that using direct eval in an ESM file will be able to access top-level symbols. These symbols are now free to be renamed to avoid name collisions, and will now be minified when identifier minification is enabled. This is unlikely to affect real-world code because most real-world uses of direct eval only attempt to access local variables, not top-level symbols.
Using direct eval in an ESM file when bundling with esbuild will generate a warning. The warning is not new and is present in previous releases of esbuild as well. The way to avoid the warning is to avoid direct eval, since direct eval is somewhat of an anti-pattern and there are better alternatives.
Expose metafile to onRebuild in watch mode
Expose metafile to onRebuild in watch mode (#1057)
Previously the build results returned to the watch mode onRebuild callback was missing the metafile property when the metafile: true option was present. This bug has been fixed.
Add a formatMessages API (#1058)
This API lets you print log messages to the terminal using the same log format that esbuild itself uses. This can be used to filter esbuild's warnings while still making the output look the same. Here's an example of calling this API:
import esbuild from 'esbuild'
const formatted = await esbuild.formatMessages([{
text: '"test" has already been declared',
location: { file: 'file.js', line: 2, column: 4, length: 4, lineText: 'let test = "second"' },
notes: [{
text: '"test" was originally declared here',
location: { file: 'file.js', line: 1, column: 4, length: 4, lineText: 'let test = "first"' },
}],
}], {
kind: 'error',
color: true,
terminalWidth: 100,
})
process.stdout.write(formatted.join(''))
Remove the file splitting optimization (#998)
This release removes the "file splitting optimization" that has up to this point been a part of esbuild's code splitting algorithm. This optimization allowed code within a single file to end up in separate chunks as long as that code had no side effects. For example, bundling two entry points that both use a disjoint set of code from a shared file consisting only of code without side effects would previously not generate any shared code chunks at all.
This optimization is being removed because the top-level await feature was added to JavaScript after this optimization was added, and performing this optimization in the presence of top-level await is more difficult than before. The correct evaulation order of a module graph containing top-level await is extremely complicated and is specified at the module boundary. Moving code that is marked as having no side effects across module boundaries under these additional constraints is even more complexity and is getting in the way of implementing top-level await. So the optimization has been removed to unblock work on top-level await, which esbuild must support.
That said, there are no breaking API changes in this release. The breaking changes are instead about how input files are interpreted and/or how output…
This release contains backwards-incompatible changes. Since esbuild is before version 1.0.0, these changes have been released as a new minor version to reflect this (as recommended by npm). You should either be pinning the exact version of esbuild in your package.json file or be using a version range syntax that only accepts patch upgrades such as ~0.9.0. See the documentation about semver for more information.
That said, there are no breaking API changes in this release. The breaking changes are instead about how input files are interpreted and/or how output files are generated in some cases. So upgrading should be relatively straightforward as your API calls should still work the same way, but please make sure to test your code when you upgrade because the output may be different. These breaking changes are as follows:
No longer support module or exports in an ESM file (#769)
This removes support for using CommonJS exports in a file with ESM exports. Previously this worked by converting the ESM file to CommonJS and then mixing the CommonJS and ESM exports into the same exports object. But it turns out that supporting this is additional complexity for the bundler, so it has been removed. It's also not something that works in real JavaScript environments since modules will never support both export syntaxes at once.
Note that this doesn't remove support for using require in ESM files. Doing this still works (and can be made to work in a real ESM environment by assigning to globalThis.require). This also doesn't remove support for using import in CommonJS files. Doing this also still works.
No longer change import() to require() (#1029)
Previously esbuild's transform for import() matched TypeScript's behavior, which is to transform it into Promise.resolve().then(() => require()) when the current output format is something other than ESM. This was done when an import is external (i.e. not bundled), either due to the expression not being a string or due to the string matching an external import path.
With this release, esbuild will no longer do this. Now import() expressions will be preserved in the output instead. These expressions can be handled in non-ESM code by arranging for the import identifier to be a function that imports ESM code. This is how node works, so it will now be possible to use import() with node when the output format is something other than ESM.
Run-time export * as statements no longer convert the file to CommonJS
Certain export * as statements require a bundler to evaluate them at run-time instead of at compile-time like the JavaScript specification. This is the case when re-exporting symbols from an external file and a file in CommonJS format.
Previously esbuild would handle this by converting the module containing the export * as statement to CommonJS too, since CommonJS exports are evaluated at run-time while ESM exports are evaluated at bundle-time. However, this is undesirable because tree shaking only works for ESM, not for CommonJS, and the CommonJS wrapper causes additional code bloat. Another upcoming problem is that top-level await cannot work within a CommonJS module because CommonJS require() is synchronous.
With this release, esbuild will now convert modules containing a run-time export * as statement to a special ESM-plus-dynamic-fallback mode. In this mode, named exports present at bundle time can still be imported directly by name, but any imports that don't match one of the explicit named imports present at bundle time will be converted to a property access on the fallback object instead of being a bundle error. These property accesses are then resolved at run-time and will be undefined if the export is missing.
Change whether certain files are interpreted as ESM or CommonJS (#1043)
The bundling algorithm currently doesn't contain any logic that requires flagging modules as CommonJS vs. ESM beforehand. Instead it handles a superset and then sort of decides later if the module should be treated as CommonJS vs. ESM based on whether the module uses the module or exports variables and/or the exports keyword.
With this release, files that follow node's rules for module types will be flagged as explicitly ESM. This includes files that end in .mjs and files within a package containing "type": "module" in the enclosing package.json file. The CommonJS module and exports features will be unavailable in these files. This matters most for files without any exports, since then it's otherwise ambiguous what the module type is.
In addition, files without exports should now accurately fall back to being considered CommonJS. They should now generate a default export of an empty object when imported using an import statement, since that's what happens in node when you import a CommonJS file into an ESM file in node. Previously the default export could be undefined because these export-less files were sort of treated as ESM but with missing import errors turned into warnings instead.
This is an edge case that rarely comes up in practice, since you usually never import things from a module that has no exports.
In addition to the breaking changes above, the following features are also included in this release:
Initial support for bundling with top-level await (#253)
Top-level await is a feature that lets you use an await expression at the top level (outside of an async function). Here is an example:
let promise = fetch('https://www.example.com/data')
export let data = await promise.then(x => x.json())
Top-level await only works in ECMAScript modules, and does not work in CommonJS modules. This means that you must use an import statement or an import() expression to import a module containing top-level await. You cannot use require() because it's synchronous while top-level await is asynchronous. There should be a descriptive error message when you try to do this.
This initial release only has limited support for top-level await. It is only supported with the esm output format, but not with the iife or cjs output formats. In addition, the compilation is not correct in that two modules that both contain top-level await and that are siblings in the import graph will be evaluated in serial instead of in parallel. Full support for top-level await will come in a future release.
Add the ability to set sourceRoot in source maps (#1028)
You can now use the --source-root= flag to set the sourceRoot field in source maps generated by esbuild. When a sourceRoot is present in a source map, all source paths are resolved relative to it. This is particularly useful when you are hosting compiled code on a server and you want to point the source files to a GitHub repo, such as what AMP does.
Here is the description of sourceRoot from the source map specification:
An optional source root, useful for relocating source files on a server or removing repeated values in the "sources" entry. This value is prepended to the individual entries in the "source" field. If the sources are not absolute URLs after prepending of the "sourceRoot", the sources are resolved relative to the SourceMap (like resolving script src in a html document).
This feature was contributed by @jridgewell.
Allow plugins to return custom file watcher paths
Currently esbuild's watch mode automatically watches all file system paths that are handled by esbuild itself, and also automatically watches the paths of files loaded by plugins when the paths are in the file namespace. The paths of files that plugins load in namespaces other than the file namespace are not automatically watched.
Also, esbuild never automatically watches any file system paths that are consulted by the plugin during its processing, since esbuild is not aware of those paths. For example, this means that if a plugin calls require.resolve(), all of the various "does this file exist" checks that it does will not be watched automatically. So if one of those files is created in the future, esbuild's watch mode will not rebuild automatically even though the build is now outdated.
To fix this problem, this release introduces the watchFiles and watchDirs properties on plugin return values. Plugins can specify these to add additional custom file system paths to esbuild's internal watch list. Paths in the watchFiles array cause esbuild to rebuild if the file contents change, and paths in the watchDirs array cause esbuild to rebuild if the set of directory entry names changes for that directory path.
Note that watchDirs does not cause esbuild to rebuild if any of the contents of files inside that directory are changed. It also does not recursively traverse through subdirectories. It only watches the set of directory entry names (i.e. the output of the Unix ls command).
Add support for Android on ARM 64-bit
Add support for Android on ARM 64-bit (#803)
This release includes support for Android in the official esbuild package. It should now be possible to install and run esbuild on Android devices through npm.
Fix incorrect MIME types on Windows (#1030)
The web server built into esbuild uses the file extension to determine the value of the Content-Type header. This was previously done using the mime.TypeByExtension() function from Go's standard library. However, this function is apparently broken on Windows because installed programs can change MIME types in the Windows registry: golang/go#32350. This release fixes the problem by using a copy of Go's mime.TypeByExtension() function without the part that reads from the Windows registry.
Using a top-level return inside an ECMAScript module is now forbidden
The CommonJS module format is implemented as an anonymous function wrapper, so technically you can use a top-level return statement and it will actually work. Some packages in the wild use this to exit early from module initialization, so esbuild supports this. However, the ECMAScript module format doesn't allow top-level returns. With this release, esbuild no longer allows top-level returns in ECMAScript modules.
Expose build options to plugins
Expose build options to plugins (#373)
Plugins can now access build options from within the plugin using the initialOptions property. For example:
let nodeEnvPlugin = {
name: 'node-env',
setup(build) {
const options = build.initialOptions
options.define = options.define || {}
options.define['process.env.NODE_ENV'] =
options.minify ? '"production"' : '"development"'
},
}
Fix an edge case with the object spread transform (#1017)
This release fixes esbuild's object spread transform in cases where property assignment could be different than property definition. For example:
console.log({
get x() {},
...{x: 1},
})
This should print {x: 1} but transforming this through esbuild with --target=es6 causes the resulting code to throw an error. The problem is that esbuild currently transforms this code to a call to Object.assign and that uses property assignment semantics, which causes the assignment to throw (since you can't assign to a getter-only property).
With this release, esbuild will now transform this into code that manually loops over the properties and copies them over one-by-one using Object.defineProperty instead. This uses property definition semantics which better matches the specification.
Fix a TypeScript parsing edge case with arrow function return types (#1016)
This release fixes the following TypeScript parsing edge case:
():Array<number>=>{return [1]}
This was tripping up esbuild's TypeScript parser because the >= token was split into a > token and a = token because the > token is needed to close the type parameter list, but the = token was not being combined with the following > token to form a => token. This is normally not an issue because there is normally a space in between the > and the => tokens here. The issue only happened when the spaces were removed. This bug has been fixed. Now after the >= token is split, esbuild will expand the = token into the following characters if possible, which can result in a =>, ==, or === token.
Enable faster synchronous transforms under a flag (#1000)
Currently the synchronous JavaScript API calls transformSync and buildSync spawn a new child process on every call. This is due to limitations with node's child_process API. Doing this means transformSync and buildSync are much slower than transform and build, which share the same child process across calls.
There was previously a workaround for this limitation that uses node's worker_threads API and atomics to block the main thread while asynchronous communication happens in a worker, but that was reverted due to a bug in node's worker_threads implementation. Now that this bug has been fixed by node, I am re-enabling this workaround. This should result in transformSync and buildSync being much faster.
This approach is experimental and is currently only enabled if the ESBUILD_WORKER_THREADS environment variable is present. If this use case matters to you, please try it out and let me know if you find any problems with it.
Update how optional chains are compiled to match new V8 versions (#1019)
An optional chain is an expression that uses the ?. operator, which roughly avoids evaluation of the right-hand side if the left-hand side is null or undefined. So a?.b is basically equivalent to a == null ? void 0 : a.b. When the language target is set to es2019 or below, esbuild will transform optional chain expressions into equivalent expressions that do not use the ?. operator.
This transform is designed to match the behavior of V8 exactly, and is designed to do something similar to the equivalent transform done by the TypeScript compiler. However, V8 has recently changed its behavior in two cases:
Forced call of an optional member expression should propagate the object to the method:
const o = { m() { return this; } };
assert((o?.m)() === o);
V8 bug: https://bugs.chromium.org/p/v8/issues/detail?id=10024
Optional call of eval must be an indirect eval:
globalThis.a = 'global';
var b = (a => eval?.('a'))('local');
assert(b === 'global');
V8 bug: https://bugs.chromium.org/p/v8/issues/detail?id=10630
This release changes esbuild's transform to match V8's new behavior. The transform in the TypeScript compiler is still emulating the old behavior as of version 4.2.3, so these syntax forms should be avoided in TypeScript code for portability.
Fix parsing of the [dir] placeholder
Fix parsing of the [dir] placeholder (#1013)
The entry names feature in the previous release accidentally didn't include parsing for the [dir] placeholder, so the [dir] placeholder was passed through verbatim into the resulting output paths. This release fixes the bug, which means you can now use the [dir] placeholder. Sorry about the oversight.
Parse and ignore the deprecated @-ms-viewport CSS rule
Enable hashes in entry point file paths (#518)
This release adds the new --entry-names= flag. It's similar to the --chunk-names= and --asset-names= flags except it sets the output paths for entry point files. The pattern defaults to [dir]/[name] which should be equivalent to the previous entry point output path behavior, so this should be a backward-compatible change.
This change has the following consequences:
It is now possible for entry point output paths to contain a hash. For example, this now happens if you pass --entry-names=[dir]/[name]-[hash]. This means you can now use esbuild to generate output files such that all output paths have a hash in them, which means it should now be possible to serve the output files with an infinite cache lifetime so they are only downloaded once and then cached by the browser forever.
It is now possible to prevent the generation of subdirectories inside the output directory. Previously esbuild replicated the directory structure of the input entry points relative to the outbase directory (which defaults to the lowest common ancestor directory across all entry points). This value is substituted into the newly-added [dir] placeholder. But you can now omit it by omitting that placeholder, like this: --entry-names=[name].
Source map names should now be equal to the corresponding output file name plus an additional .map extension. Previously the hashes were content hashes, so the source map had a different hash than the corresponding output file because they had different contents. Now they have the same hash so finding the source map should now be easier (just add .map).
Due to the way the new hashing algorithm works, all chunks can now be generated fully in parallel instead of some chunks having to wait until their dependency chunks have been generated first. The import paths for dependency chunks are now swapped in after chunk generation in a second pass (detailed below). This could theoretically result in a speedup although I haven't done any benchmarks around this.
Implementing this feature required overhauling how hashes are calculated to prevent the chicken-and-egg hashing problem due to dynamic imports, which can cause cycles in the import graph of the resulting output files when code splitting is enabled. Since generating a hash involved first hashing all of your dependencies, you could end up in a situation where you needed to know the hash to calculate the hash (if a file was a dependency of itself).
The hashing algorithm now works in three steps (potentially subject to change in the future):
The initial versions of all output files are generated in parallel, with temporary paths used for any imports of other output files. Each temporary path is a randomly-generated string that is unique for each output file. An initial source map is also generated at this step if source maps are enabled.
The hash for the first step includes: the raw content of the output file excluding the temporary paths, the relative file paths of all input files present in that output file, the relative output path for the resulting output file (with [hash] for the hash that hasn't been computed yet), and contents of the initial source map.
After the initial versions of all output files have been generated, calculate the final hash and final output path for each output file. Calculating the final output path involves substituting the final hash for the [hash] placeholder in the entry name template.
The hash for the second step includes: the hash from the first step for this file and all of its transitive dependencies.
After all output files have a final output path, the import paths in each output file for importing other output files are substituted. Source map offsets also have to be adjusted because the final output path is likely a different length than the temporary path used in the first step. This is also done in parallel for each output file.
This whole algorithm roughly means the hash of a given output file should change if an only if any input file in that output file or any output file it depends on is changed. So the output path and therefore the browser's cache key should not change for a given output file in between builds if none of the relevant input files were changed.
Fix importing a path containing a ? character on Windows (#989)
On Windows, the ? character is not allowed in path names. This causes esbuild to fail to import paths containing this character. This is usually fine because people don't put ? in their file names for this reason. However, the import paths for some ancient CSS code contains the ? character as a hack to work around a bug in Internet Explorer:
@font-face {
src:
url("./icons.eot?#iefix") format('embedded-opentype'),
url("./icons.woff2") format('woff2'),
url("./icons.woff") format('woff'),
url("./icons.ttf") format('truetype'),
url("./icons.svg#icons") format('svg');
}
The intent is for the bundler to ignore the ?#iefix part. However, there may actually be a file called icons.eot?#iefix on the file system so esbuild checks the file system for both icons.eot?#iefix and icons.eot. This check was triggering this issue. With this release, an invalid path is considered the same as a missing file so bundling code like this should now work on Windows.
Parse and ignore the deprecated @-ms-viewport CSS rule (#997)
The @viewport rule has been deprecated and removed from the web. Modern browsers now completely ignore this rule. However, in theory it sounds like would still work for mobile versions of Internet Explorer, if those still exist. The https://ant.design/ library contains an instance of the @-ms-viewport rule and it currently causes a warning with esbuild, so this release adds support for parsing this rule to disable the warning.
Avoid mutating the binary executable file in place (#963)
This release changes the install script for the esbuild npm package to use the "rename a temporary file" approach instead of the "write the file directly" approach to replace the esbuild command stub file with the real binary executable. This should hopefully work around a problem with the pnpm package manager and its use of hard links.
Avoid warning about potential issues with sideEffects in packages (#999)
Bare imports such as import "foo" mean the package is only imported for its side effects. Doing this when the package contains "sideEffects": false in package.json causes a warning because it means esbuild will not import the file since it has been marked as having no side effects, even though the import statement clearly expects it to have side effects. This is usually caused by an incorrect sideEffects annotation in the package.
However, this warning is not immediately actionable if the file containing the import statement is itself in a package. So with this release, esbuild will no longer issue this warning if the file containing the import is inside a node_modules folder. Note that even though the warning is no longer there, this situation can still result in a broken bundle if the sideEffects annotation is incorrect.
Fix path resolution with the exports field for scoped packages
Fix path resolution with the exports field for scoped packages
This release fixes a bug where the exports field in package.json files was not being detected for scoped packages (i.e. packages of the form @scope/pkg-name instead of just pkg-name). The exports field should now be respected for these kinds of packages.
Improved error message in exports failure case
Node's new conditional exports feature can be non-intuitive and hard to use. Now that esbuild supports this feature (as of version 0.9.0), you can get into a situation where it's impossible to import a package if the package's exports field in its package.json file isn't configured correctly.
Previously the error message for this looked like this:
> entry.js:1:7: error: Could not resolve "jotai" (mark it as external to exclude it from the bundle)
1 │ import 'jotai'
╵ ~~~~~~~
node_modules/jotai/package.json:16:13: note: The path "." is not exported by "jotai"
16 │ "exports": {
╵ ^
With this release, the error message will now provide additional information about why the package cannot be imported:
> entry.js:1:7: error: Could not resolve "jotai" (mark it as external to exclude it from the bundle)
1 │ import 'jotai'
╵ ~~~~~~~
node_modules/jotai/package.json:16:13: note: The path "." is not currently exported by package "jotai"
16 │ "exports": {
╵ ^
node_modules/jotai/package.json:18:9: note: None of the conditions provided ("module", "require", "types") match any of the currently active conditions ("browser", "default", "import")
18 │ ".": {
╵ ^
entry.js:1:7: note: Consider using a "require()" call to import this package
1 │ import 'jotai'
╵ ~~~~~~~
In this case, one solution could be import this module using require() since this package provides an export for the require condition. Another solution could be to pass --conditions=module to esbuild since this package provides an export for the module condition (the types condition is likely not valid JavaScript code).
This problem occurs because this package doesn't provide an import path for ESM code using the import condition and also doesn't provide a fallback import path using the default condition.
Mention glob syntax in entry point error messages (#976)
In this release, including a * in the entry point path now causes the failure message to tell you that glob syntax must be expanded first before passing the paths to esbuild. People that hit this are usually converting an existing CLI command to a JavaScript API call and don't know that glob expansion is done by their shell instead of by esbuild. An appropriate fix is to use a library such as glob to expand the glob pattern first before passing the paths to esbuild.
Raise certain VM versions in the JavaScript feature compatibility table
JavaScript VM feature compatibility data is derived from this dataset: https://kangax.github.io/compat-table/. The scripts that process the dataset expand the data to include all VM versions that support a given feature (e.g. chrome44, chrome45, chrome46, ...) so esbuild takes the minimum observed version as the first version for which the feature is supported.
However, some features can have subtests that each check a different aspect of the feature. In this case the desired version is the minimum version within each individual subtest, but the maximum of those versions across all subtests (since esbuild should only use the feature if it works in all cases). Previously esbuild computed the minimum version across all subtests, but now esbuild computes the maximum version across all subtests. This means esbuild will now lower JavaScript syntax in more cases.
Mention the configured target environment in error messages (#975)
Using newer JavaScript syntax with an older target environment (e.g. chrome10) can cause a build error if esbuild doesn't support transforming that syntax such that it is compatible with that target environment. Previously the error message was generic but with this release, the target environment is called outp explicitly in the error message. This is helpful if esbuild is being wrapped by some other tool since the other tool can obscure what target environment is actually being passed to esbuild.
Fix an issue with Unicode and source maps
This release fixes a bug where non-ASCII content that ended up in an output file but that was not part of an input file could throw off source mappings. An example of this would be passing a string containing non-ASCII characters to the globalName setting with the minify setting active and the charset setting set to utf8. The conditions for this bug are fairly specific and unlikely to be hit, so it's unsurprising that this issue hasn't been discovered earlier. It's also unlikely that this issue affected real-world code.
The underlying cause is that while the meaning of column numbers in source maps is undefined in the specification, in practice most tools treat it as the number of UTF-16 code units from the start of the line. The bug happened because column increments for outside-of-file characters were incorrectly counted using byte offsets instead of UTF-16 code unit counts.
Fix export name annotations in CommonJS output for node
Fix export name annotations in CommonJS output for node (#960)
The previous release introduced a regression that caused a syntax error when building ESM files that have a default export with --platform=node. This is because the generated export contained the default keyword like this: 0 && (module.exports = {default});. This regression has been fixed.
Fix bundling when parent directory is inaccessible
Fix bundling when parent directory is inaccessible (#938)
Previously bundling with esbuild when a parent directory is inaccessible did not work because esbuild would try to read the directory to search for a node_modules folder and would then fail the build when that failed. In practice this caused issues in certain Linux environments where a directory close to the root directory was inaccessible (e.g. on Android). With this release, esbuild will treat inaccessible directories as empty to allow for the node_modules search to continue past the inaccessible directory and into its parent directory. This means it should now be possible to bundle with esbuild in these situations.
Avoid allocations in JavaScript API stdout processing (#941)
This release improves the efficiency of the JavaScript API. The API runs the binary esbuild executable in a child process and then communicates with it over stdin/stdout. Previously the stdout buffer containing the remaining partial message was copied after each batch of messages due to a bug. This was unintentional and unnecessary, and has been removed. Now this part of the code no longer involves any allocations. This fix was contributed by @jridgewell.
Support conditional @import syntax when not bundling (#953)
Previously conditional CSS imports such as @import "print.css" print; was not supported at all and was considered a syntax error. With this release, it is now supported in all cases except when bundling an internal import. Support for bundling internal CSS imports is planned but will happen in a later release.
Always lower object spread and rest when targeting V8 (#951)
This release causes object spread (e.g. a = {...b}) and object rest (e.g. {...a} = b) to always be lowered to a manual implementation instead of using native syntax when the --target= parameter includes a V8-based JavaScript runtime such as chrome, edge, or node. It turns out this feature is implemented inefficiently in V8 and copying properties over to a new object is around a 2x performance improvement. In addition, doing this manually instead of using the native implementation generates a lot less work for the garbage collector. You can see V8 bug 11536 for details. If the V8 performance bug is eventually fixed, the translation of this syntax will be disabled again for V8-based targets containing the bug fix.
Fix object rest return value (#956)
This release fixes a bug where the value of an object rest assignment was incorrect if the object rest assignment was lowered:
// This code was affected
let x, y
console.log({x, ...y} = {x: 1, y: 2})
Previously this code would incorrectly print {y: 2} (the value assigned to y) when the object rest expression was lowered (i.e. with --target=es2017 or below). Now this code will correctly print {x: 1, y: 2} instead. This bug did not affect code that did not rely on the return value of the assignment expression, such as this code:
// This code was not affected
let x, y
({x, ...y} = {x: 1, y: 2})
Basic support for CSS page margin rules (#955)
There are 16 different special at-rules that can be nested inside the @page rule. They are defined in this specification. Previously esbuild treated these as unknown rules, but with this release esbuild will now treat these as known rules. The only real difference in behavior is that esbuild will no longer warn about these rules being unknown.
Add export name annotations to CommonJS output for node
When you import a CommonJS file using an ESM import statement in node, the default import is the value of module.exports in the CommonJS file. In addition, node attempts to generate named exports for properties of the module.exports object.
Except that node doesn't actually ever look at the properties of that object to determine the export names. Instead it parses the CommonJS file and scans the AST for certain syntax patterns. A full list of supported patterns can be found in the documentation for the cjs-module-lexer package. This library doesn't currently support the syntax patterns used by esbuild.
While esbuild could adapt its syntax to these patterns, the patterns are less compact than the ones used by esbuild and doing this would lead to code bloat. Supporting two separate ways of generating export getters would also complicate esbuild's internal implementation, which is undesirable.
Another alternative could be to update the implementation of cjs-module-lexer to support the specific patterns used by esbuild. This is also undesirable because this pattern detection would break when minification is enabled, this would tightly couple esbuild's output format with node and prevent esbuild from changing it, and it wouldn't work for existing and previous versions of node that still have the old version of this library.
Instead, esbuild will now add additional code to "annotate" ESM files that have been converted to CommonJS when esbuild's platform has been set to node. The annotation is dead code but is still detected by the cjs-module-lexer library. If the original ESM file has the exports foo and bar, the additional annotation code will look like this:
0 && (module.exports = {foo, bar});
This allows you to use named imports with an ESM import statement in node (previously you could only use the default import):
import { foo, bar } from './file-built-by-esbuild.cjs'
Adding support for this to esbuild is a breaking change (i.e. code that was working fine before can easily stop working) so adding support for it has…
This release contains backwards-incompatible changes. Since esbuild is before version 1.0.0, these changes have been released as a new minor version to reflect this (as recommended by npm). You should either be pinning the exact version of esbuild in your package.json file or be using a version range syntax that only accepts patch upgrades such as ^0.8.0. See the documentation about semver for more information.
Add support for node's exports field in package.json files (#187)
This feature was recently added to node. It allows you to rewrite what import paths inside your package map to as well as to prevent people from importing certain files in your package. Adding support for this to esbuild is a breaking change (i.e. code that was working fine before can easily stop working) so adding support for it has been delayed until this breaking change release.
One way to use this feature is to remap import paths for your package. For example, this would remap an import of your-pkg/esm/lib.js (the "public" import path) to your-pkg/dist/esm/lib.js (the "private" file system path):
{
"name": "your-pkg",
"exports": {
"./esm/*": "./dist/esm/*",
"./cjs/*": "./dist/cjs/*"
}
}
Another way to use this feature is to have conditional imports where the same import path can mean different things in different situations. For example, this would remap require('your-pkg') to your-pkg/required.cjs and import 'your-pkg' to your-pkg/imported.mjs:
{
"name": "your-pkg",
"exports": {
"import": "./imported.mjs",
"require": "./required.cjs"
}
}
There is built-in support for the import and require conditions depending on the kind of import and the browser and node conditions depending on the current platform. In addition, the default condition always applies regardless of the current configuration settings and can be used as a catch-all fallback condition.
Note that when you use conditions, your package may end up in the bundle multiple times! This is a subtle issue that can cause bugs due to duplicate copies of your code's state in addition to bloating the resulting bundle. This is commonly known as the dual package hazard. The primary way of avoiding this is to put all of your code in the require condition and have the import condition just be a light wrapper that calls require on your package and re-exports the package using ESM syntax.
There is also support for custom conditions with the --conditions= flag. The meaning of these is entirely up to package authors. For example, you could imagine a package that requires you to configure --conditions=test,en-US. Node has currently only endorsed the development and production custom conditions for recommended use.
Remove the esbuild.startService() API
Due to #656, Calling service.stop() no longer does anything, so there is no longer a strong reason for keeping the esbuild.startService() API around. The primary thing it currently does is just make the API more complicated and harder to use. You can now just call esbuild.build() and esbuild.transform() directly instead of calling esbuild.startService().then(service => service.build()) or esbuild.startService().then(service => service.transform()).
If you are using esbuild in the browser, you now need to call esbuild.initialize({ wasmURL }) and wait for the returned promise before calling esbuild.transform(). It takes the same options that esbuild.startService() used to take. Note that the esbuild.buildSync() and esbuild.transformSync() APIs still exist when using esbuild in node. Nothing has changed about the synchronous esbuild APIs.
Remove the metafile from outputFiles (#633)
Previously using metafile with the API is unnecessarily cumbersome because you have to extract the JSON metadata from the output file yourself instead of it just being provided to you as a return value. This is especially a bummer if you are using write: false because then you need to use a for loop over the output files and do string comparisons with the file paths to try to find the one corresponding to the metafile. Returning the metadata directly is an important UX improvement for the API. It means you can now do this:
const result = await esbuild.build({
entryPoints: ['entry.js'],
bundle: true,
metafile: true,
})
console.log(result.metafile.outputs)
The banner and footer options are now language-specific (#712)
The --banner= and --footer= options now require you to pass the file type:
CLI:
esbuild --banner:js=//banner --footer:js=//footer
esbuild --banner:css=/*banner*/ --footer:css=/*footer*/
JavaScript
esbuild.build({
banner: { js: '//banner', css: '/*banner*/' },
footer: { js: '//footer', css: '/*footer*/' },
})
Go
api.Build(api.BuildOptions{
Banner: map[string]string{"js": "//banner"},
Footer: map[string]string{"js": "//footer"},
})
api.Build(api.BuildOptions{
Banner: map[string]string{"css": "/*banner*/"},
Footer: map[string]string{"css": "/*footer*/"},
})
This was changed because the feature was originally added in a JavaScript-specific manner, which was an oversight. CSS banners and footers must be separate from JavaScript banners and footers to avoid injecting JavaScript syntax into your CSS files.
The extensions .mjs and .cjs are no longer implicit
Previously the "resolve extensions" setting included .mjs and .cjs but this is no longer the case. This wasn't a good default because it doesn't match node's behavior and could break some packages. You now have to either explicitly specify these extensions or configure the "resolve extensions" setting yourself.
Remove the --summary flag and instead just always print a summary (#704)
The summary can be disabled if you don't want it by passing --log-level=warning instead. And it can be enabled in the API by setting logLevel: 'info'. I'm going to try this because I believe it will improve the UX. People have this problem with esbuild when they first try it where it runs so quickly that they think it must be broken, only to later discover that it actually worked fine. While this is funny, it seems like a good indication that the UX could be improved. So I'm going to try automatically printing a summary to see how that goes. Note that the summary is not printed if incremental builds are active (this includes the watch and serve modes).
Rename --error-limit= to --log-limit=
This parameter has been renamed because it now applies to both warnings and errors, not just to errors. Previously setting the error limit did not apply any limits to the number of warnings printed, which could sometimes result in a deluge of warnings that are problematic for Windows Command Prompt, which is very slow to print to and has very limited scrollback. Now the log limit applies to the total number of log messages including both errors and warnings, so no more than that number of messages will be printed. The log usually prints log messages immediately but it will now intentionally hold back warnings when approaching the limit to make room for possible future errors during a build. So if a build fails you should be guaranteed to see an error message (i.e. warnings can't use up the entire log limit and then prevent errors from being printed).
Remove the deprecated --avoid-tdz option
This option is now always enabled and cannot be disabled, so it is being removed from the API. The existing API parameter no longer does anything so this removal has no effect the generated output.
Remove SpinnerBusy and SpinnerIdle from the Go API
These options were part of an experiment with the CLI that didn't work out. Watch mode no longer uses a spinner because it turns out people want to be able to interleave esbuild's stderr pipe with other tools and were getting tripped up by the spinner animation. These options no longer do anything and have been removed.
Fix overlapping chunk names when code splitting is active
Fix overlapping chunk names when code splitting is active (#928)
Code splitting chunks use a content hash in their file name. This is good for caching because it means the file name is guaranteed to change if the chunk contents change, and the file name is guaranteed to stay the same if the chunk contents don't change (e.g. someone only modifies a comment). However, using a pure content hash can cause bugs if two separate chunks end up with the same contents.
A high-level example would be two identical copies of a library being accidentally collapsed into a single copy. While this results in a smaller bundle, this is incorrect because each copy might need to have its own state and so must be represented independently in the bundle.
This release fixes this issue by mixing additional information into the file name hash, which is no longer a content hash. The information includes the paths of the input files as well as the ranges of code within the file that are included in the chunk. File paths are used because they are a stable file identifier, but the relative path is used with / as the path separator to hopefully eliminate cross-platform differences between Unix and Windows.
Fix --keep-names for lowered class fields
Anonymous function expressions used in class field initializers are automatically assigned a .name property in JavaScript:
class Example {
field1 = () => {}
static field2 = () => {}
}
assert(new Example().field1.name === 'field1')
assert(Example.field2.name === 'field2')
This usually doesn't need special handling from esbuild's --keep-names option because esbuild doesn't modify field names, so the .name property will not change. However, esbuild will relocate the field initializer if the configured language target doesn't support class fields (e.g. --target=es6). In that case the .name property wasn't preserved even when --keep-names was specified. This bug has been fixed. Now the .name property should be preserved in this case as long as you enable --keep-names.
Enable importing certain data URLs in CSS and JavaScript
You can now import data URLs of type text/css using a CSS @import rule and import data URLs of type text/javascript and application/json using a JavaScript import statement. For example, doing this is now possible:
import 'data:text/javascript,console.log("hello!");';
import _ from 'data:application/json,"world!"';
This is for compatibility with node which supports this feature natively. Importing from a data URL is sometimes useful for injecting code to be evaluated before an external import without needing to generate a separate imported file.
Fix a discrepancy with esbuild's tsconfig.json implementation
Fix a discrepancy with esbuild's tsconfig.json implementation (#913)
If a tsconfig.json file contains a "baseUrl" value and "extends" another tsconfig.json file that contains a "paths" value, the base URL used for interpreting the paths should be the overridden value. Previously esbuild incorrectly used the inherited value, but with this release esbuild will now use the overridden value instead.
Work around the Jest testing framework breaking node's Buffer API (#914)
Running esbuild within a Jest test fails because Jest causes Buffer instances to not be considered Uint8Array instances, which then breaks the code esbuild uses to communicate with its child process. More info is here: https://github.com/facebook/jest/issues/4422. This release contains a workaround that copies each Buffer object into a Uint8Array object when this invariant is broken. That should prevent esbuild from crashing when it's run from within a Jest test.
Better handling of implicit main fields in package.json
If esbuild's automatic main vs. module detection is enabled for package.json files, esbuild will now use index.js as an implicit main field if the main field is missing but index.js is present. This means if a package.json file only contains a module field but not a main field and the package is imported using both an ESM import statement and a CommonJS require call, the index.js file will now be picked instead of the file in the module field.
_Note: This could be considered a breaking change or a bug fix depending on your point of view._
Align more closely with node's default import behavior for CommonJS (#532)
Note: This could be considered a breaking change or a bug fix depending on your point of view.
Importing a CommonJS file into an ESM file does not behave the same everywhere. Historically people compiled their ESM code into CommonJS using Babel before ESM was supported natively. More recently, node has made it possible to use ESM syntax natively but to still import CommonJS files into ESM. These behave differently in many ways but one of the most unfortunate differences is how the default export is handled.
When you import a normal CommonJS file, both Babel and node agree that the value of module.exports should be stored in the ESM import named default. However, if the CommonJS file used to be an ESM file but was compiled into a CommonJS file, Babel will set the ESM import named default to the value of the original ESM export named default while node will continue to set the ESM import named default to the value of module.exports. Babel detects if a CommonJS file used to be an ESM file by the presence of the exports.__esModule = true marker.
This is unfortunate because it means there is no general way to make code work with both ecosystems. With Babel the code import * as someFile from './some-file' can access the original default export with someFile.default but with node you need to use someFile.default.default instead. Previously esbuild followed Babel's approach but starting with this release, esbuild will now try to use a blend between the Babel and node approaches.
This is the new behavior: importing a CommonJS file will set the default import to module.exports in all cases except when module.exports.__esModule && "default" in module.exports, in which case it will fall through to module.exports.default. In other words: in cases where the default import was previously undefined for CommonJS files when exports.__esModule === true, the default import will now be module.exports. This should hopefully keep Babel cross-compiled ESM code mostly working but at the same time now enable some node-oriented code to start working.
If you are authoring a library using ESM but shipping it as CommonJS, the best way to avoid this mess is to just never use default exports in ESM. Only use named exports with names other than default.
Fix bug when ESM file has empty exports and is converted to CommonJS (#910)
A file containing the contents export {} is still considered to be an ESM file even though it has no exports. However, if a file containing this edge case is converted to CommonJS internally during bundling (e.g. when it is the target of require()), esbuild failed to mark the exports symbol from the CommonJS wrapping closure as used even though it is actually needed. This resulted in an output file that crashed when run. The exports symbol is now considered used in this case, so the bug has been fixed.
Avoid introducing this for imported function calls
It is possible to import a function exported by a CommonJS file into an ESM file like this:
import {fn} from './cjs-file.js'
console.log(fn())
When you do this, esbuild currently transforms your code into something like this:
var cjs_file = __toModule(require("./cjs-file.js"));
console.log(cjs_file.fn());
However, doing that changes the value of this observed by the export fn. The property access cjs_file.fn is in the syntactic "call target" position so the value of this becomes the value of cjs_file. With this release, esbuild will now use a different syntax in this case to avoid passing cjs_file as this:
var cjs_file = __toModule(require("./cjs-file.js"));
console.log((0, cjs_file.fn)());
This change in esbuild mirrors a similar recent TypeScript compiler change, and also makes esbuild more consistent with Babel which already does this transformation.
Fix ordering issue with private class methods
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 members
Normal 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 a WeakMap or a WeakSet 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 symbols
Using 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 direct eval 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 direct eval 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 ESM import from direct eval 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 direct eval.
I recently realized that the last consequence of direct eval (the problem about not being able to reference import 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 direct eval 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 direct eval 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 direct eval into that nested scope.
Using direct eval in a file in ESM format is now a warning. This is because the semantics of direct eval are poorly understood (most people don't intend to use direct eval at all) and because the negative consequences of bundling code with direct eval are usually unexpected and undesired. Of the few valid use cases for direct eval, it is usually a good idea to rewrite your code to avoid using direct eval 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 to featureFlags but avoids all of the negative consequences of bundling code with direct eval.
Support chunk and asset file name templates (#733, #888)
Support chunk and asset file name templates (#733, #888)
This release introduces the --chunk-names= and --asset-names= flags. These flags let you customize the output paths for chunks and assets within the output directory. Each output path is a template and currently supports these placeholders:
[name]: The original name of the file. This will be chunk for chunks and will be the original file name (without the extension) for assets.[hash]: The content hash of the file. This is not necessarily stable across different esbuild versions but will be stable within the same esbuild version.For example, if you want to move all chunks and assets into separate subdirectories, you could use --chunk-names=chunks/[name]-[hash] and --asset-names=assets/[name]-[hash]. Note that the path template should not include the file extension since the file extension is always automatically added to the end of the path template.
Additional name template features are planned in the future including a [dir] placeholder for the relative path from the outbase directory to the original input directory as well as an --entry-names= flag, but these extra features have not been implemented yet.
Handle this in class static field initializers (#885)
When you use this in a static field initializer inside a class statement or expression, it references the class object itself:
class Foo {
static Bar = class extends this {
}
}
assert(new Foo.Bar() instanceof Foo)
This case previously wasn't handled because doing this is a compile error in TypeScript code. However, JavaScript does allow this so esbuild needs to be able to handle this. This edge case should now work correctly with this release.
Do not warn about dynamic imports when .catch() is detected (#893)
Previously esbuild avoids warning about unbundled import() expressions when using the try { await import(_) } pattern, since presumably the try block is there to handle the run-time failure of the import() expression failing. This release adds some new patterns that will also suppress the warning: import(_).catch(_), import(_).then(_).catch(_), and import(_).then(_, _).
CSS namespaces are no longer supported
CSS namespaces are a weird feature that appears to only really be useful for styling XML. And the world has moved on from XHTML to HTML5 so pretty much no one uses CSS namespaces anymore. They are also complicated to support in a bundler because CSS namespaces are file-scoped, which means:
Default namespaces can be different in different files, in which case some default namespaces would have to be converted to prefixed namespaces to avoid collisions.
Prefixed namespaces from different files can use the same name, in which case some prefixed namespaces would need to be renamed to avoid collisions.
Instead of implementing all of that for an extremely obscure feature, CSS namespaces are now just explicitly not supported. The code to handle @namespace has been removed from esbuild. This will likely not affect anyone, especially because bundling code using CSS namespaces with esbuild didn't even work correctly in the first place.
Fix a concurrent map write with the --inject: feature
Fix a concurrent map write with the --inject: feature (#878)
This release fixes an issue where esbuild could potentially crash sometimes with a concurrent map write when using injected files and entry points that were neither relative nor absolute paths. This was an edge case where esbuild's low-level file subsystem was being used without being behind a mutex lock. This regression was likely introduced in version 0.8.21. The cause of the crash has been fixed.
Provide kind to onResolve plugins (#879)
Plugins that add onResolve callbacks now have access to the kind parameter which tells you what kind of import is being resolved. It will be one of the following values:
"entry-point" in JS (api.ResolveEntryPoint in Go)
An entry point provided by the user
"import-statement" in JS (api.ResolveJSImportStatement in Go)
A JavaScript import or export statement
"require-call" in JS (api.ResolveJSRequireCall in Go)
A JavaScript call to require(...) with a string argument
"dynamic-import" in JS (api.ResolveJSDynamicImport in Go)
A JavaScript import(...) expression with a string argument
"require-resolve" in JS (api.ResolveJSRequireResolve in Go)
A JavaScript call to require.resolve(...) with a string argument
"import-rule" in JS (api.ResolveCSSImportRule in Go)
A CSS @import rule
"url-token" in JS (api.ResolveCSSURLToken in Go)
A CSS url(...) token
These values are pretty much identical to the kind field in the JSON metadata file.
The stderr log format now contains line numbers after file names
The stderr log format now contains line numbers after file names (#865)
Error messages in stderr now have a line and column number after the file name.
Before:
> src/structs/RTree.js: warning: Duplicate key "compareMinX" in object literal
469 │ compareMinX: function (a, b)
╵ ~~~~~~~~~~~
src/structs/RTree.js: note: The original "compareMinX" is here
206 │ compareMinX: compareNodeMinX,
╵ ~~~~~~~~~~~
After:
> src/structs/RTree.js:469:4: warning: Duplicate key "compareMinX" in object literal
469 │ compareMinX: function (a, b)
╵ ~~~~~~~~~~~
src/structs/RTree.js:206:4: note: The original "compareMinX" is here
206 │ compareMinX: compareNodeMinX,
╵ ~~~~~~~~~~~
This should make log messages slightly easier to parse if you want to parse stderr instead of using esbuild's API. Previously you needed a multi-line regular expression to get the line number, but now that the line number is duplicated in two places you should only need a single-line regular expression.
Note that this is still the hacky way to get error information and is potentially unstable, since it will break if the log format changes. Log messages are mainly intended for humans. The straightforward and stable way to do this is still to use esbuild's API, which returns log messages as an array of objects.
Allow --define with import.meta
The --define feature lets you replace specific identifiers and member expression chains with compile-time constants. However, it previously didn't work with import.meta because this is a special case in the grammar. The import keyword is not actually an identifier expression. This distinction isn't helpful though, and it's not unreasonable to want to use the --define feature to replace import.meta properties too.
With this release, it's now possible to use e.g. --define:import.meta.foo=123 to replace specific properties accessed off of the import.meta object as well as to use e.g. --define:import.meta={\"foo\":123} to substitute the entire import.meta expression with something else.
Fix a race condition with multiple injected files (#871)
Using multiple injected files could cause a data race that trips Go's race detector. The data race has been fixed in this release. The fix was contributed by @Deleplace.
Change --serve behavior to serve on all interfaces (#866)
The default address for the --serve flag has changed from 127.0.0.1 (serve on the loopback interface) to 0.0.0.0 (serve on all interfaces). You can still manually specify either one using --serve=127.0.0.1:8000 or --serve=0.0.0.0:8000. This just changes the default behavior that happens when you pass --serve with no host address (or when you just use the --servedir= flag without --serve=).
In addition, you can now also specify an IPv6 address. Previously there was a parsing issue that prevented this. For example, you can pass --serve=[::1]:8000 to serve on the loopback interface and --serve=[::]:8000 to serve on all interfaces.
Change the import resolution rules of absolute paths (#862)
Previously absolute paths were considered to be pre-resolved by the resolver (in contrast to relative and package paths, which need to be converted to an absolute path). This meant that absolute paths which did not actually exist caused a failure in the loader when it tried to load the path instead of in the resolver when it tried to resolve the path.
With the previous change in version 0.8.47 to support removing URL query and/or hash parameters from the path, path resolution can now be run multiple times. If path resolution fails and the path contains a ? and/or #, path resolution is re-run with the URL query/hash parameters removed. It is problematic to consider absolute paths to be pre-resolved because it means that paths containing query/hash parameters make the loader try to load the wrong path, and do not run the resolver again with the parameter suffix removed.
In this release, esbuild will now validate absolute paths in the resolver. So invalid paths will now fail in the resolver and retry without the parameter suffix instead of failing in the loader, which correctly handles a parameter suffix on absolute paths. In addition, this release now handles implicit file extensions on absolute paths. This makes esbuild a more accurate copy of node's module resolution algorithm, which does this as well.
Output files in metafile now have entryPoint (#711)
There is now an optional entryPoint property on each output file in the JSON metadata file generated with the --metafile= flag. It is only present for output files that are the bundled results of entry point files, and contains the path name of the corresponding input entry point file. This property is not present on other kinds of output files (e.g. code splitting chunks). This feature was contributed by @remorses.
Using direct eval now pulls in module and exports
Using direct eval now pulls in module and exports
Use of direct eval forces the file to become a CommonJS module and disables dead code elimination in the entire file. The CommonJS closure is necessary to avoid name collisions with other modules, since eval means symbols in the file can no longer be renamed to avoid collisions.
However, the CommonJS module and exports variables that are arguments to the closure previously weren't considered to be used in this scenario, meaning they may be omitted as dead code for size reasons. This could cause code inside eval to behave incorrectly. Now use of direct eval automatically counts as a use of both module and exports so these variables should now always be present in this case.
Always remove all "use asm" directives (#856)
The asm.js subset of JavaScript has complicated validation rules that are triggered by this directive. The parser and code generator in esbuild was not designed with asm.js in mind and round-tripping asm.js code through esbuild will very likely cause it to no longer validate as asm.js. When this happens, V8 prints a warning and people don't like seeing the warning. The warning looks like this:
(node:58335) V8: example.js:3 Invalid asm.js: Unexpected token
(Use `node --trace-warnings ...` to show where the warning was created)
I am deliberately not attempting to preserve the validity of asm.js code because it's a complicated legacy format and it's obsolete now that WebAssembly exists. By removing all "use asm" directives, the code will just become normal JavaScript and work fine without generating a warning.
Fix a variable hoisting edge case (#857)
It is allowed to use a nested var hoisted declaration with the same name as a top-level function declaration. In that case the two symbols should merge and be treated as the same symbol:
async function x() {}
{
var x;
}
The parser previously allowed this for regular functions but not for async or generator functions. Now with this release, this behavior is also allowed for these special kinds of functions too.
Remove empty CSS rules when minifying (#851)
Empty rules with no content such as div {} are now removed when CSS is minified. This change was contributed by @susiwen8.
Work around a problem with pnpm and NODE_PATH
Work around a problem with pnpm and NODE_PATH (#816)
In version 0.8.43, esbuild added support for node's NODE_PATH environment variable which contains a list of global folders to use during path resolution. However, this causes a problem when esbuild is installed with pnpm, an alternative JavaScript package manager. Specifically pnpm adds a bogus path to NODE_PATH that doesn't exist but that has a file as a parent directory. Previously this caused esbuild to fail with the error not a directory. Now with this release, esbuild will ignore this bogus path instead of giving an error.
Add more names to the global no-side-effect list (#842)
This release adds almost all known globals from the browser and node to the list of known globals. Membership in this list means accessing the global is assumed to have no side effects. That means tree shaking is allowed to remove unused references to these globals. For example, since HTMLElement is now in the known globals list, the following class will now be removed when unused:
class MyElement extends HTMLElement {
}
In addition, membership in this list relaxes ordering constraints for the purposes of minification. It allows esbuild to reorder references to these globals past other expressions. For example, since console.log is now in the known globals list, the following simplification will now be performed during minification:
// Original
export default (a) => {
if (a) console.log(b); else console.log(c)
}
// Minified (previous release)
export default (a) => {
a ? console.log(b) : console.log(c);
};
// Minified (this release)
export default (a) => {
console.log(a ? b : c);
};
This transformation is not generally safe because the console.log property access might evaluate code which could potentially change the value of a. This is only considered safe in this instance because console.log is now in the known globals list.
Note that membership in this list does not say anything about whether the function has side effects when called. It only says that the identifier has no side effects when referenced. So console.log() is still considered to have side effects even though console.log is now considered to be free of side effects.
The following globals are not on the list and are considered to have side effects:
scrollXscrollYinnerWidthinnerHeightpageXOffsetpageYOffsetlocalStoragesessionStorageAccessing layout-related properties can trigger a layout and accessing storage-related properties can throw an exception if certain privacy settings are enabled. Both of these behaviors are considered side effects.
Fix a TypeScript parser regression (#846)
Restrictions on array and object destructuring patterns in the previous release introduced a regression where arrays or objects in TypeScript code could fail to parse if they were wrapped in a double layer of parentheses. This was due to the speculative parsing of arrow function arguments. The regression has been fixed.
Add the Go-specific cli.ParseServeOptions() API (#834)
This API is specifically for people trying to emulate esbuild's CLI in Go. It lets you share esbuild's logic of parsing the --serve= and --servedir= flags. Use it like this:
serveOptions, args, err := cli.ParseServeOptions([]string{
"--serve=8000",
})
buildOptions, err := cli.ParseBuildOptions(args)
result := api.Serve(serveOptions, buildOptions)
This release fixes the following edge cases:
Fix some parsing edge cases (#835)
This release fixes the following edge cases:
Code using in inside a template literal inside a for loop initializer such as for (let x = `${a in b ? '0' : '1'}`; false; ); is now allowed. Previously the in operator was incorrectly considered to be part of a for-in loop.
In TypeScript, it's not valid to have a newline in between the async and the < tokens inside the code async <T>() => {}. Previously this was incorrectly treated as an asynchronous arrow function expression.
Code of the form new async() must construct the function called async. Previously this was incorrectly treated as new (async())() instead due to the speculative parsing of asynchronous arrow functions.
Code of the form new async () => {} must not be allowed. Previously this was incorrectly allowed since the speculative parsing of asynchronous arrow functions did not check the precedence level.
It's not valid to start an initializer expression in a for-of loop with the token let such as for (let.foo of bar) {}. This is now forbidden. In addition, the code generator now respects this rule so for ((let.foo) of bar) {} is now printed as for ((let).foo of bar) {}.
Array and object binding patterns do not allow a comma after rest elements, so code such as [...a, b] = [c] is invalid. This case is correctly handled by esbuild. However, it's possible to have both an array or object binding pattern and an array or object literal on the left-hand side of a destructuring assignment such as [[...a, b].c] = [d]. In that case it should be allowed for a comma to come after the spread element in the array or object literal expression. Previously this was incorrectly treated as an error by esbuild.
It's technically allowed (although perhaps not ever actually useful) to call super() from within a default argument initializer like this:
class Derived extends Base {
constructor(arg = super()) {
}
}
Previously esbuild did not permit this, which is incorrect. Doing this is now permitted.
It is an error to use arguments in a class field initializer such as class { x = arguments[0] }, but it is not an error to use arguments in a computed class property name such as class { [arguments[0]] = x } or inside TypeScript decorators such as class { @decorator(arguments[0]) x() {} }. Previously all of these cases were an error in esbuild, which is incorrect. Using arguments inside computed class property names and TypeScript decorators is now allowed.
It is not permitted to use a function declaration inside an if statement such as if (0) function f() {} in strict mode. Previously this was allowed, but this is now forbidden.
It is not permitted to re-declare a generator and/or asynchronous function declaration inside a block scope:
// This is allowed
function *a() {}
function *a() {}
// This is allowed
function f() {
function *b() {}
function *b() {}
}
// This is not allowed
{
function *c() {}
function *c() {}
}
The parser now enforces this rule.
Legacy octal escape sequences are octal escape sequences other than \0 with a single zero. These are forbidden in untagged template literals and in all strings in strict mode code. Previously esbuild didn't enforce this rule, but it is now enforced.
Technically the directive prologue is allowed to contain multiple directives, so strict mode should still be applied even if a "use strict"; directive is preceded by another directive. For example, "use \000"; "use strict"; should be a syntax error because strict mode is active. This technicality has now been implemented.
It is supposed to be a syntax error if a use strict directive is inside a function with a non-simple parameter list, such as (x = 1) => { 'use strict' }. Previously esbuild allowed this code, but now this code is a syntax error.
It is forbidden for a template literal tag to be an optional chain such as a?.b`c`. This rule is now enforced by esbuild, so code like this is now a syntax error. In addition, the code generator now avoids generating this syntax by wrapping any optional chain template literal tags in parentheses.
According to the standard, all code inside a class statement or expression should be in strict mode. Previously esbuild treated code inside a class as the same strict mode status as the surrounding code, but now code in a class is always interpreted as strict mode code.
Duplicate bindings in the same parameter list are not allowed if the parameter list isn't simple, such as in the code function f(a, [a]) {}, or if the parameter list belongs to an arrow function or a method. This rule is now enforced by esbuild's parser, so doing this is now a syntax error.
Array and object destructuring patterns are only valid if they are not surrounded by parentheses. Previously esbuild incorrectly allowed code such as ([]) = [] and ({}) = {}. This invalid code is now a syntax error.
It is now an error to use the shorthand property syntax ({yield}) inside a generator and ({await}) inside an asynchronous function. Previously those cases were incorrectly allowed.
A newline in between async and a method name is no longer allowed. Instead, this is a syntax error inside an object literal and a class field inside a class body.
Remove the local web server feature from the WebAssembly package (#836)
This feature didn't work anyway (maybe sockets don't work with Go's WebAssembly target?) and including it added around 3mb of unnecessary extra code to the WebAssembly module file. Removing this brings the size of the WebAssembly module from around 11mb down to 8.3mb.
Release native binaries for the Apple M1 chip
Release native binaries for the Apple M1 chip (#550)
Previously installing esbuild on a M1 actually installed the x86-64 version, which required the Rosetta 2 translator. This was because Go hadn't yet released support for the M1. Now that Go 1.16.0 has been released, esbuild can support the M1 natively. It's supported by esbuild starting with this release. There are reports of the native version being 1.4x faster than the translated version. This change was contributed by @rtsao.
Omit warning about require.someProperty when targeting CommonJS (#812)
The require.cache property allows introspecting the state of the require cache, generally without affecting what is imported/bundled.
Since esbuild's static analyzer only detects direct calls to require, it currently warns about uses of require in any situation other than a direct call since that means the value is "escaping" the analyzer. This is meant to detect and warn about indirect calls such as ['fs', 'path'].map(require).
However, this warning is not relevant when accessing a property off of the require object such as require.cache because a property access does not result in capturing the value of require. Now a warning is no longer generated for require.someProperty when the output format is cjs. This allows for the use of features such as require.cache and require.extensions. This fix was contributed by @huonw.
Support ignored URL parameters at the end of import paths (#826)
If path resolution fails, ebuild will now try again with the URL query and/or fragment removed. This helps handle ancient CSS code like this that contains hacks for Internet Explorer:
@font-face {
src:
url("./themes/default/assets/fonts/icons.eot?#iefix") format('embedded-opentype'),
url("./themes/default/assets/fonts/icons.woff2") format('woff2'),
url("./themes/default/assets/fonts/icons.woff") format('woff'),
url("./themes/default/assets/fonts/icons.ttf") format('truetype'),
url("./themes/default/assets/fonts/icons.svg#icons") format('svg');
}
Previously path resolution would fail because these files do not end with the .eot?#iefix or .svg#icons extensions. Now path resolution should succeed. The URL query and fragment are not unconditionally stripped because there is apparently code in the wild that uses # as a directory name. So esbuild will still try to resolve the full import path first and only try to reinterpret the path as a URL if that fails.
Prevent paths starting with / from being used as relative paths on Windows (#822)
On Windows, absolute paths start with a drive letter such as C:\... instead of with a slash like /.... This means that paths starting with a / can actually be used as relative paths. For example, this means an import of /subfolder/image.png will match the file at the path ./subfolder/image.png. This is problematic for Windows users because they may accidentally make use of these paths and then try to run their code on a non-Windows platform only for it to fail to build.
Now paths starting with a / are always treated as an absolute path on all platforms. This means you can no longer import files at a relative path that starts with / on Windows. You should be using a ./ prefix instead.
Warn when importing a path with the wrong case
Importing a path with the wrong case (e.g. File.js instead of file.js) will work on Windows and sometimes on macOS because they have case-insensitive file systems, but it will never work on Linux because it has a case-sensitive file system. To help you make your code more portable and to avoid cross-platform build failures, esbuild now issues a warning when you do this.
If you write .0 instead of 0 in CSS and enabled --minify, esbuild would previously minify this token incorrectly (the token was deleted). This bug has
Fix minification of .0 in CSS (#804)
If you write .0 instead of 0 in CSS and enabled --minify, esbuild would previously minify this token incorrectly (the token was deleted). This bug has been fixed and esbuild should now minify this token to 0.
Support range requests in local HTTP server
The local HTTP server built in to esbuild now supports range requests, which are necessary for video playback in Safari. This means you can now use <video> tags in your HTML pages with esbuild's local HTTP server.
The --serve flag starts a local web server and serves the files that would normally be written to the output directory. So for example if you had an e
Add the --servedir= flag (#796)
The --serve flag starts a local web server and serves the files that would normally be written to the output directory. So for example if you had an entry point called src/app.ts and an output directory of --outdir=www/js, using esbuild with --serve would expose the generated output file via http://localhost:8000/app.js (but not write anything to www/js). This can then be used in combination with your normal development server (running concurrently on another port) by adding <script src="http://localhost:8000/app.js"></script> in your HTML file. So esbuild with the --serve flag is meant to augment your normal development server, not replace it.
This release introduces a new --servedir= flag which gives you the option of replacing your normal development server with esbuild. The directory you pass here will be "underlayed" below the output directory. Specifically when an incoming HTTP request comes in esbuild will first check if it matches one of the generated output files and if so, serve the output file directly from memory. Otherwise esbuild will fall back to serving content from the serve directory on the file system. In other words, server's URL structure behaves like a normal file server in a world where esbuild had written the generated output files to the file system (even though the output files actually only exist in memory).
So for example if you had an entry point called src/app.ts and an output directory of --outdir=www/js, using esbuild with --servedir=www would expose the entire contents of the www directory via http://localhost:8000/ except for the http://localhost:8000/js/app.js URL which would contain the compiled contents of src/app.ts. This lets you have a www/index.html file containing just <script src="/js/app.js"></script> and use one web server instead of two.
The benefit of doing things this way is that you can use the exact same HTML pages in development and production. In development you can run esbuild with --servedir= and esbuild will serve the generated output files directly. For production you can omit that flag and esbuild will write the generated files to the file system. In both cases you should be getting the exact same result in the browser with the exact same code in both development and production.
This will of course not support all workflows, but that's intentional. This is designed to be a quality-of-life improvement for the simple case of building a small static website with some HTML, JavaScript, and CSS. More advanced setups may prefer to avoid the --servedir= feature and e.g. configure a NGINX reverse proxy to esbuild's local server to integrate esbuild into a larger existing development setup.
One unintended consequence of this feature is that esbuild can now be used as a general local HTTP server via esbuild --servedir=.. Without any entry points, esbuild won't actually build anything and will just serve files like a normal web server. This isn't the intended use case but it could perhaps be a useful side effect of this feature.
Remove absolute paths for disabled packages from source maps (#786)
This change is similar to the one from the previous release for disabled files, but it applies to package paths instead of relative paths. It's relevant when using packages that override dependencies with alternative packages using the browser field in their package.json file. Using relative paths instead of absolute paths fixes a determinism issue where build output was different on different systems. This fix was contributed by @eelco.
Handle absolute paths in tsconfig.json (#792)
Some automatically-generated tsconfig.json paths can have absolute paths in them. This is allowed by the TypeScript compiler (specifically in the paths and extends fields). With this release, esbuild now supports absolute paths in paths and extends too.
Change the watch mode output format (#793)
Previously esbuild would print a "..." animation to the console while watch mode was scanning for changes. The intent of this was to a) not take up too much space in the terminal and b) show that esbuild's watch mode isn't frozen. Since the release I have gotten feedback that this isn't desirable. People want more feedback about what's happening and want to be able to run regexes over the stderr stream instead of using esbuild's actual API.
This release changes the output format for watch mode. Now esbuild will print [watch] build started when watch mode triggers a rebuild and [watch] build finished when the rebuild is complete. Any build errors will be printed in between those two log messages.
Note that this means esbuild's watch mode output is now more verbose, especially when there are frequent file changes. If you want to hide these new messages you can use --log-level= with a level other than info.
This release introduces a logo for esbuild:
Create a logo for esbuild (#61)
This release introduces a logo for esbuild:
<p> <img width="100" height="100" src="https://esbuild.github.io/favicon.svg"> </p>
Inspirations for the logo include:
The fast-forward symbol because esbuild is extremely fast and because one of esbuild's goals is to accelerate the evolution of the whole web tooling ecosystem.
The right-shift symbol because esbuild's production optimizations make your code smaller and because esbuild itself contains many low-level optimizations for speed.
Having a logo for esbuild should make it easier to include esbuild in lists of other tools since the other tools often all have logos.
Add support for node's --preserve-symlinks flag (#781)
This release adds the --preserve-symlinks flag which behaves like the corresponding flag in node. Without the flag, esbuild and node will use the real path (after resolving symlinks) as the identity of a file. This means that a given file can only be instantiated once. With the flag, esbuild and node will use the original path (without resolving symlinks) as the identity of a file. This means that a given file can be instantiated multiple times, once for every symlink pointing to it. Each copy will have its own identity so the resulting bundle may contain duplicate files. This option is useful if your code relies on this flag in node (or the resolve.symlinks setting in Webpack).
Ignore a leading byte order mark (BOM) in CSS files (#776)
Some text editors insert a U+FEFF code point at the start of text files. This is a zero-width non-breaking space character. Using one at the start of a file is a convention which is meant to indicate that the contents of the file are UTF-8 encoded. When this is done, the character is called a byte order mark.
Unlike JavaScript, CSS does not treat U+FEFF as whitespace. It is treated as an identifier instead. This was causing esbuild to misinterpret files starting with a BOM as starting with an extra identifier, which could then cause the initial CSS rule in the file to be parsed incorrectly.
Now esbuild will skip over a BOM if it's present before beginning to parse CSS. This should prevent issues when working with these files.
Add message notes to the API
The internal logging system has the ability to attach additional notes to messages to provide more information. These show up as additional log messages in the terminal when using the command-line interface. Here is an example of a note:
> src/structs/RTree.js: warning: Duplicate key "compareMinX" in object literal
469 │ compareMinX: function (a, b)
╵ ~~~~~~~~~~~
src/structs/RTree.js: note: The original "compareMinX" is here
206 │ compareMinX: compareNodeMinX,
╵ ~~~~~~~~~~~
With this release, notes are also supported in the JS and Go APIs. This means you can now generate your own notes using plugins as well as inspect the notes generated by esbuild.
Add origin information to errors from plugins (#780)
Errors thrown during JavaScript plugin callback evaluation will now be annoated to show where that plugin callback was registered. That looks like this:
> example-plugin.js: error: [example-plugin] foo.bar is not a function
15 │ foo.bar();
╵ ^
at ./example-plugin.js:15:13
at ./node_modules/esbuild/lib/main.js:750:34
example-plugin.js: note: This error came from the "onLoad" callback registered here
13 │ build.onLoad({ filter: /.*/ }, args => {
╵ ~~~~~~
at setup (./example-plugin.js:13:13)
at handlePlugins (./node_modules/esbuild/lib/main.js:668:7)
This should make it easier to debug crashes in plugin code.
Fix a regression with the synchronous JavaScript API (#784)
In version 0.8.39, a change was made to avoid dangling esbuild processes when node exits abnormally. The change introduced a periodic ping between the child esbuild process and its host process. If the ping doesn't go through, the child process is able to detect that the host process is no longer there. Then it knows to exit since it's no longer being used.
This caused a problem with the synchronous JavaScript API calls which run the esbuild child process in a single-response mode. The ping message was interpreted as a second response and tripped up the message protocol. Pings are only useful for the asynchronous API calls. Running the pings during synchronous API calls was unintentional. With this release pings are no longer run for synchronous API calls so this regression should be fixed.
Remove absolute paths for disabled files from source maps (#785)
Files can be ignored (i.e. set to empty) using the browser field in package.json. Specifically, you can set the browser field to a map where the key is the module name and the value is false. This is a convention followed by several bundlers including esbuild.
Previously ignoring a file caused that file's path to appear as an absolute path in any generated source map. This is problematic because it means different source maps will be generated on different systems, since the absolute path contains system-specific directory information. Now esbuild will treat these paths the same way it treats other paths and will put a relative path in the source map.
Support the XDG_CACHE_HOME environment variable
Support the XDG_CACHE_HOME environment variable (#757)
On Linux, the install script for esbuild currently caches downloaded binary executables in ~/.cache/esbuild/bin. This change means esbuild will now try installing to $XDG_CACHE_HOME/esbuild/bin instead of the XDG_CACHE_HOME environment variable exists. This allows you to customize the cache directory on Linux. The specification that defines XDG_CACHE_HOME is here.
Further improve constant folding of branches (#765)
At a high level, this release adds the following substitutions to improve constant folding and dead code elimination:
if (anything && falsyWithSideEffects) → if (anything, falsyWithSideEffects)if (anything || truthyWithSideEffects) → if (anything, truthyWithSideEffects)if (anything && truthyNoSideEffects) → if (anything)if (anything || falsyNoSideEffects) → if (anything)if (anything, truthyOrFalsy) → anything; if (truthyOrFalsy)And also these substitutions for unused expressions:
primitive == primitive → primitive, primitivetypeof identifier → (remove entirely)The actual substitutions are more complex since they are more comprehensive but they essentially result in this high-level behavior. Note that these substitutions are only done when minification is enabled.
Fix an edge case with CSS variable syntax (#760)
CSS variables are whitespace-sensitive even though other CSS syntax is mostly not whitespace sensitive. It is apparently common for this to cause problems with CSS tooling that pretty-prints and minifies CSS, including esbuild before this release. Some examples of issues with other tools include postcss/postcss#1404 and tailwindlabs/tailwindcss#2889. The issue affects code like this:
div {
--some-var: ;
some-decl: var(--some-var, );
}
It would be a change in semantics to minify this code to either --some-var:; or var(--some-var,) due to the whitespace significance of CSS variables, so such transformations are invalid. With this release, esbuild should now preserve whitespace in these two situations (CSS variable declarations and CSS variable references).
Add support for recursive symlinks during path resolution (#766)
Previously recursive symlinks (a symlink that points to another symlink) were an unhandled case in the path resolution algorithm. Now these cases should be supported up to a depth of 256 symlinks. This means esbuild's path resolution should now work with multi-level yarn link scenarios.
Fix subtle circular dependency issue (#758)
If esbuild is used to transform TypeScript to JavaScript without bundling (i.e. each file is transformed individually), the output format is CommonJS, and the original TypeScript code contains an import cycle where at least one of the links in the cycle is an export * as re-export statement, there could be certain situations where evaluating the transformed code results in an import being undefined. This is caused by the __esModule marker being added after the call to require() for the first transformed re-export statement. The fix was to move the marker to before the first call to require(). The __esModule marker is a convention from Babel that esbuild reuses which marks a module as being originally in the ECMAScript module format instead of the CommonJS module format.
Add support for the NODE_PATH environment variable
This is a rarely-used feature of Node's module resolution algorithm. From the documentation:
If the
NODE_PATHenvironment variable is set to a colon-delimited list of absolute paths, then Node.js will search those paths for modules if they are not found elsewhere.On Windows,
NODE_PATHis delimited by semicolons (;) instead of colons.
The CLI takes the list of node paths from the value of the NODE_PATH environment variable, but the JS and Go APIs take the list as an array of strings instead (called nodePaths in JS and NodePaths in Go).
Fix crash with block-level function declaration and --keep-names
Fix crash with block-level function declaration and --keep-names (#755)
This release fixes a crash with block-level function declarations and the --keep-names option. The crash affected code that looks like this:
if (true) function f() {}
assert.strictEqual(f.name, 'f')
Disallow additional features in strict mode
This change improves esbuild's compliance with the JavaScript specification. It is now an error to use legacy octal numeric literals and the identifiers implements, interface, let, package, private, protected, public, static, and yield in strict mode code.
Basic support for watch mode with plugins (#752)
With this release, watch mode should now work with simple on-load plugins. Watch mode is implemented by tracking all file system accesses made by esbuild as it does a build. However, this doesn't catch external file system accesses such as those made by plugins. Now if an on-load plugin is used on a path in the file namespace, esbuild will also read the file during watch mode so that watch mode is aware of the file system access. Note that there is not yet API support for a plugin to return additional paths for watch mode to monitor.
Make JavaScript API error format more consistent (#745)
If a JavaScript error is thrown while validating the build options, the thrown error should now have errors and warnings properties just like normal build errors. Previously these properties were only present if the build itself failed but not if build options were invalid. This consistency should make it easier to process errors from the build API call.
Fix memory leak with watch mode when using the CLI
Fix memory leak with watch mode when using the CLI (#750)
This release fixes a memory leak when using --watch from the CLI (command-line interface). When esbuild was in this state, every incremental build resulted in more memory being consumed. This problem did not affect users of the JS API or Go API, only users of the CLI API.
The problem was that the GC (garbage collector) was disabled. Oops. This is done by default for speed when you use esbuild via the CLI, which makes sense for most CLI use cases because the process is usually short-lived and doesn't need to waste time cleaning up memory. But it does not make sense for flags that cause esbuild to be a long-running process.
Previously the only exception to this rule was the --serve flag. When I added watch mode, I forgot to enable GC for the --watch flag too. With this release, the GC is enabled for both the --serve and the --watch flags so esbuild should no longer leak memory in watch mode.
Special-case certain syntax with --format=esm (#749)
You can now no longer use the following syntax features with the esm output format:
with statement: with (x) {}delete xIn addition, the following syntax feature is transformed when using the esm output format:
for (var x = y in {}) {} → x = y; for (var x in {}) {}The reason is because all JavaScript engines interpret code in the esm output format as strict mode and these syntax features are disallowed in strict mode. Note that this new strict mode handling behavior in esbuild is only dependent on the output format. It does not depend on the presence or absence of "use strict" directives.
Basic "use strict" tracking
The JavaScript parser now tracks "use strict" directives and propagates strict mode status through the code. In addition, files containing the import and/or export keywords are also considered to be in strict mode. Strict mode handling is complex and esbuild currently doesn't implement all strict mode checks. But the changes in this release are a starting point. It is now an error to use certain syntax features such as a with statement within a strict mode scope.
Fix a minifier bug with with statements
The minifier removes references to local variables if they are unused. However, that's not correct to do inside a with statement scope because what appears to be an identifier may actually be a property access, and property accesses could have arbitrary side effects if they resolve to a getter or setter method. Now all identifier expressions inside with statements are preserved when minifying.
Transform block-level function declarations
Block-level function declarations are now transformed into equivalent syntax that avoids block-level declarations. Strict mode and non-strict mode have subtly incompatible behavior for how block-level function declarations are interpreted. Doing this transformation prevents problems with code that was originally strict mode that is run as non-strict mode and vice versa.
Now esbuild uses the presence or absence of a strict mode scope to determine how to interpret the block-level function declaration and then converts it to the equivalent unambiguous syntax such that it works the same regardless of whether or not the current scope is in strict mode:
// This original code:
while (!y) {
function y() {}
}
// is transformed into this code in strict mode:
while (!y) {
let y2 = function() {};
}
// and into this code when not in strict mode:
while (!y) {
let y2 = function() {};
var y = y2;
}
Fix TypeScript parameter decorators on class constructors
Fix TypeScript parameter decorators on class constructors (#734)
This release fixes a TypeScript translation bug where parameter decorators on class constructors were translated incorrectly. Affected code looks like this:
class Example {
constructor(@decorator param: any) {}
}
This bug has been fixed. In addition, decorators are no longer allowed on class constructors themselves because they are not allowed in TypeScript.
Resolve browser entries in package.json with no file extension (#740)
This fix changes how esbuild interprets the browser field in package.json. It will now remap imports without a file extension to browser map entries without a file extension, which improves compatibility with Webpack. Specifically, a package.json file with "browser": {"./file": "./something.js"} will now match an import of ./file. Previously the package.json file had to contain something like "browser": {"./file.js": "./something.js"} instead. Note that for compatibility with the rest of the ecosystem, a remapping of ./file will counter-intuitively not match an import of ./file.js even though it works fine in the other direction.
Warning: npm v7 bug may prevent esbuild installation
This is a warning for people reading these release notes, not a code change. I have discovered a bug in npm v7 where your package-lock.json file can become corrupted such that no postinstall scripts are run. This bug affects all packages with postinstall scripts, not just esbuild, and happens when running npm v7 on a package-lock.json file from npm v6 or earlier. It seems like deleting and regenerating your package-lock.json file is a valid workaround that should get esbuild working again.
Fix the JavaScript watch mode API exiting early
Fix the JavaScript watch mode API exiting early (#730)
The previous release contained a bug that caused the JavaScript watch mode API to exit early in some cases. This bug should now be fixed. The problem was caused by some code that shouldn't even need to exist now that you are no longer required to call stop() on an esbuild service created by startService() (it was made optional in version 0.8.32). I took the opportunity to clean up the internals of esbuild's JavaScript API implementation which ended up removing the entire section of code that contained this bug.
Add an API option for a per-build working directory (#689)
You can now use the absWorkingDir API option to customize the current working directory. It will default to the value of process.cwd() at the time of the call to startService() when not specified, which matches the existing behavior. The working directory is used for a few different things including resolving relative paths given as API options to absolute paths and pretty-printing absolute paths as relative paths in log messages.
In addition to being a useful feature, this change also simplifies esbuild's internals. Previously esbuild had to maintain separate child processes if the current working directory was changed in between build API calls. Now esbuild will always reuse the same child process across all build API calls. The stop() call on the startService() API is also now a no-op (it doesn't do anything anymore) and the startService() API may be removed in future releases.
Fix stray esbuild process after node exits (#643)
I discovered that using esbuild's JavaScript incremental build API could result in the child esbuild process not exiting when the parent node process exits. This was due to a reference counting issue. The bug has been fixed so this shouldn't happen anymore.
Implement a simple cross-platform watch mode
Implement a simple cross-platform watch mode (#21)
With this release, you can use the --watch flag to run esbuild in watch mode which watches the file system for changes and does an incremental build when something has changed. The watch mode implementation uses polling instead of OS-specific file system events for portability.
Note that it is still possible to implement watch mode yourself using esbuild's incremental build API and a file watcher library of your choice if you don't want to use a polling-based approach. Also note that this watch mode feature is about improving developer convenience and does not have any effect on incremental build time (i.e. watch mode is not faster than other forms of incremental builds).
The new polling system is intended to use relatively little CPU vs. a traditional polling system that scans the whole directory tree at once. The file system is still scanned regularly but each scan only checks a random subset of your files to reduce CPU usage. This means a change to a file will be picked up soon after the change is made but not necessarily instantly. With the current heuristics, large projects should be completely scanned around every 2 seconds so in the worst case it could take up to 2 seconds for a change to be noticed. However, after a change has been noticed the change's path goes on a short list of recently changed paths which are checked on every scan, so further changes to recently changed files should be noticed almost instantly.
Add pluginData to pass data between plugins (#696)
You can now return additional data from a plugin in the optional pluginData field and it will be passed to the next plugin that runs in the plugin chain. So if you return it from an onLoad plugin, it will be passed to the onResolve plugins for any imports in that file, and if you return it from an onResolve plugin, an arbitrary one will be passed to the onLoad plugin when it loads the file (it's arbitrary since the relationship is many-to-one). This is useful to pass data between different plugins without them having to coordinate directly.
Improve ambiguous import handling
Improve ambiguous import handling (#723)
It is an error to try to import a name from a file where there are multiple matching exports due to multiple export * from statements from files which export that name. This release contains a few improvements to ambiguous import handling:
This release fixes a bug where named export shadowing didn't work correctly with multiple levels of re-exports. A named export closer in the re-export chain is supposed to hide a named export deeper in the re-export chain without causing an ambiguous import. The bug caused this case to be incorrectly flagged as an error even though it should have been allowed. This case is now allowed without an error.
Previously the error message just said that there was an ambiguous import but didn't have any additional information. With this release, the error message also points out where the two different exports that have collided are in their original source files. Hopefully this should make it quicker to diagnose these types of issues.
Real JavaScript environments only treat ambiguous imports as an error if they are explicitly a named import. Using the import * as syntax and then accessing the ambiguous import with a property access results in undefined instead of an error. Previously esbuild also treated this case as an error because it automatically rewrites star-import syntax to named-import syntax to improve tree shaking. With this release, this case is now treated as a warning instead of an error and the import will be automatically replaced with an undefined literal in the bundled code.
Reuse automatically-generated temporary *.node files (#719)
The previous change to hide the automatically-generated N-API native node extensions from Yarn 2 writes these *.node files to the system's temporary directory. A new one was being created on each run which is wasteful even though they are only a few kilobytes in size. With this release *.node files will now be reused if they are already present in the system's temporary directory, so a new one is no longer created on each run. This fix was contributed by @kzc.
Fix the serve API with outfile (#707)
This release fixes a bug where the serve API did not work with the outfile setting. Using this setting with the serve API should now work fine.
Warn about duplicate keys in object literals
Using a duplicate key in an object literal such as {x: 1, x: 2} is now a warning. This is allowed in JavaScript but results in subsequent keys overwriting the previous key. It's usually a copy/paste error and isn't ever useful so it's worth warning about.
Avoid generating duplicate keys in JSON metadata
The output map that is generated when the metafile feature is active could potentially have duplicate keys if the file loader is used, there are multiple entry points, and two or more entry points reference the same file. This is harmless because both keys mapped to the same value, but it's confusing and unnecessary. Duplicate keys are no longer present in the output map in this latest release.
Make the JSON metafile structure match the type definitions (#726)
Previously imports and/or exports could be missing from entries in the output map in certain cases (specifically for source maps and files loaded with the file loader). This was problematic because the TypeScript type definitions for the metafile say that the imports and exports properties are non-optional. With this release, the imports and exports properties are now always present so the existing TypeScript type definitions are now accurate.
Update from Go 1.15.5 to Go 1.15.7
The version of Go used to build the released binary executables on npm is now Go 1.15.7. This change shouldn't result in any visible changes to esbuild. It was only upgraded because the Go extension for the VSCode IDE now uses the official gopls Go language service and this extension wanted the latest version of Go.
Fix an issue with writing large files to stdout using the WebAssembly executable
Fix an issue with writing large files to stdout using the WebAssembly executable
The previous release introduced a regression where large output files written to stdout were incorrectly truncated when using the WebAssembly esbuild command. This regression was due to a missing callback to the JavaScript write() function when called on the stdout stream. The regression has been fixed.
Hide the N-API native node extensions from Yarn 2
The previous release introduced some very small (1-2kb) *.node native extensions to fix a bug with node failing to exit properly. However, this causes Yarn 2 to unzip the esbuild package, which is undesirable. This release puts these native node extensions inside JavaScript code instead to hide them from Yarn 2. The native extensions are written to a temporary file at run-time if necessary.
Fix a commonly-missed corner case with await inside
Fix a commonly-missed corner case with await inside **
I recently discovered an interesting discussion about JavaScript syntax entitled "Most implementations seem to have missed that await x ** 2 is not legal". Indeed esbuild has missed this, but this is not surprising because V8 has missed this as well and I usually test esbuild against V8 to test if esbuild is conformant with the JavaScript standard. Regardless, it sounds like the result of the discussion is that the specification should stay the same and implementations should be fixed. This release fixes this bug in esbuild's parser. The syntax await x ** 2 is no longer allowed and parentheses are now preserved for the syntax (await x) ** 2.
Allow namespaced names in JSX syntax (#702)
XML-style namespaced names with a : in the middle are a part of the JSX specification but they are explicitly unimplemented by React and TypeScript so esbuild doesn't currently support them. However, there was a user request to support this feature since it's part of the JSX specification and esbuild's JSX support can be used for non-React purposes. So this release now supports namespaced names in JSX expressions:
let xml =
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<rdf:Description rdf:ID="local-record">
<dc:title>Local Record</dc:title>
</rdf:Description>
</rdf:RDF>
This JSX expression is now transformed by esbuild to the following JavaScript:
let xml = React.createElement("rdf:RDF", {
"xmlns:rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"xmlns:dc": "http://purl.org/dc/elements/1.1/"
}, React.createElement("rdf:Description", {
"rdf:ID": "local-record"
}, React.createElement("dc:title", null, "Local Record")));
Note that if you are trying to namespace your React components, this is not the feature to use. You should be using a . instead of a : for namespacing your React components since . resolves to a JavaScript property access.
Fix worker: false in esbuild's browser-based JavaScript API
The browser-based JavaScript API creates a web worker by default but this can be disabled by passing worker: false. When you do this the WebAssembly code is run in the current thread which will lock up the thread. This is mainly useful if you're calling the JavaScript API from within a web worker and you want to avoid creating another nested web worker.
This option was unintentionally broken when the internal JavaScript web worker source code was moved from an inline function to a string in version 0.5.20. The regression has been fixed and the worker: false scenario now has test coverage.
Fix absolute paths with the esbuild-wasm package on Windows (#687)
The package esbuild-wasm has an esbuild command implemented using WebAssembly instead of using native code. It uses node's WebAssembly implementation and calls methods on node's fs module to access the file system.
Go's path/filepath module has a bug where Windows paths are interpreted as Unix paths when targeting WebAssembly: golang/go#43768. This causes multiple issues including absolute paths such as C:\path\to\file.js being interpreted as relative paths (since they don't start with a /) and being joined onto the end of other paths.
To fix this, esbuild now does all of its own path handling instead of using Go's path handling code. The esbuild code base now contains a forked copy of path/filepath that can handle both Windows and Unix paths. The decision about which one to use is made at run-time. When targeting WebAssembly, the presence of the C:\ directory is used to determine if Windows-style paths should be used.
With this release, it should now be possible to use Windows-style paths with esbuild's WebAssembly implementation on Windows.
Fix using stdin with the esbuild-wasm package on Windows (#687)
Node has an old bug (nodejs/node#19831, nodejs/node#35997) where fs.read returns an EOF error at the end of stdin on Windows. This causes Go's WebAssembly implementation to panic when esbuild tries to read from stdin.
The workaround was to manually check for this case and then ignore the error in this specific case. With this release, it should now be possible to pipe something to the esbuild command on Windows.
Fix stdout and stderr not supporting Unicode in the esbuild-wasm package on Windows (#687)
Node's fs.write API is broken when writing Unicode to stdout and stderr on Windows, and this will never be fixed: nodejs/node#24550. This is problematic for Go's WebAssembly implementation because it uses this API for writing to all file descriptors.
The workaround is to manually intercept the file descriptors for stdout and stderr and redirect them to process.stdout and process.stderr respectively. Passing Unicode text to write() on these objects instead of on the fs API strangely works fine. So with this release, Unicode text should now display correctly when using esbuild's WebAssembly implementation on Windows (or at least, as correctly as the poor Unicode support in Windows Command Prompt allows).
Add a hack for faster command-line execution for the WebAssembly module in certain cases
Node has an unfortunate bug where the node process is unnecessarily kept open while a WebAssembly module is being optimized: https://github.com/nodejs/node/issues/36616. This means cases where running esbuild should take a few milliseconds can end up taking many seconds instead.
The workaround is to force node to exit by ending the process early. This is done in one of two ways depending on the exit code. For non-zero exit codes (i.e. when there is a build error), the esbuild command now calls process.kill(process.pid) to avoid the hang.
For zero exit codes, the esbuild command now loads a N-API native node extension that calls the operating system's exit(0) function. This is done without requiring node-gyp by precompiling each supported platform and just including all of them in the esbuild-wasm package since they are so small. If this hack doesn't work in certain cases, the process should exit anyway just potentially many seconds later. Currently the only supported platforms for this hack are 64-bit macOS, Windows, and Linux.
Fix non-absolute paths with the esbuild-wasm package in the browser (#693)
When using esbuild in the browser via WebAssembly, it was not possible to specify an non-absolute output path. Normally you can do this and esbuild will just convert it to an absolute path by resolving it as a relative path from the current working directory. However, Go's WebAssembly implementation has no current working directory so the conversion operation to an absolute path failed, causing esbuild's API to fail.
With this release, esbuild should now behave as if the current working directory is / in the browser. For example, this means calling the build() API with outfile: 'file.js' should now generate an output file called /file.js instead of causing an error.
Fix a parser bug about suffix expressions after an arrow function body
Fix a parser bug about suffix expressions after an arrow function body (#701)
The JavaScript parser incorrectly handled suffix expressions after a non-expression arrow function body. In practice, this came up when a semicolon was omitted from the end of an expression statement and the following expression could be considered a suffix expression:
x = () => {}
(y)
This was incorrectly parsed as (x = () => {})(y); instead of x = () => {}; y;. With this release, this edge case should now be parsed correctly.
Add new neutral platform to help text (#695)
The new --platform=neutral API option that was added in the previous release was incorrectly not listed in the CLI help text for the platform feature. This omission has been fixed. The fix was contributed by @hardfist.
Fix esbuild potentially exiting early during incremental rebuilds
Fix esbuild potentially exiting early during incremental rebuilds
The change in the previous release to make calling stop() optional caused a regression for incremental rebuilds where calling rebuild() could potentially cause the process to exit early before the incremental rebuild is completed. This is because the implementation of rebuild() was missing a reference count to track that the service is now temporarily needed again. This omission was an oversight, and has now been fixed.
Fix using the new sourcesContent option with the transform API (#682)
Due to an oversight, the sourcesContent: false option that was added in version 0.8.27 didn't work with the JavaScript transform API. This was unintentional and has been fixed. This fix was contributed by @jschaf.
Insert the object spread shim in constructor methods after the super() call (#678)
This fixes an issue with the transform for object spread to older compile targets. Previously the following code would be transformed to code that crashes when run if the compile target is es2017 or lower:
class Derived extends Base {
prop = null;
constructor({ ...args }) {
super(args);
}
}
This code was incorrectly compiled to something like this, which will throw ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor:
class Derived extends Base {
constructor(_a) {
__publicField(this, "prop", null);
var args = __rest(_a, []);
super(args);
}
}
With this release, it will now be compiled to something like this instead:
class Derived extends Base {
constructor(_a) {
var args = __rest(_a, []);
super(args);
__publicField(this, "prop", null);
}
}
Add the --platform=neutral API option (#674)
There are currently two platform values: browser (the default) and node. These settings are a convenient way to configure multiple defaults for other API options for maximum compatibility. However, some users want to configure everything themselves so esbuild does not assume any platform-specific behavior. In this case you can now use --platform=neutral to disable platform-specific default values. Note that this means if you want to use npm-style packages you will have to configure a main field yourself with something like --main-fields=main.
Provide minified and non-minified versions of in-browser API library (#616)
The in-browser JavaScript API libraries for esbuild are in the esbuild-wasm package. There are two: esbuild-wasm/lib/browser.js in UMD format and esbuild-wasm/esm/browser.js in ESM format. Previously these were minified since they contain a large string of JavaScript that cannot be minified by other tools. Now they are no longer minified, and there are new minified versions available at esbuild-wasm/lib/browser.min.js and esbuild-wasm/esm/browser.min.js.
Calling stop() on the JavaScript API is now optional
Calling stop() on the JavaScript API is now optional (#656)
The JavaScript implementation of esbuild's API now calls unref() internally so node will now exit even if the internal long-lived esbuild process is still running. You should no longer need to explicitly call stop() on the service returned by startService(), which simplifies service lifetime management. This feature was contributed by @SalvatorePreviti.
Fix bug in metafile path generation (#662)
Certain import path metadata in the JSON file generated by the --metafile setting could be incorrect in scenarios with code splitting active and multiple entry points in different subdirectories. The incorrect paths referred to cross-chunk imports of other generated code splitting chunks and were incorrectly relative to the subdirectory inside the output directory instead of relative to the output directory itself. This issue has been fixed.
Add kind to import paths in metafile JSON (#655)
The --metafile flag generates build metadata in JSON format describing the input and output files in the build. Previously import path objects only had a path property. With this release, they now also have a kind property that describes the way the file was imported. The value is a string that is equal to one of the following values:
For JavaScript files:
import-statementrequire-calldynamic-importrequire-resolveFor CSS files:
import-ruleurl-tokenAdd support for TypeScript 4.2 syntax
Most of the new features included in the TypeScript 4.2 beta announcement are type system features that don't apply to esbuild. But there's one upcoming feature that adds new syntax: abstract construct signatures. They look like this:
let Ctor: abstract new () => HasArea = Shape;
This new syntax can now be parsed by esbuild.
Add detail to errors and warnings (#654)
Errors and warnings returned by the JavaScript and Go APIs now have a detail property which contains the original error. This is relevant if a custom JavaScript exception is thrown or a custom Go error is returned from inside a plugin callback.
Disable code warnings inside node_modules directories even with plugins (#666)
Some of the warnings that esbuild generates exist to point out suspicious looking code that is likely a bug. An example is typeof x == 'null' since the typeof operator never generates the string null. Arguably these warnings belong in a linter instead of in esbuild since esbuild is a bundler, but I figured that some warnings about obviously broken code would still be helpful because many people don't run linters. It's part of my quest to improve software quality. And these warnings have caught real bugs in published code so they aren't meaningless. The warning must be considered very unlikely to be a false positive to be included.
A change was added in version 0.7.4 to exclude files inside node_modules directories from these warnings. Even if the warnings flag a real bug, the warning is frustrating as a user because it's mostly non-actionable. The only resolution other than turning off warnings is to file an issue with the package, since code in published packages is immutable.
However, since then the plugin API has been released and this behavior didn't apply if the import path was resolved by a plugin. It only applied if the import path was resolved by esbuild itself. That problem is fixed in this release. Now these warnings will be omitted from any file with node_modules in its path, even if the path originated from a plugin.
Remove the warning about self-assignment (#666)
This warning was added in version 0.8.11 and warns about self-assignment such as x = x. The rationale is that this is likely a copy/paste error. However, it triggers too often for cross-compiled TypeScript code so the false positive rate is too high. The warning has now been removed.
Disable constant folding for the ?: operator when not minifying (#657)
When minification is not enabled, the ?: operator will now no longer be simplified if the condition evaluates to true or false. This could result in slower builds in certain cases because esbuild may now scan more files unnecessarily during bundling. This change was made because of a user request.
Fix minification issue from previous release
Fix minification issue from previous release (#648)
The minification optimization to omit certain continue and return statements when it's implied by control flow in version 0.8.29 caused a regression when the branch condition uses a hoisted function:
if (fn()) return;
...
function fn() {}
In that case, transforming the code by inverting the condition and moving the following statements inside the branch is not valid because the function is no longer hoisted to above the branch condition. This release fixes the regression by avoiding this optimization in cases like this.
Add the option --sourcemap=both (#650)
This new option puts the generated source map both an inline //# sourceMappingURL= data URL comment inside the output file and in an external file next to the output file. Using it is also possible with the transform API, which will cause it to return both an inline data URL comment in the code value and the source map JSON in the map value.
Tree-shake unused code with --format=iife (#639)
When the output format is IIFE (which wraps the code in an immediately-invoked function expression), esbuild now assumes that it's safe to remove unused code. This is an assumption that esbuild always makes when bundling but that esbuild previously didn't make when not bundling. Now esbuild will remove code even when not bundling as long as the output format is IIFE.
This is only done for the IIFE output format because people are currently using the other formats to compile "partial modules", meaning they expect to be able to append code to esbuild's output and have that appended code be able to reference unused code inside esbuild's output. So it's not safe for esbuild to remove unused code in those cases. The IIFE output format wraps everything in a closure so unused code is not exposed to the module-level scope. Appended code will not be able to access unused code inside the closure so that means it's safe to remove.
Fix @jsx and @jsxFrag comments without trailing spaces
Fix @jsx and @jsxFrag comments without trailing spaces
The --jsx-factory and --jsx-fragment settings can be set on a per-file basis using // @jsx name or // @jsxFrag name comments. Comments of the form /* @jsx name */ or /* @jsxFrag name */ will also work. However, there was a bug where comments of the form /* @jsx name*/ or /* @jsxFrag name*/ (a multi-line comment without a trailing space at the end) did not work. This bug has been fixed, and you now no longer need a trailing space for multi-line comments.
Minification improvements
The expression before a switch statement is now folded into the value. This means fn(); switch (x) { ... } turns into switch (fn(), x) { ... }.
Uses of === and !== are converted to == or != if the types of both sides can easily be statically determined. This means (x & 1) === 0 turns into (x & 1) == 0.
Equality comparisons are removed if both sides are boolean and one side is a constant. This means !x === true turns into !x.
Certain unary and binary operators are now removed if unused. This means if (a() === b()) {} turns into a(), b();.
The comma operator is now extracted from certain expressions. This means (a, b) + c turns into a, b + c.
Minification now takes advantage of the left-associativity of certain operators. This means a && (b && c) turns into a && b && c.
Computed properties that are strings now become no longer computed. This means {['a']: b} turns into {a: b} and class { ['a'] = b } turns into class { a = b }.
Repeated if-jump statements are now merged. This means if (a) break; if (b) break; turns into if (a || b) break;.
Fix issues with nested source maps (#638)
A nested source map happens when an input file has a valid //# sourceMappingURL= comment that points to a valid source map file. In that case, esbuild will read that source map and use it to map back to the original source code from the generated file. This only happens if you enable source map generation in esbuild via --sourcemap. This release fixes the following issues:
Generated source maps were incorrect when an input file had a nested source map and the input source map had more than one source file. This regression was introduced by an optimization in version 0.8.25 that parallelizes the generation of certain internal source map data structures. The index into the generated sources array was incorrectly incremented by 1 for every input file instead of by the number of sources in the input source map. This issue has been fixed and now has test coverage.
Generated source maps were incorrect when an input file had a nested source map, the file starts with a local variable, the previous file ends with a local variable of that same type, and the input source map is missing a mapping at the start of the file. An optimization was added in version 0.7.18 that splices together local variable declarations from separate files when they end up adjacent to each other in the generated output file (i.e. var a=0;var b=2; becomes var a=0,b=2; when a and b are in separate files). The source map splicing was expecting a mapping at the start of the file and that isn't necessarily the case when using nested source maps. The optimization has been disabled for now to fix source map generation, and this specific case has test coverage.
Allow entry points outside of the outbase directory
Allow entry points outside of the outbase directory (#634)
When esbuild generates the output path for a bundled entry point, it computes the relative path from the outbase directory to the input entry point file and then joins that relative path to the output directory. For example, if there are two entry points src/pages/home/index.ts and src/pages/about/index.ts, the outbase directory is src, and the output directory is out, the output directory will contain out/pages/home/index.js and out/pages/about/index.js.
However, this means that the outbase directory is expected to contain all entry point files (even implicit entry point files from import() expressions). If an entry point isn't under the outbase directory then esbuild will to try to write the output file outside of the output directory, since the path of the entry point relative to outbase will start with ../ which is then joined to the output directory. This is unintentional. All output files are supposed to be written inside of the output directory.
This release fixes the problem by creating a directory with the name _.._ in the output directory for output file paths of entry points that are not inside the outbase directory. So if the previous example was bundled with an outbase directory of temp, the output directory will contain out/_.._/pages/home/index.js and out/_.._/pages/about/index.js. Doing this instead of stripping the leading ../ off the relative path is necessary to avoid collisions between different entry points with the same path suffix.
Minification improvements
This release contains the following minification improvements:
Expressions of the form !(a == b) are now converted to a != b. This also applies similarly for the other three equality operators.
A trailing continue; statement inside the body of a loop is now removed.
Minification can now omit certain continue and return statements when it's implied by control flow:
// Before minification
function fn() {
if (a) return;
while (b) {
if (c) continue;
d();
}
}
// After minification
function fn() {
if (!a)
for (; b; )
c || d();
}
Certain single-use variables are now inlined if the use directly follows the variable:
// Before minification
let result = fn();
let callback = result.callback;
return callback.call(this);
// After minification
return fn().callback.call(this);
This transformation is only done when it's safe to do so. The safety conditions are complex but at a high level, an expression cannot be reordered past another expression if either of them could possibly have side effects.
Add a --summary flag that prints helpful information after a build
Add a --summary flag that prints helpful information after a build (#631)
Normally esbuild's CLI doesn't print anything after doing a build if nothing went wrong. This allows esbuild to be used as part of a more complex chain of tools without the output cluttering the terminal. However, sometimes it is nice to have a quick overview in your terminal of what the build just did. You can now add the --summary flag when using the CLI and esbuild will print a summary of what the build generated. It looks something like this:
$ ./esbuild --summary --bundle src/Three.js --outfile=build/three.js --sourcemap
build/three.js 1.0mb ⚠️
build/three.js.map 1.8mb
⚡ Done in 43ms
Keep unused imports in TypeScript code in one specific case (#604)
The official TypeScript compiler always removes imported symbols that aren't used as values when converting TypeScript to JavaScript. This is because these symbols could be types and not removing them could result in a run-time module instantiation failure because of missing exports. This even happens when the tsconfig.json setting "importsNotUsedAsValues" is set to "preserve". Doing this just keeps the import statement itself but confusingly still removes the imports that aren't used as values.
Previously esbuild always exactly matched the behavior of the official TypeScript compiler regarding import removal. However, that is problematic when trying to use esbuild to compile a partial module such as when converting TypeScript to JavaScript inside a file written in the Svelte programming language. Here is an example:
<script lang="ts">
import Counter from './Counter.svelte';
export let name: string = 'world';
</script>
<main>
<h1>Hello {name}!</h1>
<Counter />
</main>
The current Svelte compiler plugin for TypeScript only provides esbuild with the contents of the <script> tag so to esbuild, the import Counter appears to be unused and is removed.
In this release, esbuild deliberately deviates from the behavior of the official TypeScript compiler if all of these conditions are met:
The "importsNotUsedAsValues" field in tsconfig.json must be present and must not be set to "remove". This is necessary because this is the only case where esbuild can assume that all imports are values instead of types. Any imports that are types will cause a type error when the code is run through the TypeScript type checker. To import types when the importsNotUsedAsValues setting is active, you must use the TypeScript-specific import type syntax instead.
You must not be using esbuild as a bundler. When bundling, esbuild needs to assume that it's not seeing a partial file because the bundling process requires renaming symbols to avoid cross-file name collisions.
You must not have identifier minification enabled. It's useless to preserve unused imports in this case because referencing them by name won't work anyway. And keeping the unused imports would be counter-productive to minification since they would be extra unnecessary data in the output file.
This should hopefully allow esbuild to be used as a TypeScript-to-JavaScript converter for programming languages such as Svelte, at least in many cases. The build pipeline in esbuild wasn't designed for compiling partial modules and this still won't be a fully robust solution (e.g. some variables may be renamed to avoid name collisions in rare cases). But it's possible that these cases are very unlikely to come up in practice. Basically this change to keep unused imports in this case should be useful at best and harmless at worst.
Mark import.meta as supported in node 10.4+
Mark import.meta as supported in node 10.4+ (#626)
It was previously marked as unsupported due to a typo in esbuild's compatibility table, which meant esbuild generated a shim for import.meta even when it's not necessary. It should now be marked as supported in node 10.4 and above so the shim will no longer be included when using a sufficiently new target environment such as --target=node10.4.
Fix for when the working directory ends with / (#627)
If the working directory ended in /, the last path component would be incorrectly duplicated. This was the case when running esbuild with Yarn 2 (but not Yarn 1) and is problematic because some externally-facing directories reference the current working directory in plugins and in output files. The problem has now been fixed and the last path component is no longer duplicated in this case. This fix was contributed by @remorses.
Add an option to omit sourcesContent from generated source maps (#624)
You can now pass --sources-content=false to omit the sourcesContent field from generated source maps. The field embeds the original source code inline in the source map and is the largest part of the source map. This is useful if you don't need the original source code and would like a smaller source map (e.g. you only care about stack traces and don't need the source code for debugging).
Fix exports from ESM files converted to CJS during code splitting (#617)
This release fixes an edge case where files in ECMAScript module format that are converted to CommonJS format during bundling can generate exports to non-top-level symbols when code splitting is active. These files must be converted to CommonJS format if they are referenced by a require() call. When that happens, the symbols in that file are placed inside the CommonJS wrapper closure and are no longer top-level symbols. This means they should no longer be considered exportable for cross-chunk export generation due to code splitting. The result of this fix is that these cases no longer generate output files with module instantiation errors.
Allow --define with array and object literals (#581)
The --define feature allows you to replace identifiers such as DEBUG with literal expressions such as false. This is valuable because the substitution can then participate in constant folding and dead code elimination. For example, if (DEBUG) { ... } could become if (false) { ... } which would then be completely removed in minified builds. However, doing this with compound literal expressions such as array and object literals is an anti-pattern because it could easily result in many copies of the same object in the output file.
This release adds support for array and object literals with --define anyway, but they work differently than other --define expressions. In this case a separate virtual file is created and configured to be injected into all files similar to how the --inject feature works. This means there is only at most one copy of the value in a given output file. However, these values do not participate in constant folding and dead code elimination, since the object can now potentially be mutated at run-time.
Ensure the current working directory remains unique per startService() call
Ensure the current working directory remains unique per startService() call
The change in version 0.8.24 to share service instances caused problems for code that calls process.chdir() before calling startService() to be able to get a service with a different working directory. With this release, calls to startService() no longer share the service instance if the working directory was different at the time of creation.
Consider import references to be side-effect free (#613)
This change improves tree shaking for code containing top-level references to imported symbols such as the following code:
import {Base} from './base'
export class Derived extends Base {}
Identifier references are considered side-effect free if they are locally-defined, but esbuild special-cases identifier references to imported symbols in its AST (the identifier Base in this example). This meant they did not trigger this check and so were not considered locally-defined and therefore side-effect free. That meant that Derived in this example would never be tree-shaken.
The reason for this is that the side-effect determination is made during parsing and during parsing it's not yet known if ./base is a CommonJS module or not. If it is, then Base would be a dynamic run-time property access on exports.Base which could hypothetically be a property with a getter that has side effects. Therefore it could be considered incorrect to remove this code due to tree-shaking because there is technically a side effect.
However, this is a very unlikely edge case and not tree-shaking this code violates developer expectations. So with this release, esbuild will always consider references to imported symbols as being side-effect free. This also aligns with ECMAScript module semantics because with ECMAScript modules, it's impossible to have a user-defined getter for an imported symbol. This means esbuild will now tree-shake unused code in cases like this.
Warn about calling an import namespace object
The following code is an invalid use of an import statement:
import * as express from "express"
express()
The express symbol here is an import namespace object, not a function, so calling it will fail at run-time. This code should have been written like this instead:
import express from "express"
express()
This comes up because for legacy reasons, the TypeScript compiler defaults to a compilation mode where the import * as statement is converted to const express = require("express") which means you can actually call express() successfully. Doing this is incompatible with standard ECMAScript module environments such as the browser, node, and esbuild because an import namespace object is never a function. The TypeScript compiler has a setting to disable this behavior called esModuleInterop and they highly recommend applying it both to new and existing projects to avoid these compatibility problems. See the TypeScript documentation for more information.
With this release, esbuild will now issue a warning when you do this. The warning indicates that your code will crash when run and that your code should be fixed.
Fix a performance regression from version 0.8.4 specific to Yarn 2
Fix a performance regression from version 0.8.4 specific to Yarn 2
Code using esbuild's transformSync function via Yarn 2 experienced a dramatic slowdown in esbuild version 0.8.4 and above. This version added a wrapper script to fix Yarn 2's incompatibility with binary packages. Some code that tries to avoid unnecessarily calling into the wrapper script contained a bug that caused it to fail, which meant that using transformSync with Yarn 2 called into the wrapper script unnecessarily. This launched an extra node process every time the esbuild executable was invoked which can be over 6x slower than just invoking the esbuild executable directly. This release should now invoke the esbuild executable directly without going through the wrapper script, which fixes the performance regression.
Fix a size regression from version 0.7.9 with certain source maps (#611)
Version 0.7.9 added a new behavior to esbuild where in certain cases a JavaScript file may be split into multiple pieces during bundling. Pieces of the same input file may potentially end up in multiple discontiguous regions in the output file. This was necessary to fix an import ordering bug with CommonJS modules. However, it had the side effect of duplicating that file's information in the resulting source map. This didn't affect source map correctness but it made source maps unnecessarily large. This release corrects the problem by ensuring that a given file's information is only ever represented once in the corresponding source map.
Share reference-counted service instances internally
Share reference-counted service instances internally (#600)
Now calling startService() multiple times will share the underlying esbuild child process as long as the lifetimes of the service objects overlap (i.e. the time from startService() to service.stop()). This is just an internal change; there is no change to the public API. It should result in a faster implementation that uses less memory if your code calls startService() multiple times. Previously each call to startService() generated a separate esbuild child process.
Fix re-exports of a side-effect free CommonJS module (#605)
This release fixes a regression introduced in version 0.8.19 in which an import of an export {...} from re-export of a CommonJS module does not include the CommonJS module if it has been marked as "sideEffect": false in its package.json file. This was the case with the Ramda library, and was due to an unhandled case in the linker.
Optionally take binary executable path from environment variable (#592)
You can now set the ESBUILD_BINARY_PATH environment variable to cause the JavaScript API to use a different binary executable path. This is useful if you want to substitute a modified version of the esbuild binary that contains some extra debugging information.
Your coding agent can read these notes before it upgrades. Set up the MCP server →