Skip to content

Commit 95aa2b8

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 985dc1a commit 95aa2b8

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

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

+72
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

+11-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { SourceFileCache } from './cache';
2424
import { DiagnosticsReporter, createDiagnosticsReporter } from './diagnostics';
2525
import {
2626
augmentHostWithCaching,
27+
augmentHostWithDependencyCollection,
2728
augmentHostWithNgcc,
2829
augmentHostWithReplacements,
2930
augmentHostWithResources,
@@ -90,6 +91,7 @@ export class AngularWebpackPlugin {
9091
private builder?: ts.EmitAndSemanticDiagnosticsBuilderProgram;
9192
private sourceFileCache?: SourceFileCache;
9293
private buildTimestamp!: number;
94+
private readonly fileDependencies = new Map<string, Set<string>>();
9395
private readonly requiredFilesToEmit = new Set<string>();
9496
private readonly requiredFilesToEmitCache = new Map<string, EmitFileResult | undefined>();
9597
private readonly fileEmitHistory = new Map<string, { length: number; hash: Uint8Array }>();
@@ -195,6 +197,11 @@ export class AngularWebpackPlugin {
195197
if (cache) {
196198
// Invalidate existing cache based on compiler file timestamps
197199
changedFiles = cache.invalidate(compiler.fileTimestamps, this.buildTimestamp);
200+
201+
// Invalidate file dependencies of changed files
202+
for (const changedFile of changedFiles) {
203+
this.fileDependencies.delete(normalizePath(changedFile));
204+
}
198205
} else {
199206
// Initialize a new cache
200207
cache = new SourceFileCache();
@@ -212,6 +219,9 @@ export class AngularWebpackPlugin {
212219
compilerOptions,
213220
);
214221

222+
// Setup source file dependency collection
223+
augmentHostWithDependencyCollection(host, this.fileDependencies, moduleResolutionCache);
224+
215225
// Setup on demand ngcc
216226
augmentHostWithNgcc(host, ngccProcessor, moduleResolutionCache);
217227

@@ -589,7 +599,7 @@ export class AngularWebpackPlugin {
589599
}
590600

591601
const dependencies = [
592-
...program.getAllDependencies(sourceFile),
602+
...this.fileDependencies.get(filePath) || [],
593603
...getExtraDependencies(sourceFile),
594604
].map(externalizePath);
595605

0 commit comments

Comments
 (0)