feat: add `snapshot.contextModule `to configure snapshots for context modules

This commit is contained in:
Xiao 2025-08-26 03:16:48 +08:00 committed by GitHub
parent 076863133b
commit d224b7b334
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 118 additions and 15 deletions

View File

@ -2475,6 +2475,19 @@ export interface SnapshotOptions {
*/ */
timestamp?: boolean; timestamp?: boolean;
}; };
/**
* Options for snapshotting the context module to determine if it needs to be built again.
*/
contextModule?: {
/**
* Use hashes of the content of the files/directories to determine invalidation.
*/
hash?: boolean;
/**
* Use timestamps of the files/directories to determine invalidation.
*/
timestamp?: boolean;
};
/** /**
* List of paths that are managed by a package manager and contain a version or hash in its path so all files are immutable. * List of paths that are managed by a package manager and contain a version or hash in its path so all files are immutable.
*/ */

View File

@ -109,8 +109,6 @@ const makeSerializable = require("./util/makeSerializable");
/** @typedef {Record<ModuleId, FakeMapType>} FakeMap */ /** @typedef {Record<ModuleId, FakeMapType>} FakeMap */
const SNAPSHOT_OPTIONS = { timestamp: true };
class ContextModule extends Module { class ContextModule extends Module {
/** /**
* @param {ResolveDependencies} resolveDependencies function to get dependencies in this context * @param {ResolveDependencies} resolveDependencies function to get dependencies in this context
@ -535,6 +533,8 @@ class ContextModule extends Module {
} }
if (!this.context && !this.options.resource) return callback(); if (!this.context && !this.options.resource) return callback();
const snapshotOptions = compilation.options.snapshot.contextModule;
compilation.fileSystemInfo.createSnapshot( compilation.fileSystemInfo.createSnapshot(
startTime, startTime,
null, null,
@ -544,7 +544,7 @@ class ContextModule extends Module {
? [this.options.resource] ? [this.options.resource]
: /** @type {string[]} */ (this.options.resource), : /** @type {string[]} */ (this.options.resource),
null, null,
SNAPSHOT_OPTIONS, snapshotOptions,
(err, snapshot) => { (err, snapshot) => {
if (err) return callback(err); if (err) return callback(err);
/** @type {BuildInfo} */ /** @type {BuildInfo} */

View File

@ -694,6 +694,7 @@ const applySnapshotDefaults = (snapshot, { production, futureDefaults }) => {
F(snapshot, "module", () => F(snapshot, "module", () =>
production ? { timestamp: true, hash: true } : { timestamp: true } production ? { timestamp: true, hash: true } : { timestamp: true }
); );
F(snapshot, "contextModule", () => ({ timestamp: true }));
F(snapshot, "resolve", () => F(snapshot, "resolve", () =>
production ? { timestamp: true, hash: true } : { timestamp: true } production ? { timestamp: true, hash: true } : { timestamp: true }
); );

View File

@ -457,6 +457,13 @@ const getNormalizedWebpackOptions = (config) => ({
timestamp: module.timestamp, timestamp: module.timestamp,
hash: module.hash hash: module.hash
})), })),
contextModule: optionalNestedConfig(
snapshot.contextModule,
(contextModule) => ({
timestamp: contextModule.timestamp,
hash: contextModule.hash
})
),
immutablePaths: optionalNestedArray(snapshot.immutablePaths, (p) => [...p]), immutablePaths: optionalNestedArray(snapshot.immutablePaths, (p) => [...p]),
managedPaths: optionalNestedArray(snapshot.managedPaths, (p) => [...p]), managedPaths: optionalNestedArray(snapshot.managedPaths, (p) => [...p]),
unmanagedPaths: optionalNestedArray(snapshot.unmanagedPaths, (p) => [...p]) unmanagedPaths: optionalNestedArray(snapshot.unmanagedPaths, (p) => [...p])

File diff suppressed because one or more lines are too long

View File

@ -5022,6 +5022,21 @@
} }
} }
}, },
"contextModule": {
"description": "Options for snapshotting the context module to determine if it needs to be built again.",
"type": "object",
"additionalProperties": false,
"properties": {
"hash": {
"description": "Use hashes of the content of the files/directories to determine invalidation.",
"type": "boolean"
},
"timestamp": {
"description": "Use timestamps of the files/directories to determine invalidation.",
"type": "boolean"
}
}
},
"immutablePaths": { "immutablePaths": {
"description": "List of paths that are managed by a package manager and contain a version or hash in its path so all files are immutable.", "description": "List of paths that are managed by a package manager and contain a version or hash in its path so all files are immutable.",
"type": "array", "type": "array",

View File

@ -653,6 +653,9 @@ describe("snapshots", () => {
"hash": true, "hash": true,
"timestamp": true, "timestamp": true,
}, },
"contextModule": Object {
"timestamp": true,
},
"immutablePaths": Array [], "immutablePaths": Array [],
"managedPaths": Array [ "managedPaths": Array [
"<cwd>/node_modules/", "<cwd>/node_modules/",

View File

@ -82,6 +82,16 @@ describe("Persistent Caching", () => {
); );
}); });
const getCacheFileTimes = async () => {
const cacheFiles = (await readdir(cachePath)).sort();
return new Map(
cacheFiles.map((f) => [
f,
fs.statSync(path.join(cachePath, f)).mtime.toString()
])
);
};
const execute = () => { const execute = () => {
const cache = {}; const cache = {};
const require = (name) => { const require = (name) => {
@ -230,11 +240,7 @@ sum([1,2,3])
` `
}); });
await compile({ entry: "./src/main.js" }); await compile({ entry: "./src/main.js" });
const firstCacheFiles = (await readdir(cachePath)).sort(); const firstCacheFileTimes = await getCacheFileTimes();
// cSpell:words Mtimes
const firstMtimes = firstCacheFiles.map(
(f) => fs.statSync(path.join(cachePath, f)).mtime
);
await updateSrc({ await updateSrc({
"main.js": ` "main.js": `
@ -248,11 +254,29 @@ import 'lodash';
readonly: true readonly: true
} }
}); });
const cacheFiles = (await readdir(cachePath)).sort(); await expect(getCacheFileTimes()).resolves.toEqual(firstCacheFileTimes);
expect(cacheFiles).toStrictEqual(firstCacheFiles); }, 20000);
expect(
firstCacheFiles.map((f) => fs.statSync(path.join(cachePath, f)).mtime) it("should not invalidate cache files if timestamps changed with dynamic import()", async () => {
// cSpell:words Mtimes const configAdditions = {
).toStrictEqual(firstMtimes); entry: "./src/main.js",
snapshot: {
resolve: { hash: true },
module: { hash: true },
contextModule: { hash: true }
}
};
await updateSrc({
"newer.js": "export default 2;",
// eslint-disable-next-line no-template-curly-in-string
"main.js": 'const f = "newer.js"; import(`./${f}`);'
});
await compile(configAdditions);
const firstCacheFileTimes = await getCacheFileTimes();
await utimes(path.resolve(srcPath, "newer.js"), new Date(), new Date());
await compile(configAdditions);
await expect(getCacheFileTimes()).resolves.toEqual(firstCacheFileTimes);
}, 20000); }, 20000);
}); });

