Releases: evanw/esbuild
v0.8.27
-
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 thesourcesContent
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 asDEBUG
with literal expressions such asfalse
. This is valuable because the substitution can then participate in constant folding and dead code elimination. For example,if (DEBUG) { ... }
could becomeif (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.
v0.8.26
-
Ensure the current working directory remains unique per
startService()
callThe change in version 0.8.24 to share service instances caused problems for code that calls
process.chdir()
before callingstartService()
to be able to get a service with a different working directory. With this release, calls tostartService()
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 thatDerived
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, thenBase
would be a dynamic run-time property access onexports.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 toconst express = require("express")
which means you can actually callexpress()
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 calledesModuleInterop
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.
v0.8.25
-
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 usingtransformSync
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.
v0.8.24
-
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 fromstartService()
toservice.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 callsstartService()
multiple times. Previously each call tostartService()
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 anexport {...} from
re-export of a CommonJS module does not include the CommonJS module if it has been marked as"sideEffect": false
in itspackage.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 theesbuild
binary that contains some extra debugging information.
v0.8.23
-
Fix non-string objects being passed to
transformSync
(#596)The transform function is only supposed to take a string. The type definitions also specify that the input must be a string. However, it happened to convert non-string inputs to a string and some code relied on that behavior. A change in 0.8.22 broke that behavior for
transformSync
specifically forUint8Array
objects, which became an array of numbers instead of a string. This release ensures that the conversion to a string is done up front to avoid something unexpected happening in the implementation. Future releases will likely enforce that the input is a string and throw an error otherwise. -
Revert the speedup to
transformSync
andbuildSync
(#595)This speedup relies on the
worker_threads
module in node. However, when esbuild is used vianode -r
as innode -r esbuild-register file.ts
, the worker thread created by esbuild somehow ends up being completely detached from the main thread. This may be a bug in node itself. Regardless, the approach esbuild was using to improve speed doesn't work in all cases so it has been reverted. It's unclear if it's possible to work around this issue. This approach for improving the speed of synchronous APIs may be a dead end.
v0.8.22
-
Escape fewer characters in virtual module paths (#588)
If a module's path is not in the
file
namespace (i.e. it was created by a plugin), esbuild doesn't assume it's a file system path. The meaning of these paths is entirely up to the plugin. It could be anything including a HTTP URL, a string of code, or randomly-generated characters.Currently esbuild generates a file name for these virtual modules using an internal "human-friendly identifier" that can also be used as a valid JavaScript identifier, which is sometimes used to for example derive the name of the default export of a bundled module. But that means virtual module paths which do happen to represent file system paths could cause more characters to be escaped than necessary. For example, esbuild escapes
-
to_
because-
is not valid in a JavaScript identifier.This release separates the file names derived from virtual module paths from the internal "human-friendly identifier" concept. Characters in the virtual module path that are valid in file paths are no longer escaped.
In the future the output file name of a virtual module will likely be completely customizable with a plugin, so it will be possible to have different behavior for this if desired. But that isn't possible quite yet.
-
Speed up the JavaScript
buildSync
andtransformSync
APIs (#590)Previously the
buildSync
andtransformSync
API calls created a new child esbuild process on every call because communicating with a long-lived child process is asynchronous in node. However, there's a trick that can work around this limitation: esbuild can communicate with the long-lived child process from a child thread using node'sworker_threads
module and block the main thread using JavaScript's new Atomics API. This was a tip from @cspotcode.This approach has now been implemented. A quick benchmark shows that
transformSync
is now 1.5x to 15x faster than it used to be. The speedup depends on the size of the input (smaller inputs get a bigger speedup). The worker thread and child process should automatically be terminated when there are no more event handlers registered on the main thread, so there is no explicitstop()
call like there is with a service object. -
Distribute a 32-bit Linux ARM binary executable via npm (#528)
You should now be able to use npm to install esbuild on a 32-bit Linux ARM device. This lets you run esbuild on a Raspberry Pi. Note that this target isn't officially supported because it's not covered by any automated tests.
v0.8.21
-
On-resolve plugins now apply to entry points (#546)
Previously entry points were required to already be resolved to valid file system paths. This meant that on-resolve plugins didn't run, which breaks certain workflows. Now entry point paths are resolved using normal import resolution rules.
To avoid making this a breaking change, there is now special behavior for entry point path resolution. If the entry point path exists relative to the current working directory and the path does not start with
./
or../
, esbuild will now automatically insert a leading./
at the start of the path to prevent the path from being interpreted as anode_modules
package path. This is only done if the file actually exists to avoid introducing./
for paths with special plugin-specific syntax. -
Enable the build API in the browser (#527)
Previously you could only use the transform API in the browser, not the build API. You can now use the build API in the browser too. There is currently no in-browser file system so the build API will not do anything by default. Using this API requires you to use plugins to provide your own file system. Instructions for running esbuild in the browser can be found here: https://esbuild.github.io/api/#running-in-the-browser.
-
Set the importer to
sourcefile
in on-resolve plugins for stdinWhen the stdin feature is used with on-resolve plugins, the importer for any import paths in stdin is currently always set to
<stdin>
. Thesourcefile
option provides a way to set the file name of stdin but it wasn't carried through to on-resolve plugins due to an oversight. This release changes this behavior so nowsourcefile
is used instead of<stdin>
if present. In addition, if the stdin resolve directory is also specified the importer will be placed in thefile
namespace similar to a normal file.
v0.8.20
-
Fix an edge case with class body initialization
When bundling, top-level class statements are rewritten to variable declarations initialized to a class expression. This avoids a severe performance pitfall in Safari when there are a large number of class statements. However, this transformation was done incorrectly if a class contained a static field that references the class name in its own initializer:
class Foo { static foo = new Foo }
In that specific case, the transformed code could crash when run because the class name is not yet initialized when the static field initializer is run. Only JavaScript code was affected. TypeScript code was not affected. This release fixes this bug.
-
Remove more types of statements as dead code (#580)
This change improves dead-code elimination in the case where unused statements follow an unconditional jump, such as a
return
:if (true) return if (something) thisIsDeadCode()
These unused statements are removed in more cases than in the previous release. Some statements may still be kept that contain hoisted symbols (
var
andfunction
statements) because they could potentially impact the code before the conditional jump.
v0.8.19
-
Handle non-ambiguous multi-path re-exports (#568)
Wildcard re-exports using the
export * from 'path'
syntax can potentially result in name collisions that cause an export name to be ambiguous. For example, the following code would result in an ambiguous export if botha.js
andb.js
export a symbol with the same name:export * from './a.js' export * from './b.js'
Ambiguous exports have two consequences. First, any ambiguous names are silently excluded from the set of exported names. If you use an
import * as
wildcard import, the excluded names will not be present. Second, attempting to explicitly import an ambiguous name using animport {} from
import clause will result in a module instantiation error.This release fixes a bug where esbuild could in certain cases consider a name ambiguous when it actually isn't. Specifically this happens with longer chains of mixed wildcard and named re-exports. Here is one such case:
// entry.js import {x, y} from './not-ambiguous.js' console.log(x, y)
// /not-ambiguous.js export * from './a.js' export * from './b.js'
// /a.js export * from './c.js'
// /b.js export {x} from './c.js'
// /c.js export let x = 1, y = 2
Previously bundling
entry.js
with esbuild would incorrectly generate an error about an ambiguousx
export. Now this case builds successfully without an error. -
Omit warnings about non-string paths in
await import()
inside atry
block (#574)Bundling code that uses
require()
orimport()
with a non-string path currently generates a warning, because the target of that import will not be included in the bundle. This is helpful to warn about because other bundlers handle this case differently (e.g. Webpack bundles the entire directory tree and emulates a file system lookup) so existing code may expect the target of the import to be bundled.You can avoid the warning with esbuild by surrounding the call to
require()
with atry
block. The thinking is that if there is a surroundingtry
block, presumably the code is expecting therequire()
call to possibly fail and is prepared to handle the error. However, there is currently no way to avoid the warning forimport()
expressions. This release introduces an analogous behavior forimport()
expressions. You can now avoid the warning with esbuild if you useawait import()
and surround it with atry
block.
v0.8.18
-
Fix a bug with certain complex optional chains (#573)
The
?.
optional chaining operator only runs the right side of the operator if the left side is undefined, otherwise it returns undefined. This operator can be applied to both property accesses and function calls, and these can be combined into long chains of operators. These expressions must be transformed to a chain of?:
operators if the?.
operator isn't supported in the configured target environment. However, esbuild had a bug where an optional call of an optional property with a further property access afterward didn't preserve the value ofthis
for the call. This bug has been fixed. -
Fix a renaming bug with external imports
There was a possibility of a cross-module name collision while bundling in a certain edge case. Specifically, when multiple files both contained an
import
statement to an external module and then both of those files were imported usingrequire
. For example:// index.js console.log(require('./a.js'), require('./b.js'))
// a.js export {exists} from 'fs'
// b.js export {exists} from 'fs'
In this case the files
a.js
andb.js
are converted to CommonJS format so they can be imported usingrequire
:// a.js import {exists} from "fs"; var require_a = __commonJS((exports) => { __export(exports, { exists: () => exists }); }); // b.js import {exists} from "fs"; var require_b = __commonJS((exports) => { __export(exports, { exists: () => exists }); }); // index.js console.log(require_a(), require_b());
However, the
exists
symbol has been duplicated without being renamed. This is will result in a syntax error at run-time. The reason this happens is that the statements in the filesa.js
andb.js
are placed in a nested scope because they are inside the CommonJS closure. Theimport
statements were extracted outside the closure but the symbols they declared were incorrectly not added to the outer scope. This problem has been fixed, and this edge case should no longer result in name collisions.