Skip to content

Releases: evanw/esbuild

v0.8.30

06 Jan 10:29
Compare
Choose a tag to compare
  • Fix @jsx and @jsxFrag comments without trailing spaces

    The --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 into switch (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 into a(), b();.

    • The comma operator is now extracted from certain expressions. This means (a, b) + c turns into a, b + c.

    • Minification now takes advantage of the left-associativity of certain operators. This means a && (b && c) turns into a && b && c.

    • Computed properties that are strings now become no longer computed. This means {['a']: b} turns into {a: b} and class { ['a'] = b } turns into class { a = b }.

    • Repeated if-jump statements are now merged. This means if (a) break; if (b) break; turns into if (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; becomes var a=0,b=2; when a and b 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

02 Jan 12:25
Compare
Choose a tag to compare
  • 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 points src/pages/home/index.ts and src/pages/about/index.ts, the outbase directory is src, and the output directory is out, the output directory will contain out/pages/home/index.js and out/pages/about/index.js.

    However, this means that the outbase directory is expected to contain all entry point files (even implicit entry point files from import() 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 to outbase 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 the outbase directory. So if the previous example was bundled with an outbase directory of temp, the output directory will contain out/_.._/pages/home/index.js and out/_.._/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 to a != 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 and return 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

31 Dec 11:17
Compare
Choose a tag to compare
  • 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 import Counter 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 in tsconfig.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 the importsNotUsedAsValues setting is active, you must use the TypeScript-specific import 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

29 Dec 12:15
Compare
Choose a tag to compare
  • 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 the sourcesContent 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 as DEBUG with literal expressions such as false. This is valuable because the substitution can then participate in constant folding and dead code elimination. For example, if (DEBUG) { ... } could become if (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

21 Dec 06:07
Compare
Choose a tag to compare
  • Ensure the current working directory remains unique per startService() call

    The change in version 0.8.24 to share service instances caused problems for code that calls process.chdir() before calling startService() to be able to get a service with a different working directory. With this release, calls to startService() 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 that Derived 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, then Base would be a dynamic run-time property access on exports.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 to const express = require("express") which means you can actually call express() 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 called esModuleInterop 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

20 Dec 11:41
Compare
Choose a tag to compare
  • 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 using transformSync 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

18 Dec 13:48
Compare
Choose a tag to compare
  • 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 from startService() to service.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 calls startService() multiple times. Previously each call to startService() 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 an export {...} from re-export of a CommonJS module does not include the CommonJS module if it has been marked as "sideEffect": false in its package.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 the esbuild binary that contains some extra debugging information.

v0.8.23

14 Dec 23:45
Compare
Choose a tag to compare
  • 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 for Uint8Array 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 and buildSync (#595)

    This speedup relies on the worker_threads module in node. However, when esbuild is used via node -r as in node -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

12 Dec 11:28
Compare
Choose a tag to compare
  • 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 and transformSync APIs (#590)

    Previously the buildSync and transformSync 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's worker_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 explicit stop() 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

08 Dec 06:03
Compare
Choose a tag to compare
  • 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 a node_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 stdin

    When the stdin feature is used with on-resolve plugins, the importer for any import paths in stdin is currently always set to <stdin>. The sourcefile 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 now sourcefile is used instead of <stdin> if present. In addition, if the stdin resolve directory is also specified the importer will be placed in the file namespace similar to a normal file.