Skip to content

v0.10.0

Compare
Choose a tag to compare
@github-actions github-actions released this 25 Mar 09:28

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.9.0. See the documentation about semver for more information.

That said, there are no breaking API changes in this release. The breaking changes are instead about how input files are interpreted and/or how output files are generated in some cases. So upgrading should be relatively straightforward as your API calls should still work the same way, but please make sure to test your code when you upgrade because the output may be different. These breaking changes are as follows:

  • No longer support module or exports in an ESM file (#769)

    This removes support for using CommonJS exports in a file with ESM exports. Previously this worked by converting the ESM file to CommonJS and then mixing the CommonJS and ESM exports into the same exports object. But it turns out that supporting this is additional complexity for the bundler, so it has been removed. It's also not something that works in real JavaScript environments since modules will never support both export syntaxes at once.

    Note that this doesn't remove support for using require in ESM files. Doing this still works (and can be made to work in a real ESM environment by assigning to globalThis.require). This also doesn't remove support for using import in CommonJS files. Doing this also still works.

  • No longer change import() to require() (#1029)

    Previously esbuild's transform for import() matched TypeScript's behavior, which is to transform it into Promise.resolve().then(() => require()) when the current output format is something other than ESM. This was done when an import is external (i.e. not bundled), either due to the expression not being a string or due to the string matching an external import path.

    With this release, esbuild will no longer do this. Now import() expressions will be preserved in the output instead. These expressions can be handled in non-ESM code by arranging for the import identifier to be a function that imports ESM code. This is how node works, so it will now be possible to use import() with node when the output format is something other than ESM.

  • Run-time export * as statements no longer convert the file to CommonJS

    Certain export * as statements require a bundler to evaluate them at run-time instead of at compile-time like the JavaScript specification. This is the case when re-exporting symbols from an external file and a file in CommonJS format.

    Previously esbuild would handle this by converting the module containing the export * as statement to CommonJS too, since CommonJS exports are evaluated at run-time while ESM exports are evaluated at bundle-time. However, this is undesirable because tree shaking only works for ESM, not for CommonJS, and the CommonJS wrapper causes additional code bloat. Another upcoming problem is that top-level await cannot work within a CommonJS module because CommonJS require() is synchronous.

    With this release, esbuild will now convert modules containing a run-time export * as statement to a special ESM-plus-dynamic-fallback mode. In this mode, named exports present at bundle time can still be imported directly by name, but any imports that don't match one of the explicit named imports present at bundle time will be converted to a property access on the fallback object instead of being a bundle error. These property accesses are then resolved at run-time and will be undefined if the export is missing.

  • Change whether certain files are interpreted as ESM or CommonJS (#1043)

    The bundling algorithm currently doesn't contain any logic that requires flagging modules as CommonJS vs. ESM beforehand. Instead it handles a superset and then sort of decides later if the module should be treated as CommonJS vs. ESM based on whether the module uses the module or exports variables and/or the exports keyword.

    With this release, files that follow node's rules for module types will be flagged as explicitly ESM. This includes files that end in .mjs and files within a package containing "type": "module" in the enclosing package.json file. The CommonJS module and exports features will be unavailable in these files. This matters most for files without any exports, since then it's otherwise ambiguous what the module type is.

    In addition, files without exports should now accurately fall back to being considered CommonJS. They should now generate a default export of an empty object when imported using an import statement, since that's what happens in node when you import a CommonJS file into an ESM file in node. Previously the default export could be undefined because these export-less files were sort of treated as ESM but with missing import errors turned into warnings instead.

    This is an edge case that rarely comes up in practice, since you usually never import things from a module that has no exports.

In addition to the breaking changes above, the following features are also included in this release:

  • Initial support for bundling with top-level await (#253)

    Top-level await is a feature that lets you use an await expression at the top level (outside of an async function). Here is an example:

    let promise = fetch('https://www.example.com/data')
    export let data = await promise.then(x => x.json())

    Top-level await only works in ECMAScript modules, and does not work in CommonJS modules. This means that you must use an import statement or an import() expression to import a module containing top-level await. You cannot use require() because it's synchronous while top-level await is asynchronous. There should be a descriptive error message when you try to do this.

    This initial release only has limited support for top-level await. It is only supported with the esm output format, but not with the iife or cjs output formats. In addition, the compilation is not correct in that two modules that both contain top-level await and that are siblings in the import graph will be evaluated in serial instead of in parallel. Full support for top-level await will come in a future release.

  • Add the ability to set sourceRoot in source maps (#1028)

    You can now use the --source-root= flag to set the sourceRoot field in source maps generated by esbuild. When a sourceRoot is present in a source map, all source paths are resolved relative to it. This is particularly useful when you are hosting compiled code on a server and you want to point the source files to a GitHub repo, such as what AMP does.

    Here is the description of sourceRoot from the source map specification:

    An optional source root, useful for relocating source files on a server or removing repeated values in the "sources" entry. This value is prepended to the individual entries in the "source" field. If the sources are not absolute URLs after prepending of the "sourceRoot", the sources are resolved relative to the SourceMap (like resolving script src in a html document).

    This feature was contributed by @jridgewell.

  • Allow plugins to return custom file watcher paths

    Currently esbuild's watch mode automatically watches all file system paths that are handled by esbuild itself, and also automatically watches the paths of files loaded by plugins when the paths are in the file namespace. The paths of files that plugins load in namespaces other than the file namespace are not automatically watched.

    Also, esbuild never automatically watches any file system paths that are consulted by the plugin during its processing, since esbuild is not aware of those paths. For example, this means that if a plugin calls require.resolve(), all of the various "does this file exist" checks that it does will not be watched automatically. So if one of those files is created in the future, esbuild's watch mode will not rebuild automatically even though the build is now outdated.

    To fix this problem, this release introduces the watchFiles and watchDirs properties on plugin return values. Plugins can specify these to add additional custom file system paths to esbuild's internal watch list. Paths in the watchFiles array cause esbuild to rebuild if the file contents change, and paths in the watchDirs array cause esbuild to rebuild if the set of directory entry names changes for that directory path.

    Note that watchDirs does not cause esbuild to rebuild if any of the contents of files inside that directory are changed. It also does not recursively traverse through subdirectories. It only watches the set of directory entry names (i.e. the output of the Unix ls command).