diff --git a/CHANGELOG.md b/CHANGELOG.md index afae0583da2..9d728a9c0aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,40 @@ This release fixes an issue where esbuild could potentially crash sometimes with a concurrent map write when using injected files and entry points that were neither relative nor absolute paths. This was an edge case where esbuild's low-level file subsystem was being used without being behind a mutex lock. This regression was likely introduced in version 0.8.21. The cause of the crash has been fixed. +* Provide `kind` to `onResolve` plugins ([#879](https://github.com/evanw/esbuild/issues/879)) + + Plugins that add `onResolve` callbacks now have access to the `kind` parameter which tells you what kind of import is being resolved. It will be one of the following values: + + * `"entry-point"` in JS (`api.ResolveEntryPoint` in Go) + + An entry point provided by the user + + * `"import-statement"` in JS (`api.ResolveJSImportStatement` in Go) + + A JavaScript `import` or `export` statement + + * `"require-call"` in JS (`api.ResolveJSRequireCall` in Go) + + A JavaScript call to `require(...)` with a string argument + + * `"dynamic-import"` in JS (`api.ResolveJSDynamicImport` in Go) + + A JavaScript `import(...)` expression with a string argument + + * `"require-resolve"` in JS (`api.ResolveJSRequireResolve` in Go) + + A JavaScript call to `require.resolve(...)` with a string argument + + * `"import-rule"` in JS (`api.ResolveCSSImportRule` in Go) + + A CSS `@import` rule + + * `"url-token"` in JS (`api.ResolveCSSURLToken` in Go) + + A CSS `url(...)` token + + These values are pretty much identical to the `kind` field in the JSON metadata file. + ## 0.8.51 * The stderr log format now contains line numbers after file names ([#865](https://github.com/evanw/esbuild/issues/865)) diff --git a/cmd/esbuild/service.go b/cmd/esbuild/service.go index 3d3aebec297..6b411474819 100644 --- a/cmd/esbuild/service.go +++ b/cmd/esbuild/service.go @@ -617,6 +617,31 @@ func (service *serviceType) convertPlugins(key int, jsPlugins interface{}) ([]ap return result, nil } + var kind string + switch args.Kind { + case api.ResolveEntryPoint: + kind = "entry-point" + + // JS + case api.ResolveJSImportStatement: + kind = "import-statement" + case api.ResolveJSRequireCall: + kind = "require-call" + case api.ResolveJSDynamicImport: + kind = "dynamic-import" + case api.ResolveJSRequireResolve: + kind = "require-resolve" + + // CSS + case api.ResolveCSSImportRule: + kind = "import-rule" + case api.ResolveCSSURLToken: + kind = "url-token" + + default: + panic("Internal error") + } + response := service.sendRequest(map[string]interface{}{ "command": "resolve", "key": key, @@ -625,6 +650,7 @@ func (service *serviceType) convertPlugins(key int, jsPlugins interface{}) ([]ap "importer": args.Importer, "namespace": args.Namespace, "resolveDir": args.ResolveDir, + "kind": kind, "pluginData": args.PluginData, }).(map[string]interface{}) diff --git a/internal/ast/ast.go b/internal/ast/ast.go index ca3fdad6866..23721264550 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -9,8 +9,11 @@ import "github.com/evanw/esbuild/internal/logger" type ImportKind uint8 const ( + // An entry point provided by the user + ImportEntryPoint ImportKind = iota + // An ES6 import or re-export statement - ImportStmt ImportKind = iota + ImportStmt // A call to "require()" ImportRequire @@ -26,9 +29,6 @@ const ( // A CSS "url(...)" token ImportURL - - // An entry point provided by the user - ImportEntryPoint ) func (kind ImportKind) StringForMetafile() string { diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index e9251623c1a..fba7d0dd8e0 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -638,6 +638,7 @@ func runOnResolvePlugins( resolverArgs := config.OnResolveArgs{ Path: path, ResolveDir: absResolveDir, + Kind: kind, PluginData: pluginData, } applyPath := logger.Path{Text: path} diff --git a/internal/config/config.go b/internal/config/config.go index bfa820f8f04..bc3a69a59e0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "regexp" "sync" + "github.com/evanw/esbuild/internal/ast" "github.com/evanw/esbuild/internal/compat" "github.com/evanw/esbuild/internal/js_ast" "github.com/evanw/esbuild/internal/logger" @@ -321,6 +322,7 @@ type OnResolveArgs struct { Path string Importer logger.Path ResolveDir string + Kind ast.ImportKind PluginData interface{} } diff --git a/lib/common.ts b/lib/common.ts index 0ffeae572a4..8a5f3fb49a7 100644 --- a/lib/common.ts +++ b/lib/common.ts @@ -637,6 +637,7 @@ export function createChannel(streamIn: StreamIn): StreamOut { importer: request.importer, namespace: request.namespace, resolveDir: request.resolveDir, + kind: request.kind, pluginData: stash.load(request.pluginData), }); diff --git a/lib/stdio_protocol.ts b/lib/stdio_protocol.ts index 7b2d8bdedb8..12a79476590 100644 --- a/lib/stdio_protocol.ts +++ b/lib/stdio_protocol.ts @@ -120,6 +120,7 @@ export interface OnResolveRequest { importer: string; namespace: string; resolveDir: string; + kind: string; pluginData: number; } diff --git a/lib/types.ts b/lib/types.ts index cc148dc241b..ad836a86b83 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -192,9 +192,23 @@ export interface OnResolveArgs { importer: string; namespace: string; resolveDir: string; + kind: ResolveKind; pluginData: any; } +export type ResolveKind = + | 'entry-point' + + // JS + | 'import-statement' + | 'require-call' + | 'dynamic-import' + | 'require-resolve' + + // CSS + | 'import-rule' + | 'url-token' + export interface OnResolveResult { pluginName?: string; diff --git a/pkg/api/api.go b/pkg/api/api.go index c7fd42cf922..69fe3e1ac90 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -407,6 +407,7 @@ type OnResolveArgs struct { Importer string Namespace string ResolveDir string + Kind ResolveKind PluginData interface{} } @@ -444,3 +445,15 @@ type OnLoadResult struct { Loader Loader PluginData interface{} } + +type ResolveKind uint8 + +const ( + ResolveEntryPoint ResolveKind = iota + ResolveJSImportStatement + ResolveJSRequireCall + ResolveJSDynamicImport + ResolveJSRequireResolve + ResolveCSSImportRule + ResolveCSSURLToken +) diff --git a/pkg/api/api_impl.go b/pkg/api/api_impl.go index 14242e60fe9..35ec8d26736 100644 --- a/pkg/api/api_impl.go +++ b/pkg/api/api_impl.go @@ -13,6 +13,7 @@ import ( "sync/atomic" "time" + "github.com/evanw/esbuild/internal/ast" "github.com/evanw/esbuild/internal/bundler" "github.com/evanw/esbuild/internal/cache" "github.com/evanw/esbuild/internal/compat" @@ -1117,11 +1118,32 @@ func (impl *pluginImpl) OnResolve(options OnResolveOptions, callback func(OnReso Filter: filter, Namespace: options.Namespace, Callback: func(args config.OnResolveArgs) (result config.OnResolveResult) { + var kind ResolveKind + switch args.Kind { + case ast.ImportEntryPoint: + kind = ResolveEntryPoint + case ast.ImportStmt: + kind = ResolveJSImportStatement + case ast.ImportRequire: + kind = ResolveJSRequireCall + case ast.ImportDynamic: + kind = ResolveJSDynamicImport + case ast.ImportRequireResolve: + kind = ResolveJSRequireResolve + case ast.ImportAt: + kind = ResolveCSSImportRule + case ast.ImportURL: + kind = ResolveCSSURLToken + default: + panic("Internal error") + } + response, err := callback(OnResolveArgs{ Path: args.Path, Importer: args.Importer.Text, Namespace: args.Importer.Namespace, ResolveDir: args.ResolveDir, + Kind: kind, PluginData: args.PluginData, }) result.PluginName = response.PluginName diff --git a/scripts/plugin-tests.js b/scripts/plugin-tests.js index 01e5067400e..97767ce7a79 100644 --- a/scripts/plugin-tests.js +++ b/scripts/plugin-tests.js @@ -1544,6 +1544,184 @@ let pluginTests = { } } }, + + async resolveKindEntryPoint({ esbuild }) { + let resolveKind = '' + try { + await esbuild.build({ + entryPoints: ['entry'], + bundle: true, + write: false, + logLevel: 'silent', + plugins: [{ + name: 'plugin', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + resolveKind = args.kind + }) + }, + }], + }) + } catch (e) { + } + assert.strictEqual(resolveKind, 'entry-point') + }, + + async resolveKindImportStmt({ esbuild }) { + let resolveKind = '' + try { + await esbuild.build({ + entryPoints: ['entry'], + bundle: true, + write: false, + logLevel: 'silent', + plugins: [{ + name: 'plugin', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (args.importer === '') return { path: args.path, namespace: 'ns' } + else resolveKind = args.kind + }) + build.onLoad({ filter: /.*/, namespace: 'ns' }, () => { + return { contents: `import 'test'` } + }) + }, + }], + }) + } catch (e) { + } + assert.strictEqual(resolveKind, 'import-statement') + }, + + async resolveKindRequireCall({ esbuild }) { + let resolveKind = '' + try { + await esbuild.build({ + entryPoints: ['entry'], + bundle: true, + write: false, + logLevel: 'silent', + plugins: [{ + name: 'plugin', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (args.importer === '') return { path: args.path, namespace: 'ns' } + else resolveKind = args.kind + }) + build.onLoad({ filter: /.*/, namespace: 'ns' }, () => { + return { contents: `require('test')` } + }) + }, + }], + }) + } catch (e) { + } + assert.strictEqual(resolveKind, 'require-call') + }, + + async resolveKindDynamicImport({ esbuild }) { + let resolveKind = '' + try { + await esbuild.build({ + entryPoints: ['entry'], + bundle: true, + write: false, + logLevel: 'silent', + plugins: [{ + name: 'plugin', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (args.importer === '') return { path: args.path, namespace: 'ns' } + else resolveKind = args.kind + }) + build.onLoad({ filter: /.*/, namespace: 'ns' }, () => { + return { contents: `import('test')` } + }) + }, + }], + }) + } catch (e) { + } + assert.strictEqual(resolveKind, 'dynamic-import') + }, + + async resolveKindRequireResolve({ esbuild }) { + let resolveKind = '' + try { + await esbuild.build({ + entryPoints: ['entry'], + bundle: true, + write: false, + logLevel: 'silent', + plugins: [{ + name: 'plugin', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (args.importer === '') return { path: args.path, namespace: 'ns' } + else resolveKind = args.kind + }) + build.onLoad({ filter: /.*/, namespace: 'ns' }, () => { + return { contents: `require.resolve('test')` } + }) + }, + }], + }) + } catch (e) { + } + assert.strictEqual(resolveKind, 'require-resolve') + }, + + async resolveKindAtImport({ esbuild }) { + let resolveKind = '' + try { + await esbuild.build({ + entryPoints: ['entry'], + bundle: true, + write: false, + logLevel: 'silent', + plugins: [{ + name: 'plugin', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (args.importer === '') return { path: args.path, namespace: 'ns' } + else resolveKind = args.kind + }) + build.onLoad({ filter: /.*/, namespace: 'ns' }, () => { + return { contents: `@import "test";`, loader: 'css' } + }) + }, + }], + }) + } catch (e) { + } + assert.strictEqual(resolveKind, 'import-rule') + }, + + async resolveKindImportStmt({ esbuild }) { + let resolveKind = '' + try { + await esbuild.build({ + entryPoints: ['entry'], + bundle: true, + write: false, + logLevel: 'silent', + plugins: [{ + name: 'plugin', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (args.importer === '') return { path: args.path, namespace: 'ns' } + else resolveKind = args.kind + }) + build.onLoad({ filter: /.*/, namespace: 'ns' }, () => { + return { contents: `div { background: url('test') }`, loader: 'css' } + }) + }, + }], + }) + } catch (e) { + } + assert.strictEqual(resolveKind, 'url-token') + }, } async function main() {