Skip to content

Commit f31d7f7

Browse files
alan-agius4dgp1130
authored andcommittedDec 15, 2021
perf(@ngtools/webpack): reduce redudant module rebuilds when cache is restored
With this change we reduce the redundant module rebuilds when Webpack's FS cache is restored. Previously, when the cache was restored we caused all modules to be rebuild even though their contents didn't change. This is because the file emit history wasn't persisted to disk. This also caused the side effect that Webpack will create additional cache `pack` files due to the unnecessary module rebuilds, which ultimatly causes increase of the size of the cache on disk. (cherry picked from commit b03b9ee)
1 parent adf925c commit f31d7f7

File tree

1 file changed

+45
-13
lines changed

1 file changed

+45
-13
lines changed
 

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

+45-13
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,14 @@ function initializeNgccProcessor(
9090
return { processor, errors, warnings };
9191
}
9292

93-
function hashContent(content: string): Uint8Array {
94-
return createHash('md5').update(content).digest();
95-
}
96-
9793
const PLUGIN_NAME = 'angular-compiler';
9894
const compilationFileEmitters = new WeakMap<Compilation, FileEmitterCollection>();
9995

96+
interface FileEmitHistoryItem {
97+
length: number;
98+
hash: Uint8Array;
99+
}
100+
100101
export class AngularWebpackPlugin {
101102
private readonly pluginOptions: AngularWebpackPluginOptions;
102103
private compilerCliModule?: typeof import('@angular/compiler-cli');
@@ -105,10 +106,11 @@ export class AngularWebpackPlugin {
105106
private ngtscNextProgram?: NgtscProgram;
106107
private builder?: ts.EmitAndSemanticDiagnosticsBuilderProgram;
107108
private sourceFileCache?: SourceFileCache;
109+
private webpackCache?: ReturnType<Compilation['getCache']>;
108110
private readonly fileDependencies = new Map<string, Set<string>>();
109111
private readonly requiredFilesToEmit = new Set<string>();
110112
private readonly requiredFilesToEmitCache = new Map<string, EmitFileResult | undefined>();
111-
private readonly fileEmitHistory = new Map<string, { length: number; hash: Uint8Array }>();
113+
private readonly fileEmitHistory = new Map<string, FileEmitHistoryItem>();
112114

113115
constructor(options: Partial<AngularWebpackPluginOptions> = {}) {
114116
this.pluginOptions = {
@@ -136,6 +138,7 @@ export class AngularWebpackPlugin {
136138
return this.pluginOptions;
137139
}
138140

141+
// eslint-disable-next-line max-lines-per-function
139142
apply(compiler: Compiler): void {
140143
const { NormalModuleReplacementPlugin, util } = compiler.webpack;
141144

@@ -177,9 +180,13 @@ export class AngularWebpackPlugin {
177180
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
178181
// Register plugin to ensure deterministic emit order in multi-plugin usage
179182
const emitRegistration = this.registerWithCompilation(compilation);
180-
181183
this.watchMode = compiler.watchMode;
182184

185+
// Initialize webpack cache
186+
if (!this.webpackCache && compilation.options.cache) {
187+
this.webpackCache = compilation.getCache(PLUGIN_NAME);
188+
}
189+
183190
// Initialize the resource loader if not already setup
184191
if (!resourceLoader) {
185192
resourceLoader = new WebpackResourceLoader(this.watchMode);
@@ -377,7 +384,7 @@ export class AngularWebpackPlugin {
377384

378385
const filesToRebuild = new Set<string>();
379386
for (const requiredFile of this.requiredFilesToEmit) {
380-
const history = this.fileEmitHistory.get(requiredFile);
387+
const history = await this.getFileEmitHistory(requiredFile);
381388
if (history) {
382389
const emitResult = await fileEmitter(requiredFile);
383390
if (
@@ -706,12 +713,8 @@ export class AngularWebpackPlugin {
706713

707714
onAfterEmit?.(sourceFile);
708715

709-
let hash;
710-
if (content !== undefined && this.watchMode) {
711-
// Capture emit history info for Angular rebuild analysis
712-
hash = hashContent(content);
713-
this.fileEmitHistory.set(filePath, { length: content.length, hash });
714-
}
716+
// Capture emit history info for Angular rebuild analysis
717+
const hash = content ? (await this.addFileEmitHistory(filePath, content)).hash : undefined;
715718

716719
const dependencies = [
717720
...(this.fileDependencies.get(filePath) || []),
@@ -737,4 +740,33 @@ export class AngularWebpackPlugin {
737740
this.compilerCliModule = await new Function(`return import('@angular/compiler-cli');`)();
738741
this.compilerNgccModule = await new Function(`return import('@angular/compiler-cli/ngcc');`)();
739742
}
743+
744+
private async addFileEmitHistory(
745+
filePath: string,
746+
content: string,
747+
): Promise<FileEmitHistoryItem> {
748+
const historyData: FileEmitHistoryItem = {
749+
length: content.length,
750+
hash: createHash('md5').update(content).digest(),
751+
};
752+
753+
if (this.webpackCache) {
754+
const history = await this.getFileEmitHistory(filePath);
755+
if (!history || Buffer.compare(history.hash, historyData.hash) !== 0) {
756+
// Hash doesn't match or item doesn't exist.
757+
await this.webpackCache.storePromise(filePath, null, historyData);
758+
}
759+
} else if (this.watchMode) {
760+
// The in memory file emit history is only required during watch mode.
761+
this.fileEmitHistory.set(filePath, historyData);
762+
}
763+
764+
return historyData;
765+
}
766+
767+
private async getFileEmitHistory(filePath: string): Promise<FileEmitHistoryItem | undefined> {
768+
return this.webpackCache
769+
? this.webpackCache.getPromise<FileEmitHistoryItem | undefined>(filePath, null)
770+
: this.fileEmitHistory.get(filePath);
771+
}
740772
}

0 commit comments

Comments
 (0)
Please sign in to comment.