Skip to content

Releases: evanw/esbuild

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.

v0.8.20

06 Dec 09:05
Compare
Choose a tag to compare
  • Fix an edge case with class body initialization

    When bundling, top-level class statements are rewritten to variable declarations initialized to a class expression. This avoids a severe performance pitfall in Safari when there are a large number of class statements. However, this transformation was done incorrectly if a class contained a static field that references the class name in its own initializer:

    class Foo {
      static foo = new Foo
    }

    In that specific case, the transformed code could crash when run because the class name is not yet initialized when the static field initializer is run. Only JavaScript code was affected. TypeScript code was not affected. This release fixes this bug.

  • Remove more types of statements as dead code (#580)

    This change improves dead-code elimination in the case where unused statements follow an unconditional jump, such as a return:

    if (true) return
    if (something) thisIsDeadCode()

    These unused statements are removed in more cases than in the previous release. Some statements may still be kept that contain hoisted symbols (var and function statements) because they could potentially impact the code before the conditional jump.

v0.8.19

05 Dec 07:57
Compare
Choose a tag to compare
  • Handle non-ambiguous multi-path re-exports (#568)

    Wildcard re-exports using the export * from 'path' syntax can potentially result in name collisions that cause an export name to be ambiguous. For example, the following code would result in an ambiguous export if both a.js and b.js export a symbol with the same name:

    export * from './a.js'
    export * from './b.js'

    Ambiguous exports have two consequences. First, any ambiguous names are silently excluded from the set of exported names. If you use an import * as wildcard import, the excluded names will not be present. Second, attempting to explicitly import an ambiguous name using an import {} from import clause will result in a module instantiation error.

    This release fixes a bug where esbuild could in certain cases consider a name ambiguous when it actually isn't. Specifically this happens with longer chains of mixed wildcard and named re-exports. Here is one such case:

    // entry.js
    import {x, y} from './not-ambiguous.js'
    console.log(x, y)
    // /not-ambiguous.js
    export * from './a.js'
    export * from './b.js'
    // /a.js
    export * from './c.js'
    // /b.js
    export {x} from './c.js'
    // /c.js
    export let x = 1, y = 2

    Previously bundling entry.js with esbuild would incorrectly generate an error about an ambiguous x export. Now this case builds successfully without an error.

  • Omit warnings about non-string paths in await import() inside a try block (#574)

    Bundling code that uses require() or import() with a non-string path currently generates a warning, because the target of that import will not be included in the bundle. This is helpful to warn about because other bundlers handle this case differently (e.g. Webpack bundles the entire directory tree and emulates a file system lookup) so existing code may expect the target of the import to be bundled.

    You can avoid the warning with esbuild by surrounding the call to require() with a try block. The thinking is that if there is a surrounding try block, presumably the code is expecting the require() call to possibly fail and is prepared to handle the error. However, there is currently no way to avoid the warning for import() expressions. This release introduces an analogous behavior for import() expressions. You can now avoid the warning with esbuild if you use await import() and surround it with a try block.

v0.8.18

04 Dec 06:17
Compare
Choose a tag to compare
  • Fix a bug with certain complex optional chains (#573)

    The ?. optional chaining operator only runs the right side of the operator if the left side is undefined, otherwise it returns undefined. This operator can be applied to both property accesses and function calls, and these can be combined into long chains of operators. These expressions must be transformed to a chain of ?: operators if the ?. operator isn't supported in the configured target environment. However, esbuild had a bug where an optional call of an optional property with a further property access afterward didn't preserve the value of this for the call. This bug has been fixed.

  • Fix a renaming bug with external imports

    There was a possibility of a cross-module name collision while bundling in a certain edge case. Specifically, when multiple files both contained an import statement to an external module and then both of those files were imported using require. For example:

    // index.js
    console.log(require('./a.js'), require('./b.js'))
    // a.js
    export {exists} from 'fs'
    // b.js
    export {exists} from 'fs'

    In this case the files a.js and b.js are converted to CommonJS format so they can be imported using require:

    // a.js
    import {exists} from "fs";
    var require_a = __commonJS((exports) => {
      __export(exports, {
        exists: () => exists
      });
    });
    
    // b.js
    import {exists} from "fs";
    var require_b = __commonJS((exports) => {
      __export(exports, {
        exists: () => exists
      });
    });
    
    // index.js
    console.log(require_a(), require_b());

    However, the exists symbol has been duplicated without being renamed. This is will result in a syntax error at run-time. The reason this happens is that the statements in the files a.js and b.js are placed in a nested scope because they are inside the CommonJS closure. The import statements were extracted outside the closure but the symbols they declared were incorrectly not added to the outer scope. This problem has been fixed, and this edge case should no longer result in name collisions.

v0.8.17

29 Nov 12:58
Compare
Choose a tag to compare
  • Get esbuild working on the Apple M1 chip via Rosetta 2 (#564)

    The Go compiler toolchain does not yet support the new Apple M1 chip. Go version 1.15 is currently in a feature freeze period so support will be added in the next version, Go 1.16, which will be released in February.

    This release changes the install script to install the executable for macOS x64 on macOS arm64 too. Doing this should still work because of the executable translation layer built into macOS. This change was contributed by @sod.

v0.8.16

26 Nov 19:43
Compare
Choose a tag to compare
  • Improve TypeScript type definitions (#559)

    The return value of the build API has some optional fields that are undefined unless certain arguments are present. That meant you had to use the ! null assertion operator to avoid a type error if you have the TypeScript strictNullChecks setting enabled in your project. This release adds additional type information so that if the relevant arguments are present, the TypeScript compiler can tell that these optional fields on the return value will never be undefined. This change was contributed by @lukeed.

  • Omit a warning about require.main when targeting CommonJS (#560)

    A common pattern in code that's intended to be run in node is to check if require.main === module. That will be true if the current file is being run from the command line but false if the current file is being run because some other code called require() on it. Previously esbuild generated a warning about an unexpected use of require. Now this warning is no longer generated for require.main when the output format is cjs.

  • Warn about defining process.env.NODE_ENV as an identifier (#466)

    The define feature can be used to replace an expression with either a JSON literal or an identifier. Forgetting to put quotes around a string turns it into an identifier, which is a common mistake. This release introduces a warning when you define process.env.NODE_ENV as an identifier instead of a string. It's very common to use define to replace process.env.NODE_ENV with either "production" or "development" and sometimes people accidentally replace it with production or development instead. This is worth warning about because otherwise there would be no indication that something is wrong until the code crashes when run.

  • Allow starting a local server at a specific host address (#563)

    By default, esbuild's local HTTP server is only available on the internal loopback address. This is deliberate behavior for security reasons, since the local network environment may not be trusted. However, it can be useful to run the server on a different address when developing with esbuild inside of a virtual machine/docker container or to request development assets from a remote testing device on the same network at a different IP address. With this release, you can now optionally specify the host in addition to the port:

    esbuild --serve=192.168.0.1:8000
    
    esbuild.serve({
      host: '192.168.0.1',
      port: 8000,
    }, {
      ...
    })
    server, err := api.Serve(api.ServeOptions{
      Host: "192.168.0.1",
      Port: 8000,
    }, api.BuildOptions{
      ...
    })

    This change was contributed by @jamalc.

v0.8.15

25 Nov 09:43
Compare
Choose a tag to compare
  • Allow paths without baseUrl in tsconfig.json

    This feature was recently released in TypeScript 4.1. The paths feature in tsconfig.json allows you to do custom import path rewriting. For example, you can map paths matching @namespace/* to the path ./namespace/src/* relative to the tsconfig.json file. Previously using the paths feature required you to additionally specify baseUrl so that the compiler could know which directory the path aliases were supposed to be relative to.

    However, specifying baseUrl has the potentially-problematic side effect of causing all import paths to be looked up relative to the baseUrl directory, which could potentially cause package paths to accidentally be redirected to non-package files. Specifying baseUrl also causes Visual Studio Code's auto-import feature to generate paths relative to the baseUrl directory instead of relative to the directory containing the current file. There is more information about the problems this causes here: microsoft/TypeScript#31869.

    With TypeScript 4.1, you can now omit baseUrl when using paths. When you do this, it as if you had written "baseUrl": "." instead for the purpose of the paths feature, but the baseUrl value is not actually set and does not affect path resolution. These tsconfig.json files are now supported by esbuild.

  • Fix evaluation order issue with import cycles and CommonJS-style output formats (#542)

    Previously entry points involved in an import cycle could cause evaluation order issues if the output format was iife or cjs instead of esm. This happened because this edge case was handled by treating the entry point file as a CommonJS file, which extracted the code into a CommonJS wrapper. Here's an example:

    Input files:

    // index.js
    import { test } from './lib'
    export function fn() { return 42 }
    if (test() !== 42) throw 'failure'
    // lib.js
    import { fn } from './index'
    export let test = fn

    Previous output (problematic):

    // index.js
    var require_esbuild = __commonJS((exports) => {
      __export(exports, {
        fn: () => fn2
      });
      function fn2() {
        return 42;
      }
      if (test() !== 42)
        throw "failure";
    });
    
    // lib.js
    var index = __toModule(require_esbuild());
    var test = index.fn;
    module.exports = require_esbuild();

    This approach changed the evaluation order because the CommonJS wrapper conflates both binding and evaluation. Binding and evaluation need to be separated to correctly handle this edge case. This edge case is now handled by inlining what would have been the contents of the CommonJS wrapper into the entry point location itself.

    Current output (fixed):

    // index.js
    __export(exports, {
      fn: () => fn
    });
    
    // lib.js
    var test = fn;
    
    // index.js
    function fn() {
      return 42;
    }
    if (test() !== 42)
      throw "failure";

v0.8.14

24 Nov 06:51
Compare
Choose a tag to compare
  • Fix a concurrency bug caused by an error message change (#556)

    An improvement to the error message for path resolution was introduced in version 0.8.12. It detects when a relative path is being interpreted as a package path because you forgot to start the path with ./:

     > src/posts/index.js: error: Could not resolve "PostCreate" (use "./PostCreate" to import "src/posts/PostCreate.js")
        2 │ import PostCreate from 'PostCreate';
          ╵                        ~~~~~~~~~~~~
    

    This is implemented by re-running path resolution for package path resolution failures as a relative path instead. Unfortunately, this second path resolution operation wasn't guarded by a mutex and could result in concurrency bugs. This issue only occurs when path resolution fails. It is fixed in this release.

v0.8.13

23 Nov 19:02
Compare
Choose a tag to compare
  • Assigning to a const symbol is now an error when bundling

    This change was made because esbuild may need to change a const symbol into a non-constant symbol in certain situations. One situation is when the "avoid TDZ" option is enabled. Another situation is some potential upcoming changes to lazily-evaluate certain modules for code splitting purposes. Making this an error gives esbuild the freedom to do these code transformations without potentially causing problems where constants are mutated. This has already been a warning for a while so code that does this should already have been obvious. This warning was made an error in a patch release because the expectation is that no real code relies on this behavior outside of conformance tests.

  • Fix for the --keep-names option and anonymous lowered classes

    This release fixes an issue where names were not preserved for anonymous classes that contained newer JavaScript syntax when targeting an older version of JavaScript. This was because that causes the class expression to be transformed into a sequence expression, which was then not recognized as a class expression. For example, the class did not have the name foo in the code below when the target was set to es6:

    let foo = class {
      #privateMethod() {}
    }

    The name property of this class object is now foo.

  • Fix captured class names when class name is re-assigned

    This fixes a corner case with class lowering to better match the JavaScript specification. In JavaScript, the body of a class statement contains an implicit constant symbol with the same name as the symbol of the class statement itself. Lowering certain class features such as private methods means moving them outside the class body, in which case the contents of the private method are no longer within the scope of the constant symbol. This can lead to a behavior change if the class is later re-assigned:

    class Foo {
      static test() { return this.#method() }
      static #method() { return Foo }
    }
    let old = Foo
    Foo = class Bar {}
    console.log(old.test() === old) // This should be true

    Previously this would print false when transformed to ES6 by esbuild. This now prints true. The current transformed output looks like this:

    var _method, method_fn;
    const Foo2 = class {
      static test() {
        return __privateMethod(this, _method, method_fn).call(this);
      }
    };
    let Foo = Foo2;
    _method = new WeakSet();
    method_fn = function() {
      return Foo2;
    };
    _method.add(Foo);
    let old = Foo;
    Foo = class Bar {
    };
    console.log(old.test() === old);
  • The --allow-tdz option is now always applied during bundling

    This option turns top-level let, const, and class statements into var statements to work around some severe performance issues in the JavaScript run-time environment in Safari. Previously you had to explicitly enable this option. Now this behavior will always happen, and there is no way to turn it off. This means the --allow-tdz option is now meaningless and no longer does anything. It will be removed in a future release.

  • When bundling and minifying, const is now converted into let

    This was done because it's semantically equivalent but shorter. It's a valid transformation because assignment to a const symbol is now a compile-time error when bundling, so changing const to let should now not affect run-time behavior.