diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md
index 0ae2b48fb7f..d7c7e6634be 100644
--- a/docs/05-plugin-development.md
+++ b/docs/05-plugin-development.md
@@ -150,7 +150,7 @@ In case a dynamic import is not passed a string as argument, this hook gets acce
Note that the return value of this hook will not be passed to `resolveId` afterwards; if you need access to the static resolution algorithm, you can use [`this.resolve(source, importer)`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null) on the plugin context.
#### `resolveId`
-Type: `(source: string, importer: string) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null}`
+Type: `(source: string, importer: string | undefined) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null}`
Kind: `async, first`
Previous Hook: [`buildStart`](guide/en/#buildstart) if we are resolving an entry point, [`transform`](guide/en/#transform) if we are resolving an import, or as fallback for [`resolveDynamicImport`](guide/en/#resolvedynamicimport). Additionally this hook can be triggered during the build phase from plugin hooks by calling [`this.emitFile`](guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string) to emit an entry point or at any time by calling [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null) to manually resolve an id.
Next Hook: [`load`](guide/en/#load) if the resolved id that has not yet been loaded, otherwise [`buildEnd`](guide/en/#buildend).
@@ -449,6 +449,7 @@ Emits a new file that is included in the build output and returns a `referenceId
{
type: 'chunk',
id: string,
+ importer?: string,
name?: string,
fileName?: string
}
@@ -468,7 +469,9 @@ You can reference the URL of an emitted file in any code returned by a [`load`](
The generated code that replaces `import.meta.ROLLUP_FILE_URL_referenceId` can be customized via the [`resolveFileUrl`](guide/en/#resolvefileurl) plugin hook. You can also use [`this.getFileName(referenceId)`](guide/en/#thisgetfilenamereferenceid-string--string) to determine the file name as soon as it is available
-If the `type` is *`chunk`*, then this emits a new chunk with the given module id as entry point. This will not result in duplicate modules in the graph, instead if necessary, existing chunks will be split or a facade chunk with reexports will be created. Chunks with a specified `fileName` will always generate separate chunks while other emitted chunks may be deduplicated with existing chunks even if the `name` does not match. If such a chunk is not deduplicated, the [`output.chunkFileNames`](guide/en/#outputchunkfilenames) name pattern will be used.
+If the `type` is *`chunk`*, then this emits a new chunk with the given module `id` as entry point. To resolve it, the `id` will be passed through all [`resolveId`](guide/en/#resolveid) hooks, just like regular entry points. If an `importer` is provided, this acts as the second parameter of `resolveId` and is important to properly resolve relative paths. If it is not provided, paths will be resolved relative to the current working directory.
+
+This will not result in duplicate modules in the graph, instead if necessary, existing chunks will be split or a facade chunk with reexports will be created. Chunks with a specified `fileName` will always generate separate chunks while other emitted chunks may be deduplicated with existing chunks even if the `name` does not match. If such a chunk is not deduplicated, the [`output.chunkFileNames`](guide/en/#outputchunkfilenames) name pattern will be used.
If the `type` is *`asset`*, then this emits an arbitrary new file with the given `source` as content. It is possible to defer setting the `source` via [`this.setAssetSource(assetReferenceId, source)`](guide/en/#thissetassetsourceassetreferenceid-string-source-string--uint8array--void) to a later time to be able to reference a file during the build phase while setting the source separately for each output during the generate phase. Assets with a specified `fileName` will always generate separate files while other emitted assets may be deduplicated with existing assets if they have the same source even if the `name` does not match. If such an asset is not deduplicated, the [`output.assetFileNames`](guide/en/#outputassetfilenames) name pattern will be used.
@@ -518,7 +521,7 @@ or converted into an Array via `Array.from(this.moduleIds)`.
Use Rollup's internal acorn instance to parse code to an AST.
-#### `this.resolve(source: string, importer: string, options?: {skipSelf: boolean}) => Promise<{id: string, external: boolean} | null>`
+#### `this.resolve(source: string, importer?: string, options?: {skipSelf: boolean}) => Promise<{id: string, external: boolean} | null>`
Resolve imports to module ids (i.e. file names) using the same plugins that Rollup uses, and determine if an import should be external. If `null` is returned, the import could not be resolved by Rollup or any plugin but was not explicitly marked as external by the user.
If you pass `skipSelf: true`, then the `resolveId` hook of the plugin from which `this.resolve` is called will be skipped when resolving.
@@ -563,9 +566,9 @@ The `position` argument is a character index where the warning was raised. If pr
- `this.getChunkFileName(chunkReferenceId: string) => string` - _**Use [`this.getFileName`](guide/en/#thisgetfilenamereferenceid-string--string)**_ - Get the file name of an emitted chunk. The file name will be relative to `outputOptions.dir`.
-- `this.isExternal(id: string, importer: string, isResolved: boolean) => boolean` - _**Use [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null)**_ - Determine if a given module ID is external when imported by `importer`. When `isResolved` is false, Rollup will try to resolve the id before testing if it is external.
+- `this.isExternal(id: string, importer: string | undefined, isResolved: boolean) => boolean` - _**Use [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null)**_ - Determine if a given module ID is external when imported by `importer`. When `isResolved` is false, Rollup will try to resolve the id before testing if it is external.
-- `this.resolveId(source: string, importer: string) => Promise` - _**Use [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null)**_ - Resolve imports to module ids (i.e. file names) using the same plugins that Rollup uses. Returns `null` if an id cannot be resolved.
+- `this.resolveId(source: string, importer?: string) => Promise` - _**Use [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean--promiseid-string-external-boolean--null)**_ - Resolve imports to module ids (i.e. file names) using the same plugins that Rollup uses. Returns `null` if an id cannot be resolved.
### File URLs
diff --git a/src/Graph.ts b/src/Graph.ts
index 1ebc7228b07..1458189e0d9 100644
--- a/src/Graph.ts
+++ b/src/Graph.ts
@@ -32,14 +32,15 @@ function normalizeEntryModules(
entryModules: string | string[] | Record
): UnresolvedModule[] {
if (typeof entryModules === 'string') {
- return [{ fileName: null, name: null, id: entryModules }];
+ return [{ fileName: null, name: null, id: entryModules, importer: undefined }];
}
if (Array.isArray(entryModules)) {
- return entryModules.map(id => ({ fileName: null, name: null, id }));
+ return entryModules.map(id => ({ fileName: null, name: null, id, importer: undefined }));
}
return Object.keys(entryModules).map(name => ({
fileName: null,
id: entryModules[name],
+ importer: undefined,
name
}));
}
diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts
index 5af0388bea9..7740914fbc0 100644
--- a/src/ModuleLoader.ts
+++ b/src/ModuleLoader.ts
@@ -35,11 +35,16 @@ import transform from './utils/transform';
export interface UnresolvedModule {
fileName: string | null;
id: string;
+ importer: string | undefined;
name: string | null;
}
-function normalizeRelativeExternalId(importer: string, source: string) {
- return isRelative(source) ? resolve(importer, '..', source) : source;
+function normalizeRelativeExternalId(source: string, importer: string | undefined) {
+ return isRelative(source)
+ ? importer
+ ? resolve(importer, '..', source)
+ : resolve(source)
+ : source;
}
function getIdMatcher>(
@@ -133,8 +138,8 @@ export class ModuleLoader {
const firstEntryModuleIndex = this.nextEntryModuleIndex;
this.nextEntryModuleIndex += unresolvedEntryModules.length;
const loadNewEntryModulesPromise = Promise.all(
- unresolvedEntryModules.map(({ fileName, id, name }) =>
- this.loadEntryModule(id, true).then(module => {
+ unresolvedEntryModules.map(({ fileName, id, name, importer }) =>
+ this.loadEntryModule(id, true, importer).then(module => {
if (fileName !== null) {
module.chunkFileNames.add(fileName);
} else if (name !== null) {
@@ -183,7 +188,7 @@ export class ModuleLoader {
}
}
const loadNewManualChunkModulesPromise = Promise.all(
- unresolvedManualChunks.map(({ id }) => this.loadEntryModule(id, false))
+ unresolvedManualChunks.map(({ id }) => this.loadEntryModule(id, false, undefined))
).then(manualChunkModules => {
for (let index = 0; index < manualChunkModules.length; index++) {
this.addModuleToManualChunk(unresolvedManualChunks[index].alias, manualChunkModules[index]);
@@ -195,7 +200,7 @@ export class ModuleLoader {
async resolveId(
source: string,
- importer: string,
+ importer: string | undefined,
skip?: number | null
): Promise {
return this.normalizeResolveIdResult(
@@ -413,8 +418,12 @@ export class ModuleLoader {
return resolvedId;
}
- private loadEntryModule = (unresolvedId: string, isEntry: boolean): Promise =>
- this.pluginDriver.hookFirst('resolveId', [unresolvedId, undefined]).then(resolveIdResult => {
+ private loadEntryModule = (
+ unresolvedId: string,
+ isEntry: boolean,
+ importer: string | undefined
+ ): Promise =>
+ this.pluginDriver.hookFirst('resolveId', [unresolvedId, importer]).then(resolveIdResult => {
if (
resolveIdResult === false ||
(resolveIdResult && typeof resolveIdResult === 'object' && resolveIdResult.external)
@@ -434,7 +443,7 @@ export class ModuleLoader {
private normalizeResolveIdResult(
resolveIdResult: ResolveIdResult,
- importer: string,
+ importer: string | undefined,
source: string
): ResolvedId | null {
let id = '';
@@ -457,10 +466,10 @@ export class ModuleLoader {
if (this.isExternal(resolveIdResult, importer, true)) {
external = true;
}
- id = external ? normalizeRelativeExternalId(importer, resolveIdResult) : resolveIdResult;
+ id = external ? normalizeRelativeExternalId(resolveIdResult, importer) : resolveIdResult;
}
} else {
- id = normalizeRelativeExternalId(importer, source);
+ id = normalizeRelativeExternalId(source, importer);
if (resolveIdResult !== false && !this.isExternal(id, importer, true)) {
return null;
}
diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts
index e88f5381d1e..30f5f38477f 100644
--- a/src/rollup/types.d.ts
+++ b/src/rollup/types.d.ts
@@ -134,6 +134,7 @@ export interface EmittedAsset {
export interface EmittedChunk {
fileName?: string;
id: string;
+ importer?: string;
name?: string;
type: 'chunk';
}
@@ -175,11 +176,11 @@ export interface PluginContext extends MinimalPluginContext {
parse: (input: string, options: any) => AcornNode;
resolve: (
source: string,
- importer: string,
+ importer?: string,
options?: { skipSelf: boolean }
) => Promise;
/** @deprecated Use `this.resolve` instead */
- resolveId: (source: string, importer: string) => Promise;
+ resolveId: (source: string, importer?: string) => Promise;
setAssetSource: (assetReferenceId: string, source: string | Uint8Array) => void;
warn: (warning: RollupWarning | string, pos?: number | { column: number; line: number }) => void;
}
@@ -216,7 +217,7 @@ export type ResolveIdHook = (
export type IsExternal = (
source: string,
- importer: string,
+ importer: string | undefined,
isResolved: boolean
) => boolean | null | undefined;
diff --git a/src/utils/FileEmitter.ts b/src/utils/FileEmitter.ts
index 39d0b972256..dab16502ddf 100644
--- a/src/utils/FileEmitter.ts
+++ b/src/utils/FileEmitter.ts
@@ -294,6 +294,7 @@ export class FileEmitter {
{
fileName: emittedChunk.fileName || null,
id: emittedChunk.id,
+ importer: emittedChunk.importer as string | undefined,
name: emittedChunk.name || null
}
],
diff --git a/src/utils/PluginContext.ts b/src/utils/PluginContext.ts
index a84795a3c76..3d832cb5652 100644
--- a/src/utils/PluginContext.ts
+++ b/src/utils/PluginContext.ts
@@ -140,7 +140,7 @@ export function getPluginContexts(
};
},
isExternal: getDeprecatedContextHandler(
- (id: string, parentId: string, isResolved = false) =>
+ (id: string, parentId: string | undefined, isResolved = false) =>
graph.moduleLoader.isExternal(id, parentId, isResolved),
'isExternal',
'resolve',
@@ -163,7 +163,7 @@ export function getPluginContexts(
);
},
resolveId: getDeprecatedContextHandler(
- (source: string, importer: string) =>
+ (source: string, importer: string | undefined) =>
graph.moduleLoader
.resolveId(source, importer)
.then(resolveId => resolveId && resolveId.id),
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_config.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_config.js
new file mode 100644
index 00000000000..f8192ec4ea2
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_config.js
@@ -0,0 +1,35 @@
+const path = require('path');
+let noImporterReferenceId;
+let mainReferenceId;
+let nestedReferenceId;
+
+module.exports = {
+ description: 'allows specifying an importer when resolving ids',
+ options: {
+ input: 'main',
+ plugins: {
+ buildStart() {
+ noImporterReferenceId = this.emitFile({ type: 'chunk', id: './lib.js' });
+ mainReferenceId = this.emitFile({
+ type: 'chunk',
+ id: './lib.js',
+ importer: path.resolve(__dirname, 'main.js')
+ });
+ nestedReferenceId = this.emitFile({
+ type: 'chunk',
+ id: './lib.js',
+ importer: path.resolve(__dirname, 'nested/virtual.js')
+ });
+ },
+ transform(code, id) {
+ if (id.endsWith('main.js')) {
+ return (
+ `console.log('no importer', import.meta.ROLLUP_FILE_URL_${noImporterReferenceId});\n` +
+ `console.log('from maim', import.meta.ROLLUP_FILE_URL_${mainReferenceId});\n` +
+ `console.log('from nested', import.meta.ROLLUP_FILE_URL_${nestedReferenceId});\n`
+ );
+ }
+ }
+ }
+ }
+};
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/generated-lib.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/generated-lib.js
new file mode 100644
index 00000000000..cee6de6e289
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/generated-lib.js
@@ -0,0 +1,5 @@
+define(function () { 'use strict';
+
+ console.log('main lib');
+
+});
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/generated-lib2.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/generated-lib2.js
new file mode 100644
index 00000000000..f76826176c4
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/generated-lib2.js
@@ -0,0 +1,5 @@
+define(function () { 'use strict';
+
+ console.log('nested lib');
+
+});
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/main.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/main.js
new file mode 100644
index 00000000000..805bcc46e71
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/amd/main.js
@@ -0,0 +1,7 @@
+define(['require'], function (require) { 'use strict';
+
+ console.log('no importer', new URL(require.toUrl('./generated-lib.js'), document.baseURI).href);
+ console.log('from maim', new URL(require.toUrl('./generated-lib.js'), document.baseURI).href);
+ console.log('from nested', new URL(require.toUrl('./generated-lib2.js'), document.baseURI).href);
+
+});
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/generated-lib.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/generated-lib.js
new file mode 100644
index 00000000000..5aab9a32157
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/generated-lib.js
@@ -0,0 +1,3 @@
+'use strict';
+
+console.log('main lib');
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/generated-lib2.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/generated-lib2.js
new file mode 100644
index 00000000000..3bad4466642
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/generated-lib2.js
@@ -0,0 +1,3 @@
+'use strict';
+
+console.log('nested lib');
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/main.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/main.js
new file mode 100644
index 00000000000..6ea003e0663
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/cjs/main.js
@@ -0,0 +1,5 @@
+'use strict';
+
+console.log('no importer', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __dirname + '/generated-lib.js').href : new URL('generated-lib.js', document.currentScript && document.currentScript.src || document.baseURI).href));
+console.log('from maim', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __dirname + '/generated-lib.js').href : new URL('generated-lib.js', document.currentScript && document.currentScript.src || document.baseURI).href));
+console.log('from nested', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __dirname + '/generated-lib2.js').href : new URL('generated-lib2.js', document.currentScript && document.currentScript.src || document.baseURI).href));
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/generated-lib.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/generated-lib.js
new file mode 100644
index 00000000000..29000e65aa3
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/generated-lib.js
@@ -0,0 +1 @@
+console.log('main lib');
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/generated-lib2.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/generated-lib2.js
new file mode 100644
index 00000000000..fb916c16842
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/generated-lib2.js
@@ -0,0 +1 @@
+console.log('nested lib');
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/main.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/main.js
new file mode 100644
index 00000000000..a2c6d8019cc
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/es/main.js
@@ -0,0 +1,3 @@
+console.log('no importer', new URL('generated-lib.js', import.meta.url).href);
+console.log('from maim', new URL('generated-lib.js', import.meta.url).href);
+console.log('from nested', new URL('generated-lib2.js', import.meta.url).href);
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/generated-lib.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/generated-lib.js
new file mode 100644
index 00000000000..507bca5d3ac
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/generated-lib.js
@@ -0,0 +1,10 @@
+System.register([], function () {
+ 'use strict';
+ return {
+ execute: function () {
+
+ console.log('main lib');
+
+ }
+ };
+});
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/generated-lib2.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/generated-lib2.js
new file mode 100644
index 00000000000..7926ecf0ea3
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/generated-lib2.js
@@ -0,0 +1,10 @@
+System.register([], function () {
+ 'use strict';
+ return {
+ execute: function () {
+
+ console.log('nested lib');
+
+ }
+ };
+});
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/main.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/main.js
new file mode 100644
index 00000000000..370c482dbbb
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/_expected/system/main.js
@@ -0,0 +1,12 @@
+System.register([], function (exports, module) {
+ 'use strict';
+ return {
+ execute: function () {
+
+ console.log('no importer', new URL('generated-lib.js', module.meta.url).href);
+ console.log('from maim', new URL('generated-lib.js', module.meta.url).href);
+ console.log('from nested', new URL('generated-lib2.js', module.meta.url).href);
+
+ }
+ };
+});
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/lib.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/lib.js
new file mode 100644
index 00000000000..29000e65aa3
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/lib.js
@@ -0,0 +1 @@
+console.log('main lib');
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/main.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/main.js
new file mode 100644
index 00000000000..61502ce3163
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/main.js
@@ -0,0 +1 @@
+console.log('ignored');
diff --git a/test/chunking-form/samples/emit-file/emit-chunk-with-importer/nested/lib.js b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/nested/lib.js
new file mode 100644
index 00000000000..fb916c16842
--- /dev/null
+++ b/test/chunking-form/samples/emit-file/emit-chunk-with-importer/nested/lib.js
@@ -0,0 +1 @@
+console.log('nested lib');