Skip to content

Releases: evanw/esbuild

v0.11.18

02 May 22:34
Compare
Choose a tag to compare
  • Add support for OpenBSD on x86-64 (#1235)

    Someone has asked for OpenBSD to be supported on x86-64. It should now be supported starting with this release.

  • Fix an incorrect warning about top-level this

    This was introduced in the previous release, and happens when using a top-level async arrow function with a compilation target that doesn't support it. The reason is that doing this generates a shim that preserves the value of this. However, this warning message is confusing because there is not necessarily any this present in the source code. The warning message has been removed in this case. Now it should only show up if this is actually present in the source code.

v0.11.17

01 May 10:11
Compare
Choose a tag to compare
  • Fix building with a large stdin string with Deno (#1219)

    When I did the initial port of esbuild's node-based API to Deno, I didn't realize that Deno's write(bytes) function doesn't actually write the provided bytes. Instead it may only write some of those bytes and needs to be repeatedly called again until it writes everything. This meant that calling esbuild's Deno-based API could hang if the API request was large enough, which can happen in practice when using the stdin string feature. The write API is now called in a loop so these hangs in Deno should now be fixed.

  • Add a warning about replacing this with undefined in ESM code (#1225)

    There is existing JavaScript code that sometimes references top-level this as a way to access the global scope. However, top-level this is actually specified to be undefined inside of ECMAScript module code, which makes referencing top-level this inside ESM code useless. This issue can come up when the existing JavaScript code is adapted for ESM by adding import and/or export. All top-level references to this are replaced with undefined when bundling to make sure ECMAScript module behavior is emulated correctly regardless of the environment in which the resulting code is run.

    With this release, esbuild will now warn about this when bundling:

     > example.mjs:1:61: warning: Top-level "this" will be replaced with undefined since this file is an ECMAScript module
        1 │ export let Array = (typeof window !== 'undefined' ? window : this).Array
          ╵                                                              ~~~~
       example.mjs:1:0: note: This file is considered an ECMAScript module because of the "export" keyword here
        1 │ export let Array = (typeof window !== 'undefined' ? window : this).Array
          ╵ ~~~~~~
    

    This warning is not unique to esbuild. Rollup also already has a similar warning:

    (!) `this` has been rewritten to `undefined`
    https://rollupjs.org/guide/en/#error-this-is-undefined
    example.mjs
    1: export let Array = (typeof window !== 'undefined' ? window : this).Array
                                                                    ^
    
  • Allow a string literal as a JSX fragment (#1217)

    TypeScript's JSX implementation allows you to configure a custom JSX factory and a custom JSX fragment, but requires that they are both valid JavaScript identifier member expression chains. Since esbuild's JSX implementation is based on TypeScript, esbuild has the same requirement. So React.createElement is a valid JSX factory value but ['React', 'createElement'] is not.

    However, the Mithril framework has decided to use "[" as a JSX fragment, which is not a valid JavaScript identifier member expression chain. This meant that using Mithril with esbuild required a workaround. In this release, esbuild now lets you use a string literal as a custom JSX fragment. It should now be easier to use esbuild's JSX implementation with libraries such as Mithril.

  • Fix metafile in onEnd with watch mode enabled (#1186)

    This release fixes a bug where the metafile property was incorrectly undefined inside plugin onEnd callbacks if watch mode is enabled for all builds after the first build. The metafile property was accidentally being set after calling onEnd instead of before.

v0.11.16

28 Apr 08:54
Compare
Choose a tag to compare
  • Fix TypeScript enum edge case (#1198)

    In TypeScript, you can reference the inner closure variable in an enum within the inner closure by name:

    enum A { B = A }

    The TypeScript compiler generates the following code for this case:

    var A;
    (function (A) {
      A[A["B"] = A] = "B";
    })(A || (A = {}));

    However, TypeScript also lets you declare an enum value with the same name as the inner closure variable. In that case, the value "shadows" the declaration of the inner closure variable:

    enum A { A = 1, B = A }

    The TypeScript compiler generates the following code for this case:

    var A;
    (function (A) {
      A[A["A"] = 1] = "A";
      A[A["B"] = 1] = "B";
    })(A || (A = {}));

    Previously esbuild reported a duplicate variable declaration error in the second case due to the collision between the enum value and the inner closure variable with the same name. With this release, the shadowing is now handled correctly.

  • Parse the @-moz-document CSS rule (#1203)

    This feature has been removed from the web because it's actively harmful, at least according to this discussion. However, there is one exception where @-moz-document url-prefix() { is accepted by Firefox to basically be an "if Firefox" conditional rule. Because of this, esbuild now parses the @-moz-document CSS rule. This should result in better pretty-printing and minification and no more warning when this rule is used.

  • Fix syntax error in TypeScript-specific speculative arrow function parsing (#1211)

    Because of grammar ambiguities, expressions that start with a parenthesis are parsed using what's called a "cover grammar" that is a super-position of both a parenthesized expression and an arrow function parameter list. In JavaScript, the cover grammar is unambiguously an arrow function if and only if the following token is a => token.

    But in TypeScript, the expression is still ambiguously a parenthesized expression or an arrow function if the following token is a : since it may be the second half of the ?: operator or a return type annotation. This requires speculatively attempting to reduce the cover grammar to an arrow function parameter list.

    However, when doing this esbuild eagerly reported an error if a default argument was encountered and the target is es5 (esbuild doesn't support lowering default arguments to ES5). This is problematic in the following TypeScript code since the parenthesized code turns out to not be an arrow function parameter list:

    function foo(check, hover) {
      return check ? (hover = 2, bar) : baz();
    }

    Previously this code incorrectly generated an error since hover = 2 was incorrectly eagerly validated as a default argument. With this release, the reporting of the default argument error when targeting es5 is now done lazily and only when it's determined that the parenthesized code should actually be interpreted as an arrow function parameter list.

  • Further changes to the behavior of the browser field (#1209)

    This release includes some changes to how the browser field in package.json is interpreted to better match how Browserify, Webpack, Parcel, and Rollup behave. The interpretation of this map in esbuild is intended to be applied if and only if it's applied by any one of these bundlers. However, there were some cases where esbuild applied the mapping and none of the other bundlers did, which could lead to build failures. These cases have been added to my growing list of browser field test cases and esbuild's behavior should now be consistent with other bundlers again.

  • Avoid placing a super() call inside a return statement (#1208)

    When minification is enabled, an expression followed by a return statement (e.g. a(); return b) is merged into a single statement (e.g. return a(), b). This is done because it sometimes results in smaller code. If the return statement is the only statement in a block and the block is in a single-statement context, the block can be removed which saves a few characters.

    Previously esbuild applied this rule to calls to super() inside of constructors. Doing that broke esbuild's class lowering transform that tries to insert class field initializers after the super() call. This transform isn't robust and only scans the top-level statement list inside the constructor, so inserting the super() call inside of the return statement means class field initializers were inserted before the super() call instead of after. This could lead to run-time crashes due to initialization failure.

    With this release, top-level calls to super() will no longer be placed inside return statements (in addition to various other kinds of statements such as throw, which are now also handled). This should avoid class field initializers being inserted before the super() call.

  • Fix a bug with onEnd and watch mode (#1186)

    This release fixes a bug where onEnd plugin callbacks only worked with watch mode when an onRebuild watch mode callback was present. Now onEnd callbacks should fire even if there is no onRebuild callback.

  • Fix an edge case with minified export names and code splitting (#1201)

    The names of symbols imported from other chunks were previously not considered for renaming during minified name assignment. This could cause a syntax error due to a name collision when two symbols have the same original name. This was just an oversight and has been fixed, so symbols imported from other chunks should now be renamed when minification is enabled.

  • Provide a friendly error message when you forget async (#1216)

    If the parser hits a parse error inside a non-asynchronous function or arrow expression and the previous token is await, esbuild will now report a friendly error about a missing async keyword instead of reporting the parse error. This behavior matches other JavaScript parsers including TypeScript, Babel, and V8.

    The previous error looked like this:

     > test.ts:2:8: error: Expected ";" but found "f"
        2 │   await f();
          ╵         ^
    

    The error now looks like this:

     > example.js:2:2: error: "await" can only be used inside an "async" function
        2 │   await f();
          ╵   ~~~~~
       example.js:1:0: note: Consider adding the "async" keyword here
        1 │ function f() {
          │ ^
          ╵ async
    

v0.11.15

26 Apr 07:57
Compare
Choose a tag to compare
  • Provide options for how to handle legal comments (#919)

    A "legal comment" is considered to be any comment that contains @license or @preserve or that starts with //! or /*!. These comments are preserved in output files by esbuild since that follows the intent of the original authors of the code.

    However, some people want to remove the automatically-generated license information before they distribute their code. To facilitate this, esbuild now provides several options for how to handle legal comments (via --legal-comments= in the CLI and legalComments in the JS API):

    • none: Do not preserve any legal comments
    • inline: Preserve all statement-level legal comments
    • eof: Move all statement-level legal comments to the end of the file
    • linked: Move all statement-level legal comments to a .LEGAL.txt file and link to them with a comment
    • external: Move all statement-level legal comments to a .LEGAL.txt file but to not link to them

    The default behavior is eof when bundling and inline otherwise.

  • Add onStart and onEnd callbacks to the plugin API

    Plugins can now register callbacks to run when a build is started and ended:

    const result = await esbuild.build({
      ...
      incremental: true,
      plugins: [{
        name: 'example',
        setup(build) {
          build.onStart(() => console.log('build started'))
          build.onEnd(result => console.log('build ended', result))
        },
      }],
    })
    await result.rebuild()

    One benefit of onStart and onEnd is that they are run for all builds including rebuilds (relevant for incremental mode, watch mode, or serve mode), so they should be a good place to do work related to the build lifecycle.

    More details:

    • build.onStart()

      You should not use an onStart callback for initialization since it can be run multiple times. If you want to initialize something, just put your plugin initialization code directly inside the setup function instead.

      The onStart callback can be async and can return a promise. However, the build does not wait for the promise to be resolved before starting, so a slow onStart callback will not necessarily slow down the build. All onStart callbacks are also run concurrently, not consecutively. The returned promise is purely for error reporting, and matters when the onStart callback needs to do an asynchronous operation that may fail. If your plugin needs to wait for an asynchronous task in onStart to complete before any onResolve or onLoad callbacks are run, you will need to have your onResolve or onLoad callbacks block on that task from onStart.

      Note that onStart callbacks do not have the ability to mutate build.initialOptions. The initial options can only be modified within the setup function and are consumed once the setup function returns. All rebuilds use the same initial options so the initial options are never re-consumed, and modifications to build.initialOptions that are done within onStart are ignored.

    • build.onEnd()

      All onEnd callbacks are run in serial and each callback is given access to the final build result. It can modify the build result before returning and can delay the end of the build by returning a promise. If you want to be able to inspect the build graph, you should set build.initialOptions.metafile = true and the build graph will be returned as the metafile property on the build result object.

v0.11.14

24 Apr 10:57
Compare
Choose a tag to compare
  • 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.

v0.11.13

23 Apr 02:52
Compare
Choose a tag to compare
  • 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 () => {} {}

v0.11.12

17 Apr 11:22
Compare
Choose a tag to compare
  • 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.

v0.11.11

15 Apr 02:11
Compare
Choose a tag to compare
  • 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/[email protected]/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.

v0.11.10

13 Apr 09:54
Compare
Choose a tag to compare
  • 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.

v0.11.9

12 Apr 09:00
Compare
Choose a tag to compare
  • 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.