Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR for version 0.11.0 #1078

Merged
merged 6 commits into from
Mar 29, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,113 @@
# Changelog

## Breaking Changes

* Change how `require()` and `import()` of ESM works ([#667](https://github.com/evanw/esbuild/issues/667), [#706](https://github.com/evanw/esbuild/issues/706))

Previously if you call `require()` on an ESM file, or call `import()` on an ESM file with code splitting disabled, esbuild would convert the ESM file to CommonJS. For example, if you had the following input files:

```js
// cjs-file.js
console.log(require('./esm-file.js').foo)

// esm-file.js
export let foo = bar()
```

The previous bundling behavior would generate something like this:

```js
var require_esm_file = __commonJS((exports) => {
__markAsModule(exports);
__export(exports, {
foo: () => foo
});
var foo = bar();
});
console.log(require_esm_file().foo);
```

This behavior has been changed and esbuild now generates something like this instead:

```js
var esm_file_exports = {};
__export(esm_file_exports, {
foo: () => foo
});
var foo;
var init_esm_file = __esm(() => {
foo = bar();
});
console.log((init_esm_file(), esm_file_exports).foo);
```

The variables have been pulled out of the lazily-initialized closure and are accessible to the rest of the module's scope. Some benefits of this approach:

* If another file does `import {foo} from "./esm-file.js"`, it will just reference `foo` directly and will not pay the performance penalty or code size overhead of the dynamic property accesses that come with CommonJS-style exports. So this improves performance and reduces code size in some cases.

* This fixes a long-standing bug ([#706](https://github.com/evanw/esbuild/issues/706)) where entry point exports could be broken if the entry point is a target of a `require()` call and the output format was ESM. This happened because previously calling `require()` on an entry point converted it to CommonJS, which then meant it only had a single `default` export, and the exported variables were inside the CommonJS closure and inaccessible to an ESM-style `export {}` clause. Now calling `require()` on an entry point only causes it to be lazily-initialized but all exports are still in the module scope and can still be exported using a normal `export {}` clause.

* Now that this has been changed, `import()` of a module with top-level await ([#253](https://github.com/evanw/esbuild/issues/253)) is now allowed when code splitting is disabled. Previously this didn't work because `import()` with code splitting disabled was implemented by converting the module to CommonJS and using `Promise.resolve().then(() => require())`, but converting a module with top-level await to CommonJS is impossible because the CommonJS call signature must be synchronous. Now that this implemented using lazy initialization instead of CommonJS conversion, the closure wrapping the ESM file can now be `async` and the `import()` expression can be replaced by a call to the lazy initializer.

* Adding the ability for ESM files to be lazily-initialized is an important step toward additional future code splitting improvements including: manual chunk names ([#207](https://github.com/evanw/esbuild/issues/207)), correct import evaluation order ([#399](https://github.com/evanw/esbuild/issues/399)), and correct top-level await evaluation order ([#253](https://github.com/evanw/esbuild/issues/253)). These features all need to make use of deferred evaluation of ESM code.

In addition, calling `require()` on an ESM file now recursively wraps all transitive dependencies of that file instead of just wrapping that ESM file itself. This is an increase in the size of the generated code, but it is important for correctness ([#667](https://github.com/evanw/esbuild/issues/667)). Calling `require()` on a module means its evaluation order is determined at run-time, which means the evaluation order of all dependencies must also be determined at run-time. If you don't want the increase in code size, you should use an `import` statement instead of a `require()` call.

* Dynamic imports now use chunk names instead of entry names ([#1056](https://github.com/evanw/esbuild/issues/1056))

Previously the output paths of dynamic imports (files imported using the `import()` syntax) were determined by the `--entry-names=` setting. However, this can cause problems if you configure the `--entry-names=` setting to omit both `[dir]` and `[hash]` because then two dynamic imports with the same name will cause an output file name collision.

Now dynamic imports use the `--chunk-names=` setting instead, which is used for automatically-generated chunks. This setting is effectively required to include `[hash]` so dynamic import name collisions should now be avoided.

In addition, dynamic imports no longer affect the automatically-computed default value of `outbase`. By default `outbase` is computed to be the [lowest common ancestor](https://en.wikipedia.org/wiki/Lowest_common_ancestor) directory of all entry points. Previously dynamic imports were considered entry points in this calculation so adding a dynamic entry point could unexpectedly affect entry point output file paths. This issue has now been fixed.

* Allow custom output paths for individual entry points

By default, esbuild will automatically generate an output path for each entry point by computing the relative path from the `outbase` directory to the entry point path, and then joining that relative path to the `outdir` directory. The output path can be customized using `outpath`, but that only works for a single file. Sometimes you may need custom output paths while using multiple entry points. You can now do this by passing the entry points as a map instead of an array:

* CLI
```
esbuild out1=in1.js out2=in2.js --outdir=out
```

* JS
```js
esbuild.build({
entryPoints: {
out1: 'in1.js',
out2: 'in2.js',
},
outdir: 'out',
})
```

* Go

```go
api.Build(api.BuildOptions{
EntryPointsAdvanced: []api.EntryPoint{{
OutputPath: "out1",
InputPath: "in1.js",
}, {
OutputPath: "out2",
InputPath: "in2.js",
}},
Outdir: "out",
})
```

This will cause esbuild to generate the files `out/out1.js` and `out/out2.js` inside the output directory. These custom output paths are used as input for the `--entry-names=` path template setting, so you can use something like `--entry-names=[dir]/[name]-[hash]` to add an automatically-computed hash to each entry point while still using the custom output path.

* Derive entry point output paths from the original input ([#945](https://github.com/evanw/esbuild/issues/945))

Previously esbuild would determine the output path for an entry point by looking at the post-resolved path. For example, running `esbuild --bundle react --outdir=out` would generate the output path `out/index.js` because the input path `react` was resolved to `node_modules/react/index.js`. With this release, the output path is now determined by looking at the pre-resolved path. For example, For example, running `esbuild --bundle react --outdir=out` now generates the output path `out/react.js`. If you need to keep using the output path that esbuild previously generated with the old behavior, you can use the custom output path feature (described above).

* Use the `file` namespace for file entry points ([#791](https://github.com/evanw/esbuild/issues/791))

Plugins that contain an `onResolve` callback with the `file` filter don't apply to entry point paths because it's not clear that entry point paths are files. For example, you could potentially bundle an entry point of `https://www.example.com/file.js` with a HTTP plugin that automatically downloads data from the server at that URL. But this behavior can be unexpected for people writing plugins.

With this release, esbuild will do a quick check first to see if the entry point path exists on the file system before running plugins. If it exists as a file, the namespace will now be `file` for that entry point path. This only checks the exact entry point name and doesn't attempt to search for the file, so for example it won't handle cases where you pass a package path as an entry point or where you pass an entry point without an extension. Hopefully this should help improve this situation in the common case where the entry point is an exact path.

## Unreleased

* Warn about mutation of private methods ([#1067](https://github.com/evanw/esbuild/pull/1067))
11 changes: 11 additions & 0 deletions cmd/esbuild/service.go
Original file line number Diff line number Diff line change
@@ -352,12 +352,23 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
incremental := request["incremental"].(bool)
hasOnRebuild := request["hasOnRebuild"].(bool)
serveObj, isServe := request["serve"].(interface{})
entries := request["entries"].([]interface{})
flags := decodeStringArray(request["flags"].([]interface{}))

options, err := cli.ParseBuildOptions(flags)
options.AbsWorkingDir = request["absWorkingDir"].(string)
options.NodePaths = decodeStringArray(request["nodePaths"].([]interface{}))

for _, entry := range entries {
entry := entry.([]interface{})
key := entry[0].(string)
value := entry[1].(string)
options.EntryPointsAdvanced = append(options.EntryPointsAdvanced, api.EntryPoint{
OutputPath: key,
InputPath: value,
})
}

// Normally when "write" is true and there is no output file/directory then
// the output is written to stdout instead. However, we're currently using
// stdout as a communication channel and writing the build output to stdout
Loading