esbuild-wasm
Advanced tools
Changelog
0.17.7
Change esbuild's parsing of TypeScript instantiation expressions to match TypeScript 4.8+ (#2907)
This release updates esbuild's implementation of instantiation expression erasure to match microsoft/TypeScript#49353. The new rules are as follows (copied from TypeScript's PR description):
When a potential type argument list is followed by
- a line break,
- an
(
token,- a template literal string, or
- any token except
<
or>
that isn't the start of an expression,we consider that construct to be a type argument list. Otherwise we consider the construct to be a
<
relational expression followed by a>
relational expression.
Ignore sideEffects: false
for imported CSS files (#1370, #1458, #2905)
This release ignores the sideEffects
annotation in package.json
for CSS files that are imported into JS files using esbuild's css
loader. This means that these CSS files are no longer be tree-shaken.
Importing CSS into JS causes esbuild to automatically create a CSS entry point next to the JS entry point containing the bundled CSS. Previously packages that specified some form of "sideEffects": false
could potentially cause esbuild to consider one or more of the JS files on the import path to the CSS file to be side-effect free, which would result in esbuild removing that CSS file from the bundle. This was problematic because the removal of that CSS is outwardly observable, since all CSS is global, so it was incorrect for previous versions of esbuild to tree-shake CSS files imported into JS files.
Add constant folding for certain additional equality cases (#2394, #2895)
This release adds constant folding for expressions similar to the following:
// Original input
console.log(
null === 'foo',
null === undefined,
null == undefined,
false === 0,
false == 0,
1 === true,
1 == true,
)
// Old output
console.log(
null === "foo",
null === void 0,
null == void 0,
false === 0,
false == 0,
1 === true,
1 == true
);
// New output
console.log(
false,
false,
true,
false,
true,
false,
true
);
Changelog
0.17.6
Fix a CSS parser crash on invalid CSS (#2892)
Previously the following invalid CSS caused esbuild's parser to crash:
@media screen
The crash was caused by trying to construct a helpful error message assuming that there was an opening {
token, which is not the case here. This release fixes the crash.
Inline TypeScript enums that are referenced before their declaration
Previously esbuild inlined enums within a TypeScript file from top to bottom, which meant that references to TypeScript enum members were only inlined within the same file if they came after the enum declaration. With this release, esbuild will now inline enums even when they are referenced before they are declared:
// Original input
export const foo = () => Foo.FOO
const enum Foo { FOO = 0 }
// Old output (with --tree-shaking=true)
export const foo = () => Foo.FOO;
var Foo = /* @__PURE__ */ ((Foo2) => {
Foo2[Foo2["FOO"] = 0] = "FOO";
return Foo2;
})(Foo || {});
// New output (with --tree-shaking=true)
export const foo = () => 0 /* FOO */;
This makes esbuild's TypeScript output smaller and faster when processing code that does this. I noticed this issue when I ran the TypeScript compiler's source code through esbuild's bundler. Now that the TypeScript compiler is going to be bundled with esbuild in the upcoming TypeScript 5.0 release, improvements like this will also improve the TypeScript compiler itself!
Fix esbuild installation on Arch Linux (#2785, #2812, #2865)
Someone made an unofficial esbuild
package for Linux that adds the ESBUILD_BINARY_PATH=/usr/bin/esbuild
environment variable to the user's default environment. This breaks all npm installations of esbuild for users with this unofficial Linux package installed, which has affected many people. Most (all?) people who encounter this problem haven't even installed this unofficial package themselves; instead it was installed for them as a dependency of another Linux package. The problematic change to add the ESBUILD_BINARY_PATH
environment variable was reverted in the latest version of this unofficial package. However, old versions of this unofficial package are still there and will be around forever. With this release, ESBUILD_BINARY_PATH
is now ignored by esbuild's install script when it's set to the value /usr/bin/esbuild
. This should unbreak using npm to install esbuild
in these problematic Linux environments.
Note: The ESBUILD_BINARY_PATH
variable is an undocumented way to override the location of esbuild's binary when esbuild's npm package is installed, which is necessary to substitute your own locally-built esbuild binary when debugging esbuild's npm package. It's only meant for very custom situations and should absolutely not be forced on others by default, especially without their knowledge. I may remove the code in esbuild's installer that reads ESBUILD_BINARY_PATH
in the future to prevent these kinds of issues. It will unfortunately make debugging esbuild harder. If ESBUILD_BINARY_PATH
is ever removed, it will be done in a "breaking change" release.
Changelog
0.17.4
Implement HTTP HEAD
requests in serve mode (#2851)
Previously esbuild's serve mode only responded to HTTP GET
requests. With this release, esbuild's serve mode will also respond to HTTP HEAD
requests, which are just like HTTP GET
requests except that the body of the response is omitted.
Permit top-level await in dead code branches (#2853)
Adding top-level await to a file has a few consequences with esbuild:
module
and exports
for exports and also enables strict mode, which disables certain syntax and changes how function hoisting works (among other things).require()
on this file or on any file that imports this file (even indirectly), since the require()
function doesn't return a promise and so can't represent top-level await.This release relaxes these rules slightly: rules 2 and 3 will now no longer apply when esbuild has identified the code branch as dead code, such as when it's behind an if (false)
check. This should make it possible to use esbuild to convert code into different output formats that only uses top-level await conditionally. This release does not relax rule 1. Top-level await will still cause esbuild to unconditionally consider the input module format to be ESM, even when the top-level await
is in a dead code branch. This is necessary because whether the input format is ESM or not affects the whole file, not just the dead code branch.
Fix entry points where the entire file name is the extension (#2861)
Previously if you passed esbuild an entry point where the file extension is the entire file name, esbuild would use the parent directory name to derive the name of the output file. For example, if you passed esbuild a file ./src/.ts
then the output name would be src.js
. This bug happened because esbuild first strips the file extension to get ./src/
and then joins the path with the working directory to get the absolute path (e.g. join("/working/dir", "./src/")
gives /working/dir/src
). However, the join operation also canonicalizes the path which strips the trailing /
. Later esbuild uses the "base name" operation to extract the name of the output file. Since there is no trailing /
, esbuild returns "src"
as the base name instead of ""
, which causes esbuild to incorrectly include the directory name in the output file name. This release fixes this bug by deferring the stripping of the file extension until after all path manipulations have been completed. So now the file ./src/.ts
will generate an output file named .js
.
Support replacing property access expressions with inject
At a high level, this change means the inject
feature can now replace all of the same kinds of names as the define
feature. So inject
is basically now a more powerful version of define
, instead of previously only being able to do some of the things that define
could do.
Soem background is necessary to understand this change if you aren't already familiar with the inject
feature. The inject
feature lets you replace references to global variable with a shim. It works like this:
inject
featureFor example, if you inject the following file using --inject:./injected.js
:
// injected.js
let processShim = { cwd: () => '/' }
export { processShim as process }
Then esbuild will replace all references to process
with the processShim
variable, which will cause process.cwd()
to return '/'
. This feature is sort of abusing the ESM export alias syntax to specify the mapping of global variables to shims. But esbuild works this way because using this syntax for that purpose is convenient and terse.
However, if you wanted to replace a property access expression, the process was more complicated and not as nice. You would have to:
inject
featuredefine
feature to map the property access expression to the random name you made in step 2For example, if you inject the following file using --inject:./injected2.js --define:process.cwd=someRandomName
:
// injected2.js
let cwdShim = () => '/'
export { cwdShim as someRandomName }
Then esbuild will replace all references to process.cwd
with the cwdShim
variable, which will also cause process.cwd()
to return '/'
(but which this time will not mess with other references to process
, which might be desirable).
With this release, using the inject feature to replace a property access expression is now as simple as using it to replace an identifier. You can now use JavaScript's "arbitrary module namespace identifier names" feature to specify the property access expression directly using a string literal. For example, if you inject the following file using --inject:./injected3.js
:
// injected3.js
let cwdShim = () => '/'
export { cwdShim as 'process.cwd' }
Then esbuild will now replace all references to process.cwd
with the cwdShim
variable, which will also cause process.cwd()
to return '/'
(but which will also not mess with other references to process
).
In addition to inserting a shim for a global variable that doesn't exist, another use case is replacing references to static methods on global objects with cached versions to both minify them better and to make access to them potentially faster. For example:
// Injected file
let cachedMin = Math.min
let cachedMax = Math.max
export {
cachedMin as 'Math.min',
cachedMax as 'Math.max',
}
// Original input
function clampRGB(r, g, b) {
return {
r: Math.max(0, Math.min(1, r)),
g: Math.max(0, Math.min(1, g)),
b: Math.max(0, Math.min(1, b)),
}
}
// Old output (with --minify)
function clampRGB(a,t,m){return{r:Math.max(0,Math.min(1,a)),g:Math.max(0,Math.min(1,t)),b:Math.max(0,Math.min(1,m))}}
// New output (with --minify)
var a=Math.min,t=Math.max;function clampRGB(h,M,m){return{r:t(0,a(1,h)),g:t(0,a(1,M)),b:t(0,a(1,m))}}