Releases: evanw/esbuild
v0.8.30
-
Fix
@jsx
and@jsxFrag
comments without trailing spacesThe
--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 intoswitch (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 intoa(), b();
. -
The comma operator is now extracted from certain expressions. This means
(a, b) + c
turns intoa, b + c
. -
Minification now takes advantage of the left-associativity of certain operators. This means
a && (b && c)
turns intoa && b && c
. -
Computed properties that are strings now become no longer computed. This means
{['a']: b}
turns into{a: b}
andclass { ['a'] = b }
turns intoclass { a = b }
. -
Repeated if-jump statements are now merged. This means
if (a) break; if (b) break;
turns intoif (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;
becomesvar a=0,b=2;
whena
andb
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.
-
v0.8.29
-
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 pointssrc/pages/home/index.ts
andsrc/pages/about/index.ts
, the outbase directory issrc
, and the output directory isout
, the output directory will containout/pages/home/index.js
andout/pages/about/index.js
.However, this means that the
outbase
directory is expected to contain all entry point files (even implicit entry point files fromimport()
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 tooutbase
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 theoutbase
directory. So if the previous example was bundled with an outbase directory oftemp
, the output directory will containout/_.._/pages/home/index.js
andout/_.._/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 toa != 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
andreturn
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.
-
v0.8.28
-
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 importCounter
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 intsconfig.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 theimportsNotUsedAsValues
setting is active, you must use the TypeScript-specificimport 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.
-
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.