Skip to content

Commit 05fe93d

Browse files
ModuleInfo.importedIds does not crash in moduleParsed (#4208)
* fixed: ModuleInfo.importedIds moduleInfo.importedIds return null if resolvedIds[source] if undefined * Only call moduleParsed once importedIds and dynamicallyImportedIds are available * Update documentation Co-authored-by: Lukas Taegert-Atkinson <lukas.taegert-atkinson@tngtech.com>
1 parent 69c099c commit 05fe93d

7 files changed

Lines changed: 122 additions & 46 deletions

File tree

docs/05-plugin-development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ This hook is called each time a module has been fully parsed by Rollup. See [`th
122122

123123
In contrast to the [`transform`](guide/en/#transform) hook, this hook is never cached and can be used to get information about both cached and other modules, including the final shape of the `meta` property, the `code` and the `ast`.
124124

125-
Note that information about imported modules is not yet available in this hook, and information about importing modules may be incomplete as additional importers could be discovered later. If you need this information, use the [`buildEnd`](guide/en/#buildend) hook.
125+
This hook will wait until all imports are resolved so that the information in `moduleInfo.importedIds` and `moduleInfo.dynamicallyImportedIds` is complete and accurate. Note however that information about importing modules may be incomplete as additional importers could be discovered later. If you need this information, use the [`buildEnd`](guide/en/#buildend) hook.
126126

127127
#### `options`
128128

src/Module.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ export interface AstContext {
116116
warn: (warning: RollupWarning, pos: number) => void;
117117
}
118118

119+
export interface DynamicImport {
120+
argument: string | ExpressionNode;
121+
id: string | null;
122+
node: ImportExpression;
123+
resolution: Module | ExternalModule | string | null;
124+
}
125+
119126
const MISSING_EXPORT_SHIM_DESCRIPTION: ExportDescription = {
120127
identifier: null,
121128
localName: MISSING_EXPORT_SHIM_VARIABLE
@@ -187,11 +194,7 @@ export default class Module {
187194
dependencies = new Set<Module | ExternalModule>();
188195
dynamicDependencies = new Set<Module | ExternalModule>();
189196
dynamicImporters: string[] = [];
190-
dynamicImports: {
191-
argument: string | ExpressionNode;
192-
node: ImportExpression;
193-
resolution: Module | ExternalModule | string | null;
194-
}[] = [];
197+
dynamicImports: DynamicImport[] = [];
195198
excludeFromSourcemap: boolean;
196199
execIndex = Infinity;
197200
exportAllSources = new Set<string>();
@@ -256,9 +259,9 @@ export default class Module {
256259
code: null,
257260
get dynamicallyImportedIds() {
258261
const dynamicallyImportedIds: string[] = [];
259-
for (const { resolution } of module.dynamicImports) {
260-
if (resolution instanceof Module || resolution instanceof ExternalModule) {
261-
dynamicallyImportedIds.push(resolution.id);
262+
for (const { id } of module.dynamicImports) {
263+
if (id) {
264+
dynamicallyImportedIds.push(id);
262265
}
263266
}
264267
return dynamicallyImportedIds;
@@ -846,7 +849,7 @@ export default class Module {
846849
} else if (argument instanceof Literal && typeof argument.value === 'string') {
847850
argument = argument.value;
848851
}
849-
this.dynamicImports.push({ argument, node, resolution: null });
852+
this.dynamicImports.push({ argument, id: null, node, resolution: null });
850853
}
851854

852855
private addExport(

src/ModuleLoader.ts

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as acorn from 'acorn';
22
import ExternalModule from './ExternalModule';
33
import Graph from './Graph';
4-
import Module from './Module';
4+
import Module, { DynamicImport } from './Module';
55
import {
66
CustomPluginOptions,
77
EmittedChunk,
@@ -276,27 +276,27 @@ export class ModuleLoader {
276276
return loadNewModulesPromise;
277277
}
278278

279-
private async fetchDynamicDependencies(module: Module): Promise<void> {
279+
private async fetchDynamicDependencies(
280+
module: Module,
281+
resolveDynamicImportPromises: Promise<
282+
[dynamicImport: DynamicImport, resolvedId: ResolvedId | string | null]
283+
>[]
284+
): Promise<void> {
280285
const dependencies = await Promise.all(
281-
module.dynamicImports.map(async dynamicImport => {
282-
const resolvedId = await this.resolveDynamicImport(
283-
module,
284-
typeof dynamicImport.argument === 'string'
285-
? dynamicImport.argument
286-
: dynamicImport.argument.esTreeNode,
287-
module.id
288-
);
289-
if (resolvedId === null) return null;
290-
if (typeof resolvedId === 'string') {
291-
dynamicImport.resolution = resolvedId;
292-
return null;
293-
}
294-
return (dynamicImport.resolution = await this.fetchResolvedDependency(
295-
relativeId(resolvedId.id),
296-
module.id,
297-
resolvedId
298-
));
299-
})
286+
resolveDynamicImportPromises.map(resolveDynamicImportPromise =>
287+
resolveDynamicImportPromise.then(async ([dynamicImport, resolvedId]) => {
288+
if (resolvedId === null) return null;
289+
if (typeof resolvedId === 'string') {
290+
dynamicImport.resolution = resolvedId;
291+
return null;
292+
}
293+
return (dynamicImport.resolution = await this.fetchResolvedDependency(
294+
relativeId(resolvedId.id),
295+
module.id,
296+
resolvedId
297+
));
298+
})
299+
)
300300
);
301301
for (const dependency of dependencies) {
302302
if (dependency) {
@@ -336,10 +336,15 @@ export class ModuleLoader {
336336
this.modulesById.set(id, module);
337337
this.graph.watchFiles[id] = true;
338338
await this.addModuleSource(id, importer, module);
339-
await this.pluginDriver.hookParallel('moduleParsed', [module.info]);
339+
const resolveStaticDependencyPromises = this.getResolveStaticDependencyPromises(module);
340+
const resolveDynamicImportPromises = this.getResolveDynamicImportPromises(module);
341+
Promise.all([
342+
...(resolveStaticDependencyPromises as Promise<never>[]),
343+
...(resolveDynamicImportPromises as Promise<never>[])
344+
]).then(() => this.pluginDriver.hookParallel('moduleParsed', [module.info]));
340345
await Promise.all([
341-
this.fetchStaticDependencies(module),
342-
this.fetchDynamicDependencies(module)
346+
this.fetchStaticDependencies(module, resolveStaticDependencyPromises),
347+
this.fetchDynamicDependencies(module, resolveDynamicImportPromises)
343348
]);
344349
module.linkImports();
345350
return module;
@@ -375,19 +380,14 @@ export class ModuleLoader {
375380
}
376381
}
377382

378-
private async fetchStaticDependencies(module: Module): Promise<void> {
383+
private async fetchStaticDependencies(
384+
module: Module,
385+
resolveStaticDependencyPromises: Promise<[source: string, resolvedId: ResolvedId]>[]
386+
): Promise<void> {
379387
for (const dependency of await Promise.all(
380-
Array.from(module.sources, async source =>
381-
this.fetchResolvedDependency(
382-
source,
383-
module.id,
384-
(module.resolvedIds[source] =
385-
module.resolvedIds[source] ||
386-
this.handleResolveId(
387-
await this.resolveId(source, module.id, EMPTY_OBJECT),
388-
source,
389-
module.id
390-
))
388+
resolveStaticDependencyPromises.map(resolveStaticDependencyPromise =>
389+
resolveStaticDependencyPromise.then(([source, resolvedId]) =>
390+
this.fetchResolvedDependency(source, module.id, resolvedId)
391391
)
392392
)
393393
)) {
@@ -450,6 +450,43 @@ export class ModuleLoader {
450450
};
451451
}
452452

453+
private getResolveDynamicImportPromises(
454+
module: Module
455+
): Promise<[dynamicImport: DynamicImport, resolvedId: ResolvedId | string | null]>[] {
456+
return module.dynamicImports.map(async dynamicImport => {
457+
const resolvedId = await this.resolveDynamicImport(
458+
module,
459+
typeof dynamicImport.argument === 'string'
460+
? dynamicImport.argument
461+
: dynamicImport.argument.esTreeNode,
462+
module.id
463+
);
464+
if (resolvedId && typeof resolvedId === 'object') {
465+
dynamicImport.id = resolvedId.id;
466+
}
467+
return [dynamicImport, resolvedId] as [DynamicImport, ResolvedId | string | null];
468+
});
469+
}
470+
471+
private getResolveStaticDependencyPromises(
472+
module: Module
473+
): Promise<[source: string, resolvedId: ResolvedId]>[] {
474+
return Array.from(
475+
module.sources,
476+
async source =>
477+
[
478+
source,
479+
(module.resolvedIds[source] =
480+
module.resolvedIds[source] ||
481+
this.handleResolveId(
482+
await this.resolveId(source, module.id, EMPTY_OBJECT),
483+
source,
484+
module.id
485+
))
486+
] as [string, ResolvedId]
487+
);
488+
}
489+
453490
private handleResolveId(
454491
resolvedId: ResolvedId | null,
455492
source: string,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const assert = require('assert');
2+
const path = require('path');
3+
4+
module.exports = {
5+
description: 'provides full importedIds and dynamicallyImportedIds in the moduleParsed hook',
6+
options: {
7+
plugins: [
8+
{
9+
load(id) {
10+
const { dynamicallyImportedIds, importedIds } = this.getModuleInfo(id);
11+
assert.deepStrictEqual(importedIds, []);
12+
assert.deepStrictEqual(dynamicallyImportedIds, []);
13+
},
14+
transform(code, id) {
15+
const { dynamicallyImportedIds, importedIds } = this.getModuleInfo(id);
16+
assert.deepStrictEqual(importedIds, []);
17+
assert.deepStrictEqual(dynamicallyImportedIds, []);
18+
},
19+
moduleParsed({ dynamicallyImportedIds, id, importedIds }) {
20+
if (id.endsWith('main.js')) {
21+
assert.deepStrictEqual(importedIds, [path.join(__dirname, 'static.js')]);
22+
assert.deepStrictEqual(dynamicallyImportedIds, [path.join(__dirname, 'dynamic.js')]);
23+
} else {
24+
assert.deepStrictEqual(importedIds, []);
25+
assert.deepStrictEqual(dynamicallyImportedIds, []);
26+
}
27+
}
28+
}
29+
]
30+
}
31+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const bar = 'dynamic';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { foo } from './static.js';
2+
3+
export const result = [foo, import('./dynamic.js')];
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 'static';

0 commit comments

Comments
 (0)