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

Batch of several breaking changes #2508

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
# Changelog

## Unreleased

**This release deliberately contains backwards-incompatible changes.** To avoid automatically picking up releases like this, you should either be pinning the exact version of `esbuild` in your `package.json` file (recommended) or be using a version range syntax that only accepts patch upgrades such as `~0.16.0`. See npm's documentation about [semver](https://docs.npmjs.com/cli/v6/using-npm/semver/) for more information.

* Interpret and rewrite `new URL(..., import.meta.url)` expressions when bundling ([#312](https://github.com/evanw/esbuild/issues/312), [#795](https://github.com/evanw/esbuild/issues/795), [#2470](https://github.com/evanw/esbuild/pull/2470))

Some other bundlers have adopted a convention where the syntax `new URL('./file.js', import.meta.url)` causes `file.js` to be included in the current bundling operation as an additional entry point. The `'./file.js'` string is rewritten in the bundler's output to point to the resulting generated file for that entry point in the output directory (relative to the generated file containing the `new URL(...)` syntax). This is somewhat similar to how `import('./file.js')` works except that this reference just returns a `URL` object without importing the module. That lets you pass the URL of a module to other APIs such as `new Worker(...)` that take a script URL as input.

Previously this pattern didn't work at all with esbuild, but it will now work in esbuild starting with this release. To use it you must ensure that bundling is enabled, that the output format is set to `esm`, and that code splitting is enabled (so you need to use `--bundle --format=esm --splitting`). In addition, the path must be a relative path (i.e. it must start with either `./` or `../`). Here's what using this feature looks like:

```ts
const url = new URL('./worker.ts', import.meta.url)
const worker = new Worker(url, { type: 'module' })

worker.onmessage = (event: MessageEvent) => {
console.log(event.data)
}
```

* Handle import paths containing wildcards ([#56](https://github.com/evanw/esbuild/issues/56), [#700](https://github.com/evanw/esbuild/issues/700), [#875](https://github.com/evanw/esbuild/issues/875), [#976](https://github.com/evanw/esbuild/issues/976), [#2221](https://github.com/evanw/esbuild/issues/2221), [#2515](https://github.com/evanw/esbuild/issues/2515))

This release introduces wildcards in import paths in two places:

* **Entry points**

You can now pass a string containing glob-style wildcards such as `./src/*.ts` as an entry point and esbuild will search the file system for files that match the pattern. This can be used to easily pass esbuild all files with a certain extension on the command line in a cross-platform way. Previously you had to rely on the shell to perform glob expansion, but that is obviously shell-dependent and didn't work at all on Windows. Note that to use this feature on the command line you will have to quote the pattern so it's passed verbatim to esbuild without any expansion by the shell. Here's an example:

```sh
esbuild --minify "./src/*.ts" --outdir=out
```

Specifically the `*` character will match any character except for the `/` character, and the `/**/` character sequence will match a path separator followed by zero or more path elements. Other wildcard operators found in glob patterns such as `?` and `[...]` are not supported.

* **Run-time import paths**

Import paths that are evaluated at run-time can now be bundled in certain limited situations. The import path expression must be a form of string concatenation and must start with either `./` or `../`. Each non-string expression in the string concatenation chain becomes a wildcard. The `*` wildcard is chosen unless the previous character is a `/`, in which case the `/**/*` character sequence is used.

```js
// These two forms are equivalent
const json1 = await import('./data/' + kind + '.json')
const json2 = await import(`./data/${kind}.json`)
```

This feature works with `require(...)`, `import(...)`, and `new URL(..., import.meta.url)` because these can all accept run-time expressions. It does not work with `import` and `export` statements because these cannot accept run-time expressions.

## 0.16.1

This is a hotfix for the previous release.
Expand Down
36 changes: 20 additions & 16 deletions cmd/esbuild/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -647,22 +647,24 @@ func resolveKindToString(kind api.ResolveKind) string {
case api.ResolveEntryPoint:
return "entry-point"

// CSS
case api.ResolveCSSImportRule:
return "import-rule"
case api.ResolveCSSURLToken:
return "url-token"

// JS
case api.ResolveJSDynamicImport:
return "dynamic-import"
case api.ResolveJSImportStatement:
return "import-statement"
case api.ResolveJSNewURL:
return "new-url"
case api.ResolveJSRequireCall:
return "require-call"
case api.ResolveJSDynamicImport:
return "dynamic-import"
case api.ResolveJSRequireResolve:
return "require-resolve"

// CSS
case api.ResolveCSSImportRule:
return "import-rule"
case api.ResolveCSSURLToken:
return "url-token"

default:
panic("Internal error")
}
Expand All @@ -673,21 +675,23 @@ func stringToResolveKind(kind string) (api.ResolveKind, bool) {
case "entry-point":
return api.ResolveEntryPoint, true

// CSS
case "import-rule":
return api.ResolveCSSImportRule, true
case "url-token":
return api.ResolveCSSURLToken, true

// JS
case "dynamic-import":
return api.ResolveJSDynamicImport, true
case "import-statement":
return api.ResolveJSImportStatement, true
case "new-url":
return api.ResolveJSNewURL, true
case "require-call":
return api.ResolveJSRequireCall, true
case "dynamic-import":
return api.ResolveJSDynamicImport, true
case "require-resolve":
return api.ResolveJSRequireResolve, true

// CSS
case "import-rule":
return api.ResolveCSSImportRule, true
case "url-token":
return api.ResolveCSSURLToken, true
}

return api.ResolveNone, false
Expand Down
18 changes: 15 additions & 3 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const (
// A call to "require.resolve()"
ImportRequireResolve

// "new URL('path', import.meta.url)" with a string argument
ImportNewURL

// A CSS "@import" rule
ImportAt

Expand All @@ -47,6 +50,8 @@ func (kind ImportKind) StringForMetafile() string {
return "dynamic-import"
case ImportRequireResolve:
return "require-resolve"
case ImportNewURL:
return "new-url"
case ImportAt, ImportAtConditional:
return "import-rule"
case ImportURL:
Expand Down Expand Up @@ -123,9 +128,10 @@ func (flags ImportRecordFlags) Has(flag ImportRecordFlags) bool {
}

type ImportRecord struct {
Assertions *[]AssertEntry
Path logger.Path
Range logger.Range
Assertions *[]AssertEntry
GlobPattern *GlobPattern
Path logger.Path
Range logger.Range

// If the "HandlesImportErrors" flag is present, then this is the location
// of the error handler. This is used for error reporting.
Expand Down Expand Up @@ -160,6 +166,12 @@ func FindAssertion(assertions []AssertEntry, name string) *AssertEntry {
return nil
}

type GlobPattern struct {
Parts []helpers.GlobPart
ExportAlias string
Kind ImportKind
}

// This stores a 32-bit index where the zero value is an invalid index. This is
// a better alternative to storing the index as a pointer since that has the
// same properties but takes up more space and costs an extra pointer traversal.
Expand Down
Loading