View File

@ -8830,6 +8830,32 @@ Object {
"multiple": false, "multiple": false,
"simpleType": "boolean", "simpleType": "boolean",
}, },
"snapshot-context-module-hash": Object {
"configs": Array [
Object {
"description": "Use hashes of the content of the files/directories to determine invalidation.",
"multiple": false,
"path": "snapshot.contextModule.hash",
"type": "boolean",
},
],
"description": "Use hashes of the content of the files/directories to determine invalidation.",
"multiple": false,
"simpleType": "boolean",
},
"snapshot-context-module-timestamp": Object {
"configs": Array [
Object {
"description": "Use timestamps of the files/directories to determine invalidation.",
"multiple": false,
"path": "snapshot.contextModule.timestamp",
"type": "boolean",
},
],
"description": "Use timestamps of the files/directories to determine invalidation.",
"multiple": false,
"simpleType": "boolean",
},
"snapshot-immutable-paths": Object { "snapshot-immutable-paths": Object {
"configs": Array [ "configs": Array [
Object { Object {

14
types.d.ts vendored
View File

@ -16170,6 +16170,20 @@ declare interface SnapshotOptionsWebpackOptions {
timestamp?: boolean; timestamp?: boolean;
}; };
/**
* Options for snapshotting the context module to determine if it needs to be built again.
*/
contextModule?: {
/**
* Use hashes of the content of the files/directories to determine invalidation.
*/
hash?: boolean;
/**
* Use timestamps of the files/directories to determine invalidation.
*/
timestamp?: boolean;
};
/** /**
* List of paths that are managed by a package manager and contain a version or hash in its path so all files are immutable. * List of paths that are managed by a package manager and contain a version or hash in its path so all files are immutable.
*/ */