Skip to content

Commit 7cc87f1

Browse files
authored
Add resolved sources to shouldTransformCachedModule (#4414)
* Add dependencies to shouldTransform hook * Test errors in shouldTransform are handled correctly * Provide resolvedSources instead of importedIds * Add documentation
1 parent 3106ec9 commit 7cc87f1

7 files changed

Lines changed: 99 additions & 8 deletions

File tree

docs/05-plugin-development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ In watch mode or when using the cache explicitly, the resolved imports of a cach
266266

267267
#### `shouldTransformCachedModule`
268268

269-
**Type:** `({id: string, code: string, ast: ESTree.Program, meta: {[plugin: string]: any}, moduleSideEffects: boolean | "no-treeshake", syntheticNamedExports: string | boolean}) => boolean`<br> **Kind:** `async, first`<br> **Previous Hook:** [`load`](guide/en/#load) where the cached file was loaded to compare its code with the cached version.<br> **Next Hook:** [`moduleParsed`](guide/en/#moduleparsed) if no plugin returns `true`, otherwise [`transform`](guide/en/#transform).
269+
**Type:** `({id: string, code: string, ast: ESTree.Program, resoledSources: {[source: string]: ResolvedId}, meta: {[plugin: string]: any}, moduleSideEffects: boolean | "no-treeshake", syntheticNamedExports: string | boolean}) => boolean`<br> **Kind:** `async, first`<br> **Previous Hook:** [`load`](guide/en/#load) where the cached file was loaded to compare its code with the cached version.<br> **Next Hook:** [`moduleParsed`](guide/en/#moduleparsed) if no plugin returns `true`, otherwise [`transform`](guide/en/#transform).
270270

271271
If the Rollup cache is used (e.g. in watch mode or explicitly via the JavaScript API), Rollup will skip the [`transform`](guide/en/#transform) hook of a module if after the [`load`](guide/en/#transform) hook, the loaded `code` is identical to the code of the cached copy. To prevent this, discard the cached copy and instead transform a module, plugins can implement this hook and return `true`.
272272

src/Module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ export default class Module {
279279
.filter(Boolean) as ResolvedId[];
280280
},
281281
get dynamicallyImportedIds() {
282+
// We cannot use this.dynamicDependencies because this is needed before
283+
// dynamicDependencies are populated
282284
const dynamicallyImportedIds: string[] = [];
283285
for (const { id } of dynamicImports) {
284286
if (id) {
@@ -316,6 +318,8 @@ export default class Module {
316318
return Array.from(sources, source => module.resolvedIds[source]).filter(Boolean);
317319
},
318320
get importedIds() {
321+
// We cannot use this.dependencies because this is needed before
322+
// dependencies are populated
319323
return Array.from(sources, source => module.resolvedIds[source]?.id).filter(Boolean);
320324
},
321325
get importers() {
@@ -703,6 +707,7 @@ export default class Module {
703707
transformFiles,
704708
...moduleOptions
705709
}: TransformModuleJSON & {
710+
resolvedIds?: ResolvedIdMap;
706711
transformFiles?: EmittedFile[] | undefined;
707712
}): void {
708713
this.info.code = code;

src/ModuleLoader.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ export class ModuleLoader {
283283
id: cachedModule.id,
284284
meta: cachedModule.meta,
285285
moduleSideEffects: cachedModule.moduleSideEffects,
286+
resolvedSources: cachedModule.resolvedIds,
286287
syntheticNamedExports: cachedModule.syntheticNamedExports
287288
}
288289
]))

src/rollup/types.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ export interface TransformModuleJSON {
108108
customTransformCache: boolean;
109109
originalCode: string;
110110
originalSourcemap: ExistingDecodedSourceMap | null;
111-
resolvedIds?: ResolvedIdMap;
112111
sourcemapChain: DecodedSourceMapOrMissing[];
113112
transformDependencies: string[];
114113
}
@@ -117,6 +116,7 @@ export interface ModuleJSON extends TransformModuleJSON, ModuleOptions {
117116
ast: AcornNode;
118117
dependencies: string[];
119118
id: string;
119+
resolvedIds: ResolvedIdMap;
120120
transformFiles: EmittedFile[] | undefined;
121121
}
122122

@@ -254,6 +254,7 @@ export type ShouldTransformCachedModuleHook = (
254254
id: string;
255255
meta: CustomPluginOptions;
256256
moduleSideEffects: boolean | 'no-treeshake';
257+
resolvedSources: ResolvedIdMap;
257258
syntheticNamedExports: boolean | string;
258259
}
259260
) => Promise<boolean> | boolean;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const path = require('path');
2+
const acorn = require('acorn');
3+
4+
const code = 'export default 42;\n';
5+
const ID_MAIN = path.join(__dirname, 'main.js');
6+
7+
module.exports = {
8+
description: 'errors in shouldTransformCachedModule abort the build',
9+
options: {
10+
cache: {
11+
modules: [
12+
{
13+
id: ID_MAIN,
14+
ast: acorn.parse(code, {
15+
ecmaVersion: 6,
16+
sourceType: 'module'
17+
}),
18+
code,
19+
dependencies: [],
20+
dynamicDependencies: [],
21+
originalCode: code,
22+
resolvedIds: {},
23+
sourcemapChain: [],
24+
transformDependencies: []
25+
}
26+
]
27+
},
28+
plugins: [
29+
{
30+
name: 'test',
31+
shouldTransformCachedModule() {
32+
throw new Error('broken');
33+
}
34+
}
35+
]
36+
},
37+
error: {
38+
code: 'PLUGIN_ERROR',
39+
hook: 'shouldTransformCachedModule',
40+
message: 'broken',
41+
plugin: 'test',
42+
watchFiles: [ID_MAIN]
43+
}
44+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 42;

test/incremental/index.js

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,15 @@ describe('incremental', () => {
338338
});
339339

340340
it('runs shouldTransformCachedModule when using a cached module', async () => {
341+
modules = {
342+
entry: `import foo from 'foo'; export default foo;`,
343+
foo: `export default import('bar')`,
344+
bar: `export default 42`
345+
};
341346
let shouldTransformCachedModuleCalls = 0;
342347

343348
const transformPlugin = {
344-
async shouldTransformCachedModule({ ast, id, meta, ...other }) {
349+
async shouldTransformCachedModule({ ast, id, meta, resolvedSources, ...other }) {
345350
shouldTransformCachedModuleCalls++;
346351
assert.strictEqual(ast.type, 'Program');
347352
assert.deepStrictEqual(other, {
@@ -352,10 +357,34 @@ describe('incremental', () => {
352357
switch (id) {
353358
case 'foo':
354359
assert.deepStrictEqual(meta, { transform: { calls: 1, id } });
360+
assert.deepStrictEqual(resolvedSources, {
361+
__proto__: null,
362+
bar: {
363+
external: false,
364+
id: 'bar',
365+
meta: {},
366+
moduleSideEffects: true,
367+
syntheticNamedExports: false
368+
}
369+
});
355370
// we return promises to ensure they are awaited
356371
return Promise.resolve(false);
372+
case 'bar':
373+
assert.deepStrictEqual(meta, { transform: { calls: 2, id } });
374+
assert.deepStrictEqual(resolvedSources, { __proto__: null });
375+
return Promise.resolve(false);
357376
case 'entry':
358377
assert.deepStrictEqual(meta, { transform: { calls: 0, id } });
378+
assert.deepStrictEqual(resolvedSources, {
379+
__proto__: null,
380+
foo: {
381+
external: false,
382+
id: 'foo',
383+
meta: {},
384+
moduleSideEffects: true,
385+
syntheticNamedExports: false
386+
}
387+
});
359388
return Promise.resolve(true);
360389
default:
361390
throw new Error(`Unexpected id ${id}.`);
@@ -369,8 +398,12 @@ describe('incremental', () => {
369398
input: 'entry',
370399
plugins: [transformPlugin, plugin]
371400
});
372-
assert.strictEqual(shouldTransformCachedModuleCalls, 0);
373-
assert.strictEqual(transformCalls, 2);
401+
assert.strictEqual(
402+
shouldTransformCachedModuleCalls,
403+
0,
404+
'initial shouldTransformCachedModule calls'
405+
);
406+
assert.strictEqual(transformCalls, 3, 'initial transform calls');
374407

375408
const {
376409
cache: { modules: cachedModules }
@@ -379,11 +412,17 @@ describe('incremental', () => {
379412
plugins: [transformPlugin, plugin],
380413
cache
381414
});
382-
assert.strictEqual(shouldTransformCachedModuleCalls, 2);
383-
assert.strictEqual(transformCalls, 3);
415+
assert.strictEqual(
416+
shouldTransformCachedModuleCalls,
417+
3,
418+
'final shouldTransformCachedModule calls'
419+
);
420+
assert.strictEqual(transformCalls, 4, 'final transform calls');
384421
assert.strictEqual(cachedModules[0].id, 'foo');
385422
assert.deepStrictEqual(cachedModules[0].meta, { transform: { calls: 1, id: 'foo' } });
386423
assert.strictEqual(cachedModules[1].id, 'entry');
387-
assert.deepStrictEqual(cachedModules[1].meta, { transform: { calls: 2, id: 'entry' } });
424+
assert.deepStrictEqual(cachedModules[1].meta, { transform: { calls: 3, id: 'entry' } });
425+
assert.strictEqual(cachedModules[2].id, 'bar');
426+
assert.deepStrictEqual(cachedModules[2].meta, { transform: { calls: 2, id: 'bar' } });
388427
});
389428
});

0 commit comments

Comments
 (0)