Skip to content

Releases: evanw/esbuild

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.

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.