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';