Skip to content

Commit

Permalink
Add optional importer parameter to this.emitFile
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Mar 13, 2020
1 parent e019ea0 commit 6df327f
Show file tree
Hide file tree
Showing 22 changed files with 141 additions and 23 deletions.
13 changes: 8 additions & 5 deletions docs/05-plugin-development.md
Expand Up @@ -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}`<br>
Type: `(source: string, importer: string | undefined) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | null, syntheticNamedExports?: boolean | null}`<br>
Kind: `async, first`<br>
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.<br>
Next Hook: [`load`](guide/en/#load) if the resolved id that has not yet been loaded, otherwise [`buildEnd`](guide/en/#buildend).
Expand Down Expand Up @@ -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
}
Expand All @@ -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.

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<string | null>` - _**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<string | null>` - _**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

Expand Down
5 changes: 3 additions & 2 deletions src/Graph.ts
Expand Up @@ -32,14 +32,15 @@ function normalizeEntryModules(
entryModules: string | string[] | Record<string, string>
): 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
}));
}
Expand Down
31 changes: 20 additions & 11 deletions src/ModuleLoader.ts
Expand Up @@ -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<T extends Array<any>>(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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]);
Expand All @@ -195,7 +200,7 @@ export class ModuleLoader {

async resolveId(
source: string,
importer: string,
importer: string | undefined,
skip?: number | null
): Promise<ResolvedId | null> {
return this.normalizeResolveIdResult(
Expand Down Expand Up @@ -413,8 +418,12 @@ export class ModuleLoader {
return resolvedId;
}

private loadEntryModule = (unresolvedId: string, isEntry: boolean): Promise<Module> =>
this.pluginDriver.hookFirst('resolveId', [unresolvedId, undefined]).then(resolveIdResult => {
private loadEntryModule = (
unresolvedId: string,
isEntry: boolean,
importer: string | undefined
): Promise<Module> =>
this.pluginDriver.hookFirst('resolveId', [unresolvedId, importer]).then(resolveIdResult => {
if (
resolveIdResult === false ||
(resolveIdResult && typeof resolveIdResult === 'object' && resolveIdResult.external)
Expand All @@ -434,7 +443,7 @@ export class ModuleLoader {

private normalizeResolveIdResult(
resolveIdResult: ResolveIdResult,
importer: string,
importer: string | undefined,
source: string
): ResolvedId | null {
let id = '';
Expand All @@ -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;
}
Expand Down
7 changes: 4 additions & 3 deletions src/rollup/types.d.ts
Expand Up @@ -134,6 +134,7 @@ export interface EmittedAsset {
export interface EmittedChunk {
fileName?: string;
id: string;
importer?: string;
name?: string;
type: 'chunk';
}
Expand Down Expand Up @@ -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<ResolvedId | null>;
/** @deprecated Use `this.resolve` instead */
resolveId: (source: string, importer: string) => Promise<string | null>;
resolveId: (source: string, importer?: string) => Promise<string | null>;
setAssetSource: (assetReferenceId: string, source: string | Uint8Array) => void;
warn: (warning: RollupWarning | string, pos?: number | { column: number; line: number }) => void;
}
Expand Down Expand Up @@ -216,7 +217,7 @@ export type ResolveIdHook = (

export type IsExternal = (
source: string,
importer: string,
importer: string | undefined,
isResolved: boolean
) => boolean | null | undefined;

Expand Down
1 change: 1 addition & 0 deletions src/utils/FileEmitter.ts
Expand Up @@ -294,6 +294,7 @@ export class FileEmitter {
{
fileName: emittedChunk.fileName || null,
id: emittedChunk.id,
importer: emittedChunk.importer as string | undefined,
name: emittedChunk.name || null
}
],
Expand Down
4 changes: 2 additions & 2 deletions src/utils/PluginContext.ts
Expand Up @@ -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',
Expand All @@ -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),
Expand Down
@@ -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`
);
}
}
}
}
};
@@ -0,0 +1,5 @@
define(function () { 'use strict';

console.log('main lib');

});
@@ -0,0 +1,5 @@
define(function () { 'use strict';

console.log('nested lib');

});
@@ -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);

});
@@ -0,0 +1,3 @@
'use strict';

console.log('main lib');
@@ -0,0 +1,3 @@
'use strict';

console.log('nested lib');

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1 @@
console.log('main lib');
@@ -0,0 +1 @@
console.log('nested lib');
@@ -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);
@@ -0,0 +1,10 @@
System.register([], function () {
'use strict';
return {
execute: function () {

console.log('main lib');

}
};
});
@@ -0,0 +1,10 @@
System.register([], function () {
'use strict';
return {
execute: function () {

console.log('nested lib');

}
};
});
@@ -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);

}
};
});
@@ -0,0 +1 @@
console.log('main lib');
@@ -0,0 +1 @@
console.log('ignored');
@@ -0,0 +1 @@
console.log('nested lib');

0 comments on commit 6df327f

Please sign in to comment.