Skip to content


Choose a tag to compare
@github-actions github-actions released this 09 Mar 06:38

This release contains backwards-incompatible changes. Since esbuild is before version 1.0.0, these changes have been released as a new minor version to reflect this (as recommended by npm). You should either be pinning the exact version of esbuild in your package.json file or be using a version range syntax that only accepts patch upgrades such as ^0.8.0. See the documentation about semver for more information.

  • Add support for node's exports field in package.json files (#187)

    This feature was recently added to node. It allows you to rewrite what import paths inside your package map to as well as to prevent people from importing certain files in your package. Adding support for this to esbuild is a breaking change (i.e. code that was working fine before can easily stop working) so adding support for it has been delayed until this breaking change release.

    One way to use this feature is to remap import paths for your package. For example, this would remap an import of your-pkg/esm/lib.js (the "public" import path) to your-pkg/dist/esm/lib.js (the "private" file system path):

      "name": "your-pkg",
      "exports": {
        "./esm/*": "./dist/esm/*",
        "./cjs/*": "./dist/cjs/*"

    Another way to use this feature is to have conditional imports where the same import path can mean different things in different situations. For example, this would remap require('your-pkg') to your-pkg/required.cjs and import 'your-pkg' to your-pkg/imported.mjs:

      "name": "your-pkg",
      "exports": {
        "import": "./imported.mjs",
        "require": "./required.cjs"

    There is built-in support for the import and require conditions depending on the kind of import and the browser and node conditions depending on the current platform. In addition, the default condition always applies regardless of the current configuration settings and can be used as a catch-all fallback condition.

    Note that when you use conditions, your package may end up in the bundle multiple times! This is a subtle issue that can cause bugs due to duplicate copies of your code's state in addition to bloating the resulting bundle. This is commonly known as the dual package hazard. The primary way of avoiding this is to put all of your code in the require condition and have the import condition just be a light wrapper that calls require on your package and re-exports the package using ESM syntax.

    There is also support for custom conditions with the --conditions= flag. The meaning of these is entirely up to package authors. For example, you could imagine a package that requires you to configure --conditions=test,en-US. Node has currently only endorsed the development and production custom conditions for recommended use.

  • Remove the esbuild.startService() API

    Due to #656, Calling service.stop() no longer does anything, so there is no longer a strong reason for keeping the esbuild.startService() API around. The primary thing it currently does is just make the API more complicated and harder to use. You can now just call and esbuild.transform() directly instead of calling esbuild.startService().then(service => or esbuild.startService().then(service => service.transform()).

    If you are using esbuild in the browser, you now need to call esbuild.initialize({ wasmURL }) and wait for the returned promise before calling esbuild.transform(). It takes the same options that esbuild.startService() used to take. Note that the esbuild.buildSync() and esbuild.transformSync() APIs still exist when using esbuild in node. Nothing has changed about the synchronous esbuild APIs.

  • Remove the metafile from outputFiles (#633)

    Previously using metafile with the API is unnecessarily cumbersome because you have to extract the JSON metadata from the output file yourself instead of it just being provided to you as a return value. This is especially a bummer if you are using write: false because then you need to use a for loop over the output files and do string comparisons with the file paths to try to find the one corresponding to the metafile. Returning the metadata directly is an important UX improvement for the API. It means you can now do this:

    const result = await{
      entryPoints: ['entry.js'],
      bundle: true,
      metafile: true,
  • The banner and footer options are now language-specific (#712)

    The --banner= and --footer= options now require you to pass the file type:

    • CLI:

      esbuild --banner:js=//banner --footer:js=//footer
      esbuild --banner:css=/*banner*/ --footer:css=/*footer*/
    • JavaScript{
        banner: { js: '//banner', css: '/*banner*/' },
        footer: { js: '//footer', css: '/*footer*/' },
    • Go

        Banner: map[string]string{"js": "//banner"},
        Footer: map[string]string{"js": "//footer"},
        Banner: map[string]string{"css": "/*banner*/"},
        Footer: map[string]string{"css": "/*footer*/"},

    This was changed because the feature was originally added in a JavaScript-specific manner, which was an oversight. CSS banners and footers must be separate from JavaScript banners and footers to avoid injecting JavaScript syntax into your CSS files.

  • The extensions .mjs and .cjs are no longer implicit

    Previously the "resolve extensions" setting included .mjs and .cjs but this is no longer the case. This wasn't a good default because it doesn't match node's behavior and could break some packages. You now have to either explicitly specify these extensions or configure the "resolve extensions" setting yourself.

  • Remove the --summary flag and instead just always print a summary (#704)

    The summary can be disabled if you don't want it by passing --log-level=warning instead. And it can be enabled in the API by setting logLevel: 'info'. I'm going to try this because I believe it will improve the UX. People have this problem with esbuild when they first try it where it runs so quickly that they think it must be broken, only to later discover that it actually worked fine. While this is funny, it seems like a good indication that the UX could be improved. So I'm going to try automatically printing a summary to see how that goes. Note that the summary is not printed if incremental builds are active (this includes the watch and serve modes).

  • Rename --error-limit= to --log-limit=

    This parameter has been renamed because it now applies to both warnings and errors, not just to errors. Previously setting the error limit did not apply any limits to the number of warnings printed, which could sometimes result in a deluge of warnings that are problematic for Windows Command Prompt, which is very slow to print to and has very limited scrollback. Now the log limit applies to the total number of log messages including both errors and warnings, so no more than that number of messages will be printed. The log usually prints log messages immediately but it will now intentionally hold back warnings when approaching the limit to make room for possible future errors during a build. So if a build fails you should be guaranteed to see an error message (i.e. warnings can't use up the entire log limit and then prevent errors from being printed).

  • Remove the deprecated --avoid-tdz option

    This option is now always enabled and cannot be disabled, so it is being removed from the API. The existing API parameter no longer does anything so this removal has no effect the generated output.

  • Remove SpinnerBusy and SpinnerIdle from the Go API

    These options were part of an experiment with the CLI that didn't work out. Watch mode no longer uses a spinner because it turns out people want to be able to interleave esbuild's stderr pipe with other tools and were getting tripped up by the spinner animation. These options no longer do anything and have been removed.