Skip to content

Commit

Permalink
Switch to using a plugin hook
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Apr 5, 2019
1 parent b5319c7 commit ef4109e
Show file tree
Hide file tree
Showing 18 changed files with 130 additions and 61 deletions.
19 changes: 19 additions & 0 deletions docs/05-plugins.md
Expand Up @@ -202,6 +202,25 @@ resolveId(id) {
}
```

#### `resolveImportMetaUrl`
Type: `({chunkId: string, moduleId: string}) => string | null`<br>
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`
<br>
Expand Down
27 changes: 1 addition & 26 deletions docs/999-big-list-of-options.md
Expand Up @@ -614,39 +614,14 @@ 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)`<br>
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`<br>
CLI: `--indent`/`--no-indent`<br>
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 {
...,
Expand Down
4 changes: 3 additions & 1 deletion src/Chunk.ts
Expand Up @@ -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;
}
}
Expand Down
16 changes: 12 additions & 4 deletions 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';
Expand Down Expand Up @@ -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;

Expand All @@ -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<string | void>('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;
}
Expand Down
7 changes: 6 additions & 1 deletion src/rollup/types.d.ts
Expand Up @@ -191,6 +191,11 @@ export type ResolveDynamicImportHook = (
parentId: string
) => Promise<string | void> | string | void;

export type ResolveImportMetaUrlHook = (
this: PluginContext,
options: { chunkId: string; moduleId: string }
) => string | void;

export type AddonHook = string | ((this: PluginContext) => string | Promise<string>);

/**
Expand Down Expand Up @@ -244,6 +249,7 @@ export interface Plugin {
renderStart?: (this: PluginContext) => Promise<void> | void;
resolveDynamicImport?: ResolveDynamicImportHook;
resolveId?: ResolveIdHook;
resolveImportMetaUrl?: ResolveImportMetaUrlHook;
transform?: TransformHook;
/** @deprecated */
transformBundle?: TransformChunkHook;
Expand Down Expand Up @@ -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<string>);
Expand Down
1 change: 0 additions & 1 deletion src/utils/mergeOptions.ts
Expand Up @@ -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'),
Expand Down
55 changes: 37 additions & 18 deletions src/utils/pluginDriver.ts
Expand Up @@ -22,6 +22,7 @@ export interface PluginDriver {
hasLoadersOrTransforms: boolean;
getAssetFileName(assetId: string): string;
hookFirst<T = any>(hook: string, args?: any[], hookContext?: HookContext): Promise<T>;
hookFirstSync<T = any>(hook: string, args?: any[], hookContext?: HookContext): T;
hookParallel(hook: string, args?: any[], hookContext?: HookContext): Promise<void>;
hookReduceArg0<R = any, T = any>(
hook: string,
Expand Down Expand Up @@ -171,22 +172,22 @@ export function createPluginDriver(
function runHookSync<T>(
hookName: string,
args: any[],
pidx: number,
pluginIndex: number,
permitValues = false,
hookContext?: HookContext
): Promise<T> {
const plugin = plugins[pidx];
let context = pluginContexts[pidx];
): T {
const plugin = plugins[pluginIndex];
let context = pluginContexts[pluginIndex];
const hook = (<any>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 {
Expand All @@ -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);
Expand All @@ -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);
}
Expand All @@ -215,22 +216,22 @@ export function createPluginDriver(
function runHook<T>(
hookName: string,
args: any[],
pidx: number,
pluginIndex: number,
permitValues = false,
hookContext?: HookContext
): Promise<T> {
const plugin = plugins[pidx];
let context = pluginContexts[pidx];
const plugin = plugins[pluginIndex];
let context = pluginContexts[pluginIndex];
const hook = (<any>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()
Expand All @@ -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);
Expand All @@ -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);
});
Expand Down Expand Up @@ -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<void>[] = [];
Expand All @@ -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);
Expand All @@ -313,14 +325,16 @@ 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);
arg0 = reduce.call(pluginContexts[i], arg0, result, plugins[i]);
}
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);
Expand Down Expand Up @@ -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.`
};
}
29 changes: 21 additions & 8 deletions 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;
}
}
}
]
}
};
4 changes: 4 additions & 0 deletions 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');

});
4 changes: 4 additions & 0 deletions 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');
4 changes: 4 additions & 0 deletions 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');
4 changes: 4 additions & 0 deletions 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');

}());
Expand Up @@ -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');

}
Expand Down

0 comments on commit ef4109e

Please sign in to comment.