Skip to content

Commit cc9be1c

Browse files
clydinalan-agius4
authored andcommitted
perf(@ngtools/webpack): avoid adding transitive dependencies to Webpack's dependency graph
This change augments a TypeScript Compiler Host's resolveModuleNames function to collect dependencies of the containing file based on the module names passed to the resolveModuleNames function. This process assumes that consumers of the Compiler Host will call resolveModuleNames with modules that are actually present in a containing file. The TypeScript compiler exhibits such behavior making this process effective at generating a set of all direct dependencies for a given source file. This process is a workaround for gathering a TypeScript SourceFile's dependencies as there is no currently exposed public method to do so. A BuilderProgram does have a `getAllDependencies` function. However, that function returns all transitive dependencies as well which can cause excessive Webpack rebuilds especially in larger programs.
1 parent ffbefc9 commit cc9be1c

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

packages/ngtools/webpack/src/ivy/host.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,78 @@ function augmentResolveModuleNames(
9090
}
9191
}
9292

93+
/**
94+
* Augments a TypeScript Compiler Host's resolveModuleNames function to collect dependencies
95+
* of the containing file passed to the resolveModuleNames function. This process assumes
96+
* that consumers of the Compiler Host will only call resolveModuleNames with modules that are
97+
* actually present in a containing file.
98+
* This process is a workaround for gathering a TypeScript SourceFile's dependencies as there
99+
* is no currently exposed public method to do so. A BuilderProgram does have a `getAllDependencies`
100+
* function. However, that function returns all transitive dependencies as well which can cause
101+
* excessive Webpack rebuilds.
102+
*
103+
* @param host The CompilerHost to augment.
104+
* @param dependencies A Map which will be used to store file dependencies.
105+
* @param moduleResolutionCache An optional resolution cache to use when the host resolves a module.
106+
*/
107+
export function augmentHostWithDependencyCollection(
108+
host: ts.CompilerHost,
109+
dependencies: Map<string, Set<string>>,
110+
moduleResolutionCache?: ts.ModuleResolutionCache,
111+
): void {
112+
if (host.resolveModuleNames) {
113+
const baseResolveModuleNames = host.resolveModuleNames;
114+
host.resolveModuleNames = function (moduleNames: string[], containingFile: string, ...parameters) {
115+
const results = baseResolveModuleNames.call(host, moduleNames, containingFile, ...parameters);
116+
117+
const containingFilePath = normalizePath(containingFile);
118+
for (const result of results) {
119+
if (result) {
120+
const containingFileDependencies = dependencies.get(containingFilePath);
121+
if (containingFileDependencies) {
122+
containingFileDependencies.add(result.resolvedFileName);
123+
} else {
124+
dependencies.set(containingFilePath, new Set([result.resolvedFileName]));
125+
}
126+
}
127+
}
128+
129+
return results;
130+
};
131+
} else {
132+
host.resolveModuleNames = function (
133+
moduleNames: string[],
134+
containingFile: string,
135+
_reusedNames: string[] | undefined,
136+
redirectedReference: ts.ResolvedProjectReference | undefined,
137+
options: ts.CompilerOptions,
138+
) {
139+
return moduleNames.map((name) => {
140+
const result = ts.resolveModuleName(
141+
name,
142+
containingFile,
143+
options,
144+
host,
145+
moduleResolutionCache,
146+
redirectedReference,
147+
).resolvedModule;
148+
149+
if (result) {
150+
const containingFilePath = normalizePath(containingFile);
151+
const containingFileDependencies = dependencies.get(containingFilePath);
152+
if (containingFileDependencies) {
153+
containingFileDependencies.add(result.resolvedFileName);
154+
} else {
155+
dependencies.set(containingFilePath, new Set([result.resolvedFileName]));
156+
}
157+
}
158+
159+
return result;
160+
});
161+
};
162+
}
163+
}
164+
93165
export function augmentHostWithNgcc(
94166
host: ts.CompilerHost,
95167
ngcc: NgccProcessor,

packages/ngtools/webpack/src/ivy/plugin.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { SourceFileCache } from './cache';
2626
import { DiagnosticsReporter, createDiagnosticsReporter } from './diagnostics';
2727
import {
2828
augmentHostWithCaching,
29+
augmentHostWithDependencyCollection,
2930
augmentHostWithNgcc,
3031
augmentHostWithReplacements,
3132
augmentHostWithResources,
@@ -92,6 +93,7 @@ export class AngularWebpackPlugin {
9293
private sourceFileCache?: SourceFileCache;
9394
private buildTimestamp!: number;
9495
private readonly lazyRouteMap: Record<string, string> = {};
96+
private readonly fileDependencies = new Map<string, Set<string>>();
9597
private readonly requiredFilesToEmit = new Set<string>();
9698
private readonly requiredFilesToEmitCache = new Map<string, EmitFileResult | undefined>();
9799
private readonly fileEmitHistory = new Map<string, { length: number; hash: Uint8Array }>();
@@ -198,6 +200,11 @@ export class AngularWebpackPlugin {
198200
if (cache) {
199201
// Invalidate existing cache based on compiler file timestamps
200202
changedFiles = cache.invalidate(compiler.fileTimestamps, this.buildTimestamp);
203+
204+
// Invalidate file dependencies of changed files
205+
for (const changedFile of changedFiles) {
206+
this.fileDependencies.delete(normalizePath(changedFile));
207+
}
201208
} else {
202209
// Initialize a new cache
203210
cache = new SourceFileCache();
@@ -215,6 +222,9 @@ export class AngularWebpackPlugin {
215222
compilerOptions,
216223
);
217224

225+
// Setup source file dependency collection
226+
augmentHostWithDependencyCollection(host, this.fileDependencies, moduleResolutionCache);
227+
218228
// Setup on demand ngcc
219229
augmentHostWithNgcc(host, ngccProcessor, moduleResolutionCache);
220230

@@ -649,7 +659,7 @@ export class AngularWebpackPlugin {
649659
}
650660

651661
const dependencies = [
652-
...program.getAllDependencies(sourceFile),
662+
...this.fileDependencies.get(filePath) || [],
653663
...getExtraDependencies(sourceFile),
654664
].map(externalizePath);
655665

0 commit comments

Comments
 (0)