Releases: evanw/esbuild
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.
v0.8.17
-
Get esbuild working on the Apple M1 chip via Rosetta 2 (#564)
The Go compiler toolchain does not yet support the new Apple M1 chip. Go version 1.15 is currently in a feature freeze period so support will be added in the next version, Go 1.16, which will be released in February.
This release changes the install script to install the executable for macOS
x64
on macOSarm64
too. Doing this should still work because of the executable translation layer built into macOS. This change was contributed by @sod.
v0.8.16
-
Improve TypeScript type definitions (#559)
The return value of the
build
API has some optional fields that are undefined unless certain arguments are present. That meant you had to use the!
null assertion operator to avoid a type error if you have the TypeScriptstrictNullChecks
setting enabled in your project. This release adds additional type information so that if the relevant arguments are present, the TypeScript compiler can tell that these optional fields on the return value will never be undefined. This change was contributed by @lukeed. -
Omit a warning about
require.main
when targeting CommonJS (#560)A common pattern in code that's intended to be run in node is to check if
require.main === module
. That will be true if the current file is being run from the command line but false if the current file is being run because some other code calledrequire()
on it. Previously esbuild generated a warning about an unexpected use ofrequire
. Now this warning is no longer generated forrequire.main
when the output format iscjs
. -
Warn about defining
process.env.NODE_ENV
as an identifier (#466)The define feature can be used to replace an expression with either a JSON literal or an identifier. Forgetting to put quotes around a string turns it into an identifier, which is a common mistake. This release introduces a warning when you define
process.env.NODE_ENV
as an identifier instead of a string. It's very common to use define to replaceprocess.env.NODE_ENV
with either"production"
or"development"
and sometimes people accidentally replace it withproduction
ordevelopment
instead. This is worth warning about because otherwise there would be no indication that something is wrong until the code crashes when run. -
Allow starting a local server at a specific host address (#563)
By default, esbuild's local HTTP server is only available on the internal loopback address. This is deliberate behavior for security reasons, since the local network environment may not be trusted. However, it can be useful to run the server on a different address when developing with esbuild inside of a virtual machine/docker container or to request development assets from a remote testing device on the same network at a different IP address. With this release, you can now optionally specify the host in addition to the port:
esbuild --serve=192.168.0.1:8000
esbuild.serve({ host: '192.168.0.1', port: 8000, }, { ... })
server, err := api.Serve(api.ServeOptions{ Host: "192.168.0.1", Port: 8000, }, api.BuildOptions{ ... })
This change was contributed by @jamalc.
v0.8.15
-
Allow
paths
withoutbaseUrl
intsconfig.json
This feature was recently released in TypeScript 4.1. The
paths
feature intsconfig.json
allows you to do custom import path rewriting. For example, you can map paths matching@namespace/*
to the path./namespace/src/*
relative to thetsconfig.json
file. Previously using thepaths
feature required you to additionally specifybaseUrl
so that the compiler could know which directory the path aliases were supposed to be relative to.However, specifying
baseUrl
has the potentially-problematic side effect of causing all import paths to be looked up relative to thebaseUrl
directory, which could potentially cause package paths to accidentally be redirected to non-package files. SpecifyingbaseUrl
also causes Visual Studio Code's auto-import feature to generate paths relative to thebaseUrl
directory instead of relative to the directory containing the current file. There is more information about the problems this causes here: microsoft/TypeScript#31869.With TypeScript 4.1, you can now omit
baseUrl
when usingpaths
. When you do this, it as if you had written"baseUrl": "."
instead for the purpose of thepaths
feature, but thebaseUrl
value is not actually set and does not affect path resolution. Thesetsconfig.json
files are now supported by esbuild. -
Fix evaluation order issue with import cycles and CommonJS-style output formats (#542)
Previously entry points involved in an import cycle could cause evaluation order issues if the output format was
iife
orcjs
instead ofesm
. This happened because this edge case was handled by treating the entry point file as a CommonJS file, which extracted the code into a CommonJS wrapper. Here's an example:Input files:
// index.js import { test } from './lib' export function fn() { return 42 } if (test() !== 42) throw 'failure'
// lib.js import { fn } from './index' export let test = fn
Previous output (problematic):
// index.js var require_esbuild = __commonJS((exports) => { __export(exports, { fn: () => fn2 }); function fn2() { return 42; } if (test() !== 42) throw "failure"; }); // lib.js var index = __toModule(require_esbuild()); var test = index.fn; module.exports = require_esbuild();
This approach changed the evaluation order because the CommonJS wrapper conflates both binding and evaluation. Binding and evaluation need to be separated to correctly handle this edge case. This edge case is now handled by inlining what would have been the contents of the CommonJS wrapper into the entry point location itself.
Current output (fixed):
// index.js __export(exports, { fn: () => fn }); // lib.js var test = fn; // index.js function fn() { return 42; } if (test() !== 42) throw "failure";
v0.8.14
-
Fix a concurrency bug caused by an error message change (#556)
An improvement to the error message for path resolution was introduced in version 0.8.12. It detects when a relative path is being interpreted as a package path because you forgot to start the path with
./
:> src/posts/index.js: error: Could not resolve "PostCreate" (use "./PostCreate" to import "src/posts/PostCreate.js") 2 │ import PostCreate from 'PostCreate'; ╵ ~~~~~~~~~~~~
This is implemented by re-running path resolution for package path resolution failures as a relative path instead. Unfortunately, this second path resolution operation wasn't guarded by a mutex and could result in concurrency bugs. This issue only occurs when path resolution fails. It is fixed in this release.
v0.8.13
-
Assigning to a
const
symbol is now an error when bundlingThis change was made because esbuild may need to change a
const
symbol into a non-constant symbol in certain situations. One situation is when the "avoid TDZ" option is enabled. Another situation is some potential upcoming changes to lazily-evaluate certain modules for code splitting purposes. Making this an error gives esbuild the freedom to do these code transformations without potentially causing problems where constants are mutated. This has already been a warning for a while so code that does this should already have been obvious. This warning was made an error in a patch release because the expectation is that no real code relies on this behavior outside of conformance tests. -
Fix for the
--keep-names
option and anonymous lowered classesThis release fixes an issue where names were not preserved for anonymous classes that contained newer JavaScript syntax when targeting an older version of JavaScript. This was because that causes the class expression to be transformed into a sequence expression, which was then not recognized as a class expression. For example, the class did not have the name
foo
in the code below when the target was set toes6
:let foo = class { #privateMethod() {} }
The
name
property of this class object is nowfoo
. -
Fix captured class names when class name is re-assigned
This fixes a corner case with class lowering to better match the JavaScript specification. In JavaScript, the body of a class statement contains an implicit constant symbol with the same name as the symbol of the class statement itself. Lowering certain class features such as private methods means moving them outside the class body, in which case the contents of the private method are no longer within the scope of the constant symbol. This can lead to a behavior change if the class is later re-assigned:
class Foo { static test() { return this.#method() } static #method() { return Foo } } let old = Foo Foo = class Bar {} console.log(old.test() === old) // This should be true
Previously this would print
false
when transformed to ES6 by esbuild. This now printstrue
. The current transformed output looks like this:var _method, method_fn; const Foo2 = class { static test() { return __privateMethod(this, _method, method_fn).call(this); } }; let Foo = Foo2; _method = new WeakSet(); method_fn = function() { return Foo2; }; _method.add(Foo); let old = Foo; Foo = class Bar { }; console.log(old.test() === old);
-
The
--allow-tdz
option is now always applied during bundlingThis option turns top-level
let
,const
, andclass
statements intovar
statements to work around some severe performance issues in the JavaScript run-time environment in Safari. Previously you had to explicitly enable this option. Now this behavior will always happen, and there is no way to turn it off. This means the--allow-tdz
option is now meaningless and no longer does anything. It will be removed in a future release. -
When bundling and minifying,
const
is now converted intolet
This was done because it's semantically equivalent but shorter. It's a valid transformation because assignment to a
const
symbol is now a compile-time error when bundling, so changingconst
tolet
should now not affect run-time behavior.