Skip to content

Commit

Permalink
Generalize hook to handle all occurrences of import.meta
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Apr 10, 2019
1 parent fc21284 commit 057ecda
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 53 deletions.
25 changes: 14 additions & 11 deletions docs/05-plugins.md
Expand Up @@ -195,27 +195,30 @@ If you return an object, then it is possible to resolve an import to a different

```js
resolveId(id) {
if (id === 'my-dependency') {
return {id: 'my-dependency-develop', external: true};
}
return null;
if (id === 'my-dependency') {
return {id: 'my-dependency-develop', external: true};
}
return null;
}
```

#### `resolveImportMetaUrl`
Type: `({chunkId: string, moduleId: string, format: string}) => string | null`<br>
#### `resolveImportMeta`
Type: `(property: string | null, {chunkId: string, moduleId: string, format: 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.
Allows to customize how Rollup handles `import.meta` and `import.meta.someProperty`, in particular `import.meta.url`. In ES modules, `import.meta` is an object and `import.meta.url` contains 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.
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. For other properties, `import.meta.someProperty` is replaced with `undefined` while `import.meta` is replaced with an object containing a `url` property.

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:
This behaviour can be changed—also for ES modules—via this hook. For each occurrence of `import.meta<.someProperty>`, this hook is called with the name of the property or `null` if `import.meta` is accessed directly. 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`;
resolveImportMeta(property, {moduleId}) {
if (property === 'url') {
return `new URL('${path.relative(process.cwd(), moduleId)}', document.baseURI).href`;
}
return null;
}
```

Expand Down
52 changes: 23 additions & 29 deletions src/ast/nodes/MetaProperty.ts
@@ -1,9 +1,7 @@
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';
import MemberExpression from './MemberExpression';
import * as NodeType from './NodeType';
import { NodeBase } from './shared/Node';
Expand Down Expand Up @@ -35,60 +33,56 @@ const relUrlMechanisms: Record<string, (relPath: string) => string> = {
export default class MetaProperty extends NodeBase {
meta: Identifier;
property: Identifier;
rendered: boolean;
type: NodeType.tMetaProperty;

initialise() {
if (this.meta.name === 'import') {
this.rendered = false;
this.context.addImportMeta(this);
}
this.included = false;
}

render(code: MagicString, options: RenderOptions) {
if (this.meta.name === 'import') this.rendered = true;
super.render(code, options);
}

renderFinalMechanism(
code: MagicString,
chunkId: string,
format: string,
pluginDriver: PluginDriver
): boolean {
if (!this.included || !(this.parent instanceof MemberExpression)) return false;

if (!this.included) return false;
const parent = this.parent;

let importMetaProperty: string;
if (parent.property instanceof Identifier) importMetaProperty = parent.property.name;
else if (parent.property instanceof Literal && typeof parent.property.value === 'string')
importMetaProperty = parent.property.value;
else return false;
const importMetaProperty =
parent instanceof MemberExpression && typeof parent.propertyKey === 'string'
? parent.propertyKey
: null;

// support import.meta.ROLLUP_ASSET_URL_[ID]
if (importMetaProperty.startsWith('ROLLUP_ASSET_URL_')) {
if (importMetaProperty && importMetaProperty.startsWith('ROLLUP_ASSET_URL_')) {
const assetFileName = this.context.getAssetFileName(importMetaProperty.substr(17));
const relPath = normalize(relative(dirname(chunkId), assetFileName));
code.overwrite(parent.start, parent.end, relUrlMechanisms[format](relPath));
code.overwrite(
(parent as MemberExpression).start,
(parent as MemberExpression).end,
relUrlMechanisms[format](relPath)
);
return true;
}

if (importMetaProperty === 'url') {
const replacement = pluginDriver.hookFirstSync<string | void>('resolveImportMetaUrl', [
{
chunkId,
format,
moduleId: this.context.module.id
}
]);
if (typeof replacement === 'string') {
const replacement = pluginDriver.hookFirstSync<string | void>('resolveImportMeta', [
importMetaProperty,
{
chunkId,
format,
moduleId: this.context.module.id
}
]);
if (typeof replacement === 'string') {
if (parent instanceof MemberExpression) {
code.overwrite(parent.start, parent.end, replacement);
} else {
code.overwrite(this.start, this.end, replacement);
}
return true;
}

return false;
}
}
5 changes: 3 additions & 2 deletions src/rollup/types.d.ts
Expand Up @@ -191,8 +191,9 @@ export type ResolveDynamicImportHook = (
parentId: string
) => Promise<string | void> | string | void;

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

Expand Down Expand Up @@ -249,7 +250,7 @@ export interface Plugin {
renderStart?: (this: PluginContext) => Promise<void> | void;
resolveDynamicImport?: ResolveDynamicImportHook;
resolveId?: ResolveIdHook;
resolveImportMetaUrl?: ResolveImportMetaUrlHook;
resolveImportMeta?: ResolveImportMetaHook;
transform?: TransformHook;
/** @deprecated */
transformBundle?: TransformChunkHook;
Expand Down
7 changes: 5 additions & 2 deletions src/utils/defaultPlugin.ts
Expand Up @@ -14,8 +14,11 @@ export function getRollupDefaultPlugin(options: InputOptions): Plugin {
if (typeof specifier === 'string' && !this.isExternal(specifier, parentId, false))
return <Promise<string>>this.resolveId(specifier, parentId);
},
resolveImportMetaUrl({ chunkId, format }) {
return importMetaUrlMechanisms[format] && importMetaUrlMechanisms[format](chunkId);
resolveImportMeta(prop, { chunkId, format }) {
const mechanism = importMetaUrlMechanisms[format] && importMetaUrlMechanisms[format](chunkId);
if (mechanism) {
return prop === null ? `({ url: ${mechanism} })` : prop === 'url' ? mechanism : 'undefined';
}
}
};
}
Expand Down
6 changes: 3 additions & 3 deletions test/form/samples/configure-import-meta-url/_config.js
Expand Up @@ -3,9 +3,9 @@ module.exports = {
options: {
plugins: [
{
resolveImportMetaUrl({ chunkId, moduleId }) {
resolveImportMeta(prop, { chunkId, moduleId }) {
if (!moduleId.endsWith('resolved.js')) {
return `'${chunkId}/${moduleId
return `'${prop}=${chunkId}:${moduleId
.replace(/\\/g, '/')
.split('/')
.slice(-2)
Expand All @@ -15,7 +15,7 @@ module.exports = {
}
},
{
resolveImportMetaUrl({ moduleId }) {
resolveImportMeta(prop, { moduleId }) {
if (!moduleId.endsWith('unresolved.js')) {
return `'resolved'`;
}
Expand Down
8 changes: 7 additions & 1 deletion test/form/samples/configure-import-meta-url/_expected/amd.js
@@ -1,9 +1,15 @@
define(['module'], function (module) { 'use strict';

console.log('resolved');
console.log('resolved');
console.log('resolved');

console.log(new URL(module.uri, document.baseURI).href);
console.log(undefined);
console.log(({ url: new URL(module.uri, document.baseURI).href }));

console.log('amd.js/configure-import-meta-url/main.js');
console.log('url=amd.js:configure-import-meta-url/main.js');
console.log('privateProp=amd.js:configure-import-meta-url/main.js');
console.log('null=amd.js:configure-import-meta-url/main.js');

});
8 changes: 7 additions & 1 deletion test/form/samples/configure-import-meta-url/_expected/cjs.js
@@ -1,7 +1,13 @@
'use strict';

console.log('resolved');
console.log('resolved');
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(undefined);
console.log(({ url: (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');
console.log('url=cjs.js:configure-import-meta-url/main.js');
console.log('privateProp=cjs.js:configure-import-meta-url/main.js');
console.log('null=cjs.js:configure-import-meta-url/main.js');
8 changes: 7 additions & 1 deletion test/form/samples/configure-import-meta-url/_expected/es.js
@@ -1,5 +1,11 @@
console.log('resolved');
console.log('resolved');
console.log('resolved');

console.log(import.meta.url);
console.log(import.meta.privateProp);
console.log(import.meta);

console.log('es.js/configure-import-meta-url/main.js');
console.log('url=es.js:configure-import-meta-url/main.js');
console.log('privateProp=es.js:configure-import-meta-url/main.js');
console.log('null=es.js:configure-import-meta-url/main.js');
@@ -1,10 +1,16 @@
(function () {
'use strict';

console.log('resolved');
console.log('resolved');
console.log('resolved');

console.log((document.currentScript && document.currentScript.src || new URL('iife.js', document.baseURI).href));
console.log(undefined);
console.log(({ url: (document.currentScript && document.currentScript.src || new URL('iife.js', document.baseURI).href) }));

console.log('iife.js/configure-import-meta-url/main.js');
console.log('url=iife.js:configure-import-meta-url/main.js');
console.log('privateProp=iife.js:configure-import-meta-url/main.js');
console.log('null=iife.js:configure-import-meta-url/main.js');

}());
Expand Up @@ -4,10 +4,16 @@ System.register([], function (exports, module) {
execute: function () {

console.log('resolved');
console.log('resolved');
console.log('resolved');

console.log(module.meta.url);
console.log(undefined);
console.log(({ url: module.meta.url }));

console.log('system.js/configure-import-meta-url/main.js');
console.log('url=system.js:configure-import-meta-url/main.js');
console.log('privateProp=system.js:configure-import-meta-url/main.js');
console.log('null=system.js:configure-import-meta-url/main.js');

}
};
Expand Down
8 changes: 7 additions & 1 deletion test/form/samples/configure-import-meta-url/_expected/umd.js
Expand Up @@ -4,9 +4,15 @@
}(function () { 'use strict';

console.log('resolved');
console.log('resolved');
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(undefined);
console.log(({ url: (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');
console.log('url=umd.js:configure-import-meta-url/main.js');
console.log('privateProp=umd.js:configure-import-meta-url/main.js');
console.log('null=umd.js:configure-import-meta-url/main.js');

}));
2 changes: 2 additions & 0 deletions test/form/samples/configure-import-meta-url/main.js
Expand Up @@ -2,3 +2,5 @@ import './resolved';
import './unresolved';

console.log(import.meta.url);
console.log(import.meta.privateProp);
console.log(import.meta);
2 changes: 2 additions & 0 deletions test/form/samples/configure-import-meta-url/resolved.js
@@ -1 +1,3 @@
console.log(import.meta.url);
console.log(import.meta.privateProp);
console.log(import.meta);
2 changes: 2 additions & 0 deletions test/form/samples/configure-import-meta-url/unresolved.js
@@ -1 +1,3 @@
console.log(import.meta.url);
console.log(import.meta.privateProp);
console.log(import.meta);

0 comments on commit 057ecda

Please sign in to comment.