diff --git a/docs/05-plugins.md b/docs/05-plugins.md index a7deaaeaf42..53ce17a6068 100644 --- a/docs/05-plugins.md +++ b/docs/05-plugins.md @@ -202,6 +202,25 @@ resolveId(id) { } ``` +#### `resolveImportMetaUrl` +Type: `({chunkId: string, moduleId: string}) => string | null`
+Kind: `sync, first` + +Allows to customize how Rollup handles `import.meta.url`. In ES modules, `import.meta.url` returns the URL of the current module, e.g. `http://server.net/bundle.js` for browsers or `file:///path/to/bundle.js` in Node. + +By default for formats other than ES modules, Rollup replaces `import.meta.url` with code that attempts to match this behaviour by returning the dynamic URL of the current chunk. Note that all formats except CommonJS and UMD assume that they run in a browser environment where `URL` and `document` are available. + + This behaviour can be changed by returning a replacement for `import.meta.url`. For example, the following code will resolve `import.meta.url` using the relative path of the original module to the current working directory and again resolve this path against the base URL of the current document at runtime: + +```javascript +// rollup.config.js +resolveImportMetaUrl({moduleId}) { + return `new URL('${path.relative(process.cwd(), moduleId)}', document.baseURI).href`; +} +``` + +Note that since this hook has access to the filename of the current chunk, its return value will not be considered when generating the hash of this chunk. + #### `transform` Type: `(code: string, id: string) => string | { code: string, map?: string | SourceMap, ast? : ESTree.Program } | null`
diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index 6bff411623b..38736b1fcf7 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -614,31 +614,6 @@ Default: `true` Whether to `Object.freeze()` namespace import objects (i.e. `import * as namespaceImportObject from...`) that are accessed dynamically. -#### output.importMetaUrl -Type: `((chunkId: string, moduleId: string) => string)`
- -This allows the user to configure how Rollup handles `import.meta.url`. In ES modules, `import.meta.url` returns the URL of the current module, e.g. `http://server.net/bundle.js` for browsers or `file:///path/to/bundle.js` in Node. - -By default for formats other than ES modules, Rollup replaces `import.meta.url` with code that attempts to match this behaviour by returning the dynamic URL of the current chunk. Note that all formats except CommonJS and UMD assume that they run in a browser environment where `URL` and `document` are available. - - This behaviour can be customized by supplying a function, which will replace `import.meta.url` for all formats: - -```javascript -// rollup.config.js -export default { - ..., - output: { - ..., - - // this will use the original module id when resolving import.meta.url - importMetaUrl(chunkId, moduleId) { - return `"${moduleId}"`; - } - } -}; - -``` - #### output.indent Type: `boolean | string`
CLI: `--indent`/`--no-indent`
@@ -646,7 +621,7 @@ Default: `true` The indent string to use, for formats that require code to be indented (`amd`, `iife`, `umd`, `system`). Can also be `false` (no indent), or `true` (the default – auto-indent) -```javascript +```js // rollup.config.js export default { ..., diff --git a/src/Chunk.ts b/src/Chunk.ts index 144dc064997..f150f22d74b 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -800,7 +800,9 @@ export default class Chunk { const module = this.orderedModules[i]; const code = this.renderedModuleSources[i]; for (const importMeta of module.importMetas) { - if (importMeta.renderFinalMechanism(code, this.id, options.format, options.importMetaUrl)) { + if ( + importMeta.renderFinalMechanism(code, this.id, options.format, this.graph.pluginDriver) + ) { usesMechanism = true; } } diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index b49f5f732a1..783eec7d077 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -1,5 +1,6 @@ import MagicString from 'magic-string'; import { dirname, normalize, relative } from '../../utils/path'; +import { PluginDriver } from '../../utils/pluginDriver'; import { RenderOptions } from '../../utils/renderHelpers'; import Identifier from './Identifier'; import Literal from './Literal'; @@ -73,7 +74,7 @@ export default class MetaProperty extends NodeBase { code: MagicString, chunkId: string, format: string, - renderImportMetaUrl: ((chunkId: string, moduleId: string) => string) | void + pluginDriver: PluginDriver ): boolean { if (!this.included || !(this.parent instanceof MemberExpression)) return false; @@ -94,9 +95,16 @@ export default class MetaProperty extends NodeBase { } if (importMetaProperty === 'url') { - const getImportMetaUrl = renderImportMetaUrl || importMetaUrlMechanisms[format]; - if (getImportMetaUrl) { - code.overwrite(parent.start, parent.end, getImportMetaUrl(chunkId, this.context.module.id)); + const replacement = + pluginDriver.hookFirstSync('resolveImportMetaUrl', [ + { + chunkId, + moduleId: this.context.module.id + } + ]) || + (importMetaUrlMechanisms[format] && importMetaUrlMechanisms[format](chunkId)); + if (typeof replacement === 'string') { + code.overwrite(parent.start, parent.end, replacement); } return true; } diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 27ee1fe93fb..e92adb7248a 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -191,6 +191,11 @@ export type ResolveDynamicImportHook = ( parentId: string ) => Promise | string | void; +export type ResolveImportMetaUrlHook = ( + this: PluginContext, + options: { chunkId: string; moduleId: string } +) => string | void; + export type AddonHook = string | ((this: PluginContext) => string | Promise); /** @@ -244,6 +249,7 @@ export interface Plugin { renderStart?: (this: PluginContext) => Promise | void; resolveDynamicImport?: ResolveDynamicImportHook; resolveId?: ResolveIdHook; + resolveImportMetaUrl?: ResolveImportMetaUrlHook; transform?: TransformHook; /** @deprecated */ transformBundle?: TransformChunkHook; @@ -324,7 +330,6 @@ export interface OutputOptions { format?: ModuleFormat; freeze?: boolean; globals?: GlobalsOption; - importMetaUrl?: (chunkId: string, moduleId: string) => string; indent?: boolean; interop?: boolean; intro?: string | (() => string | Promise); diff --git a/src/utils/mergeOptions.ts b/src/utils/mergeOptions.ts index cbb595fb1cc..00455edc6e9 100644 --- a/src/utils/mergeOptions.ts +++ b/src/utils/mergeOptions.ts @@ -258,7 +258,6 @@ function getOutputOptions( format: format === 'esm' ? 'es' : format, freeze: getOption('freeze', true), globals: getOption('globals'), - importMetaUrl: getOption('importMetaUrl'), indent: getOption('indent', true), interop: getOption('interop', true), intro: getOption('intro'), diff --git a/src/utils/pluginDriver.ts b/src/utils/pluginDriver.ts index be80d7e3cff..2c32d9403d5 100644 --- a/src/utils/pluginDriver.ts +++ b/src/utils/pluginDriver.ts @@ -22,6 +22,7 @@ export interface PluginDriver { hasLoadersOrTransforms: boolean; getAssetFileName(assetId: string): string; hookFirst(hook: string, args?: any[], hookContext?: HookContext): Promise; + hookFirstSync(hook: string, args?: any[], hookContext?: HookContext): T; hookParallel(hook: string, args?: any[], hookContext?: HookContext): Promise; hookReduceArg0( hook: string, @@ -171,22 +172,22 @@ export function createPluginDriver( function runHookSync( hookName: string, args: any[], - pidx: number, + pluginIndex: number, permitValues = false, hookContext?: HookContext - ): Promise { - const plugin = plugins[pidx]; - let context = pluginContexts[pidx]; + ): T { + const plugin = plugins[pluginIndex]; + let context = pluginContexts[pluginIndex]; const hook = (plugin)[hookName]; if (!hook) return; const deprecatedHookNewName = deprecatedHookNames[hookName]; if (deprecatedHookNewName) - context.warn(hookDeprecationWarning(hookName, deprecatedHookNewName, plugin, pidx)); + context.warn(hookDeprecationWarning(hookName, deprecatedHookNewName, plugin, pluginIndex)); if (hookContext) { context = hookContext(context, plugin); - if (!context || context === pluginContexts[pidx]) + if (!context || context === pluginContexts[pluginIndex]) throw new Error('Internal Rollup error: hookContext must return a new context object.'); } try { @@ -196,7 +197,7 @@ export function createPluginDriver( error({ code: 'INVALID_PLUGIN_HOOK', message: `Error running plugin hook ${hookName} for ${plugin.name || - `Plugin at position ${pidx + 1}`}, expected a function hook.` + `Plugin at position ${pluginIndex + 1}`}, expected a function hook.` }); } return hook.apply(context, args); @@ -206,7 +207,7 @@ export function createPluginDriver( if (err.code) err.pluginCode = err.code; err.code = 'PLUGIN_ERROR'; } - err.plugin = plugin.name || `Plugin at position ${pidx + 1}`; + err.plugin = plugin.name || `Plugin at position ${pluginIndex + 1}`; err.hook = hookName; error(err); } @@ -215,22 +216,22 @@ export function createPluginDriver( function runHook( hookName: string, args: any[], - pidx: number, + pluginIndex: number, permitValues = false, hookContext?: HookContext ): Promise { - const plugin = plugins[pidx]; - let context = pluginContexts[pidx]; + const plugin = plugins[pluginIndex]; + let context = pluginContexts[pluginIndex]; const hook = (plugin)[hookName]; if (!hook) return; const deprecatedHookNewName = deprecatedHookNames[hookName]; if (deprecatedHookNewName) - context.warn(hookDeprecationWarning(hookName, deprecatedHookNewName, plugin, pidx)); + context.warn(hookDeprecationWarning(hookName, deprecatedHookNewName, plugin, pluginIndex)); if (hookContext) { context = hookContext(context, plugin); - if (!context || context === pluginContexts[pidx]) + if (!context || context === pluginContexts[pluginIndex]) throw new Error('Internal Rollup error: hookContext must return a new context object.'); } return Promise.resolve() @@ -241,7 +242,7 @@ export function createPluginDriver( error({ code: 'INVALID_PLUGIN_HOOK', message: `Error running plugin hook ${hookName} for ${plugin.name || - `Plugin at position ${pidx + 1}`}, expected a function hook.` + `Plugin at position ${pluginIndex + 1}`}, expected a function hook.` }); } return hook.apply(context, args); @@ -252,7 +253,7 @@ export function createPluginDriver( if (err.code) err.pluginCode = err.code; err.code = 'PLUGIN_ERROR'; } - err.plugin = plugin.name || `Plugin at position ${pidx + 1}`; + err.plugin = plugin.name || `Plugin at position ${pluginIndex + 1}`; err.hook = hookName; error(err); }); @@ -289,6 +290,16 @@ export function createPluginDriver( } return promise; }, + + // chains synchronously, first non-null result stops and returns + hookFirstSync(name, args?, hookContext?) { + for (let i = 0; i < plugins.length; i++) { + const result = runHookSync(name, args, i, false, hookContext); + if (result != null) return result as any; + } + return null; + }, + // parallel, ignores returns hookParallel(name, args, hookContext) { const promises: Promise[] = []; @@ -299,6 +310,7 @@ export function createPluginDriver( } return Promise.all(promises).then(() => {}); }, + // chains, reduces returns of type R, to type T, handling the reduced value as the first hook argument hookReduceArg0(name, [arg0, ...args], reduce, hookContext) { let promise = Promise.resolve(arg0); @@ -313,7 +325,8 @@ export function createPluginDriver( } return promise; }, - // chains, synchronically reduces returns of type R, to type T, handling the reduced value as the first hook argument + + // chains synchronously, reduces returns of type R, to type T, handling the reduced value as the first hook argument hookReduceArg0Sync(name, [arg0, ...args], reduce, hookContext) { for (let i = 0; i < plugins.length; i++) { const result = runHookSync(name, [arg0, ...args], i, false, hookContext); @@ -321,6 +334,7 @@ export function createPluginDriver( } return arg0; }, + // chains, reduces returns of type R, to type T, handling the reduced value separately. permits hooks as values. hookReduceValue(name, initial, args, reduce, hookContext) { let promise = Promise.resolve(initial); @@ -431,10 +445,15 @@ const uncacheablePlugin: (pluginName: string) => PluginCache = pluginName => ({ } }); -function hookDeprecationWarning(name: string, newName: string, plugin: Plugin, pidx: number) { +function hookDeprecationWarning( + name: string, + newName: string, + plugin: Plugin, + pluginIndex: number +) { return { code: name.toUpperCase() + '_HOOK_DEPRECATED', message: `The ${name} hook used by plugin ${plugin.name || - `at position ${pidx + 1}`} is deprecated. The ${newName} hook should be used instead.` + `at position ${pluginIndex + 1}`} is deprecated. The ${newName} hook should be used instead.` }; } diff --git a/test/form/samples/configure-import-meta-url/_config.js b/test/form/samples/configure-import-meta-url/_config.js index 9352526c873..2e6888486a8 100644 --- a/test/form/samples/configure-import-meta-url/_config.js +++ b/test/form/samples/configure-import-meta-url/_config.js @@ -1,14 +1,27 @@ module.exports = { description: 'allows to configure import.meta.url', options: { - output: { - importMetaUrl(chunkId, moduleId) { - return `'${chunkId}/${moduleId - .replace(/\\/g, '/') - .split('/') - .slice(-2) - .join('/')}'`; + plugins: [ + { + resolveImportMetaUrl({ chunkId, moduleId }) { + if (!moduleId.endsWith('resolved.js')) { + return `'${chunkId}/${moduleId + .replace(/\\/g, '/') + .split('/') + .slice(-2) + .join('/')}'`; + } + return null; + } + }, + { + resolveImportMetaUrl({ moduleId }) { + if (!moduleId.endsWith('unresolved.js')) { + return `'resolved'`; + } + return null; + } } - } + ] } }; diff --git a/test/form/samples/configure-import-meta-url/_expected/amd.js b/test/form/samples/configure-import-meta-url/_expected/amd.js index a05f2631fd5..aa3bae2b300 100644 --- a/test/form/samples/configure-import-meta-url/_expected/amd.js +++ b/test/form/samples/configure-import-meta-url/_expected/amd.js @@ -1,5 +1,9 @@ define(['module'], function (module) { 'use strict'; + console.log('resolved'); + + console.log(new URL(module.uri, document.baseURI).href); + console.log('amd.js/configure-import-meta-url/main.js'); }); diff --git a/test/form/samples/configure-import-meta-url/_expected/cjs.js b/test/form/samples/configure-import-meta-url/_expected/cjs.js index a73c255c05e..40cf325fe1f 100644 --- a/test/form/samples/configure-import-meta-url/_expected/cjs.js +++ b/test/form/samples/configure-import-meta-url/_expected/cjs.js @@ -1,3 +1,7 @@ 'use strict'; +console.log('resolved'); + +console.log((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('cjs.js', document.baseURI).href))); + console.log('cjs.js/configure-import-meta-url/main.js'); diff --git a/test/form/samples/configure-import-meta-url/_expected/es.js b/test/form/samples/configure-import-meta-url/_expected/es.js index a71014898e3..9c60ac1b8c3 100644 --- a/test/form/samples/configure-import-meta-url/_expected/es.js +++ b/test/form/samples/configure-import-meta-url/_expected/es.js @@ -1 +1,5 @@ +console.log('resolved'); + +console.log(import.meta.url); + console.log('es.js/configure-import-meta-url/main.js'); diff --git a/test/form/samples/configure-import-meta-url/_expected/iife.js b/test/form/samples/configure-import-meta-url/_expected/iife.js index d4aff63d317..885ab9f917e 100644 --- a/test/form/samples/configure-import-meta-url/_expected/iife.js +++ b/test/form/samples/configure-import-meta-url/_expected/iife.js @@ -1,6 +1,10 @@ (function () { 'use strict'; + console.log('resolved'); + + console.log((document.currentScript && document.currentScript.src || new URL('iife.js', document.baseURI).href)); + console.log('iife.js/configure-import-meta-url/main.js'); }()); diff --git a/test/form/samples/configure-import-meta-url/_expected/system.js b/test/form/samples/configure-import-meta-url/_expected/system.js index f0105ea8de8..f6fea59b8e9 100644 --- a/test/form/samples/configure-import-meta-url/_expected/system.js +++ b/test/form/samples/configure-import-meta-url/_expected/system.js @@ -3,6 +3,10 @@ System.register([], function (exports, module) { return { execute: function () { + console.log('resolved'); + + console.log(module.meta.url); + console.log('system.js/configure-import-meta-url/main.js'); } diff --git a/test/form/samples/configure-import-meta-url/_expected/umd.js b/test/form/samples/configure-import-meta-url/_expected/umd.js index fb16fd659d9..4fac39ab8db 100644 --- a/test/form/samples/configure-import-meta-url/_expected/umd.js +++ b/test/form/samples/configure-import-meta-url/_expected/umd.js @@ -3,6 +3,10 @@ factory(); }(function () { 'use strict'; + console.log('resolved'); + + console.log((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('umd.js', document.baseURI).href))); + console.log('umd.js/configure-import-meta-url/main.js'); })); diff --git a/test/form/samples/configure-import-meta-url/main.js b/test/form/samples/configure-import-meta-url/main.js index d9536a69b3f..31d82d63ea7 100644 --- a/test/form/samples/configure-import-meta-url/main.js +++ b/test/form/samples/configure-import-meta-url/main.js @@ -1 +1,4 @@ +import './resolved'; +import './unresolved'; + console.log(import.meta.url); diff --git a/test/form/samples/configure-import-meta-url/resolved.js b/test/form/samples/configure-import-meta-url/resolved.js new file mode 100644 index 00000000000..d9536a69b3f --- /dev/null +++ b/test/form/samples/configure-import-meta-url/resolved.js @@ -0,0 +1 @@ +console.log(import.meta.url); diff --git a/test/form/samples/configure-import-meta-url/unresolved.js b/test/form/samples/configure-import-meta-url/unresolved.js new file mode 100644 index 00000000000..d9536a69b3f --- /dev/null +++ b/test/form/samples/configure-import-meta-url/unresolved.js @@ -0,0 +1 @@ +console.log(import.meta.url); diff --git a/test/misc/optionList.js b/test/misc/optionList.js index 57b7a6cb097..ab34fa25c8c 100644 --- a/test/misc/optionList.js +++ b/test/misc/optionList.js @@ -1,3 +1,3 @@ exports.input = 'acorn, acornInjectPlugins, cache, chunkGroupingSize, context, experimentalCacheExpiry, experimentalOptimizeChunks, experimentalTopLevelAwait, external, inlineDynamicImports, input, manualChunks, moduleContext, onwarn, perf, plugins, preserveModules, preserveSymlinks, shimMissingExports, treeshake, watch'; -exports.flags = 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, chunkGroupingSize, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalOptimizeChunks, experimentalTopLevelAwait, exports, extend, external, f, file, footer, format, freeze, g, globals, h, i, importMetaUrl, indent, inlineDynamicImports, input, interop, intro, m, manualChunks, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, paths, perf, plugins, preferConst, preserveModules, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, strict, treeshake, v, w, watch'; -exports.output = 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, file, footer, format, freeze, globals, importMetaUrl, indent, interop, intro, name, namespaceToStringTag, noConflict, outro, paths, preferConst, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict'; +exports.flags = 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, chunkGroupingSize, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalOptimizeChunks, experimentalTopLevelAwait, exports, extend, external, f, file, footer, format, freeze, g, globals, h, i, indent, inlineDynamicImports, input, interop, intro, m, manualChunks, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, paths, perf, plugins, preferConst, preserveModules, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, strict, treeshake, v, w, watch'; +exports.output = 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, file, footer, format, freeze, globals, indent, interop, intro, name, namespaceToStringTag, noConflict, outro, paths, preferConst, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict';