Skip to content

Commit

Permalink
perf(@ngtools/webpack): avoid adding transitive dependencies to Webpa…
Browse files Browse the repository at this point in the history
…ck'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.
  • Loading branch information
clydin authored and alan-agius4 committed Mar 11, 2021
1 parent 985dc1a commit 95aa2b8
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 1 deletion.
72 changes: 72 additions & 0 deletions packages/ngtools/webpack/src/ivy/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,78 @@ function augmentResolveModuleNames(
}
}

/**
* Augments a TypeScript Compiler Host's resolveModuleNames function to collect dependencies
* of the containing file passed to the resolveModuleNames function. This process assumes
* that consumers of the Compiler Host will only call resolveModuleNames with modules that are
* actually present in a containing 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.
*
* @param host The CompilerHost to augment.
* @param dependencies A Map which will be used to store file dependencies.
* @param moduleResolutionCache An optional resolution cache to use when the host resolves a module.
*/
export function augmentHostWithDependencyCollection(
host: ts.CompilerHost,
dependencies: Map<string, Set<string>>,
moduleResolutionCache?: ts.ModuleResolutionCache,
): void {
if (host.resolveModuleNames) {
const baseResolveModuleNames = host.resolveModuleNames;
host.resolveModuleNames = function (moduleNames: string[], containingFile: string, ...parameters) {
const results = baseResolveModuleNames.call(host, moduleNames, containingFile, ...parameters);

const containingFilePath = normalizePath(containingFile);
for (const result of results) {
if (result) {
const containingFileDependencies = dependencies.get(containingFilePath);
if (containingFileDependencies) {
containingFileDependencies.add(result.resolvedFileName);
} else {
dependencies.set(containingFilePath, new Set([result.resolvedFileName]));
}
}
}

return results;
};
} else {
host.resolveModuleNames = function (
moduleNames: string[],
containingFile: string,
_reusedNames: string[] | undefined,
redirectedReference: ts.ResolvedProjectReference | undefined,
options: ts.CompilerOptions,
) {
return moduleNames.map((name) => {
const result = ts.resolveModuleName(
name,
containingFile,
options,
host,
moduleResolutionCache,
redirectedReference,
).resolvedModule;

if (result) {
const containingFilePath = normalizePath(containingFile);
const containingFileDependencies = dependencies.get(containingFilePath);
if (containingFileDependencies) {
containingFileDependencies.add(result.resolvedFileName);
} else {
dependencies.set(containingFilePath, new Set([result.resolvedFileName]));
}
}

return result;
});
};
}
}

export function augmentHostWithNgcc(
host: ts.CompilerHost,
ngcc: NgccProcessor,
Expand Down
12 changes: 11 additions & 1 deletion packages/ngtools/webpack/src/ivy/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { SourceFileCache } from './cache';
import { DiagnosticsReporter, createDiagnosticsReporter } from './diagnostics';
import {
augmentHostWithCaching,
augmentHostWithDependencyCollection,
augmentHostWithNgcc,
augmentHostWithReplacements,
augmentHostWithResources,
Expand Down Expand Up @@ -90,6 +91,7 @@ export class AngularWebpackPlugin {
private builder?: ts.EmitAndSemanticDiagnosticsBuilderProgram;
private sourceFileCache?: SourceFileCache;
private buildTimestamp!: number;
private readonly fileDependencies = new Map<string, Set<string>>();
private readonly requiredFilesToEmit = new Set<string>();
private readonly requiredFilesToEmitCache = new Map<string, EmitFileResult | undefined>();
private readonly fileEmitHistory = new Map<string, { length: number; hash: Uint8Array }>();
Expand Down Expand Up @@ -195,6 +197,11 @@ export class AngularWebpackPlugin {
if (cache) {
// Invalidate existing cache based on compiler file timestamps
changedFiles = cache.invalidate(compiler.fileTimestamps, this.buildTimestamp);

// Invalidate file dependencies of changed files
for (const changedFile of changedFiles) {
this.fileDependencies.delete(normalizePath(changedFile));
}
} else {
// Initialize a new cache
cache = new SourceFileCache();
Expand All @@ -212,6 +219,9 @@ export class AngularWebpackPlugin {
compilerOptions,
);

// Setup source file dependency collection
augmentHostWithDependencyCollection(host, this.fileDependencies, moduleResolutionCache);

// Setup on demand ngcc
augmentHostWithNgcc(host, ngccProcessor, moduleResolutionCache);

Expand Down Expand Up @@ -589,7 +599,7 @@ export class AngularWebpackPlugin {
}

const dependencies = [
...program.getAllDependencies(sourceFile),
...this.fileDependencies.get(filePath) || [],
...getExtraDependencies(sourceFile),
].map(externalizePath);

Expand Down

0 comments on commit 95aa2b8

Please sign in to comment.