diff --git a/.changeset/import-graph-resolution-cache.md b/.changeset/import-graph-resolution-cache.md new file mode 100644 index 0000000..21c150d --- /dev/null +++ b/.changeset/import-graph-resolution-cache.md @@ -0,0 +1,5 @@ +--- +"diagnostics-webpack-plugin": patch +--- + +Resolve the imports of every file in a pass of the import graph through one module resolution cache per set of compiler options, rather than looking up the same package or shared module again for every file importing it. diff --git a/src/import-graph.js b/src/import-graph.js index 48e70e4..f541952 100644 --- a/src/import-graph.js +++ b/src/import-graph.js @@ -39,6 +39,7 @@ import { toPosixPath } from "./utils.js"; * @property {Map} options the compiler options of each config file * @property {Map} reads every file read for a signature, as it was read * @property {Map} configHashes each config file and what it extends, hashed, as the signatures were last read under + * @property {Map} resolutions what each set of compiler options resolved this pass, so that every file in a folder does not look its imports up again * @property {Map} faces what each file that changed showed the files importing it, as the last change left it, hashed */ @@ -62,6 +63,7 @@ function createGraph(ts) { options: new Map(), reads: new Map(), configHashes: new Map(), + resolutions: new Map(), faces: new Map(), }; } @@ -101,6 +103,32 @@ function optionsFor(graph, file) { return graph.options.get(config); } +/** + * What the files of one set of compiler options resolved this pass. Kept for + * no longer than a pass: a file written since can make an import that resolved + * to nothing resolve, which an answer kept from before would hide. + * @param {Graph} graph the graph + * @param {EXPECTED_ANY} options the compiler options the imports are resolved under + * @returns {EXPECTED_ANY} the cache TypeScript resolves them through + */ +function resolutionsFor(graph, options) { + let cache = graph.resolutions.get(options); + + if (!cache) { + const { ts } = graph; + + cache = ts.createModuleResolutionCache( + ts.sys.getCurrentDirectory(), + (/** @type {string} */ name) => + ts.sys.useCaseSensitiveFileNames ? name : name.toLowerCase(), + options, + ); + graph.resolutions.set(options, cache); + } + + return cache; +} + /** * @param {Graph} graph the graph * @param {string} file the file the text is of @@ -128,6 +156,7 @@ function importsOf(graph, file, text) { file, options, ts.sys, + resolutionsFor(graph, options), ).resolvedModule; if (found) { @@ -241,6 +270,8 @@ function faceOf(graph, file, text, imports) { * @returns {Set | undefined} the files to lint again, as posix paths, or nothing when every file has to be */ function affectedFiles(graph, files, changed) { + graph.resolutions.clear(); + const covered = new Set(files.map((file) => toPosixPath(resolve(file)))); const touched = changed.map((file) => toPosixPath(resolve(file))); @@ -531,6 +562,8 @@ function projectSignature(graph, configs) { * @returns {Map} the signature of each of them that can be read, by its posix path */ function signatures(graph, files) { + graph.resolutions.clear(); + const wanted = files.map((file) => toPosixPath(resolve(file))); // Read first, so that the config files they resolve through are known. diff --git a/types/import-graph.d.ts b/types/import-graph.d.ts index ce014e8..4489453 100644 --- a/types/import-graph.d.ts +++ b/types/import-graph.d.ts @@ -71,6 +71,10 @@ export type Graph = { * each config file and what it extends, hashed, as the signatures were last read under */ configHashes: Map; + /** + * what each set of compiler options resolved this pass, so that every file in a folder does not look its imports up again + */ + resolutions: Map; /** * what each file that changed showed the files importing it, as the last change left it, hashed */