From dc2acb49765edc4e4cee7248b47892cad62342fd Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 9 Jun 2021 10:57:59 -0400 Subject: [PATCH] refactor(@ngtools/webpack): use Webpack loader context hook to access file emitters The Angular Webpack loader will now access the Angular Webpack Plugin's file emitters via the loader context directly. This is instead of using the not fully public Webpack compilation property on the loader context. This change also removes the need to directly alter the Webpack compilation object which previously added an additional property. --- packages/ngtools/webpack/src/ivy/loader.ts | 13 ++++---- packages/ngtools/webpack/src/ivy/plugin.ts | 38 +++++++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/ngtools/webpack/src/ivy/loader.ts b/packages/ngtools/webpack/src/ivy/loader.ts index 3898b1c112da..3b400cae4245 100644 --- a/packages/ngtools/webpack/src/ivy/loader.ts +++ b/packages/ngtools/webpack/src/ivy/loader.ts @@ -10,20 +10,19 @@ import * as path from 'path'; import { AngularPluginSymbol, FileEmitterCollection } from './symbol'; export function angularWebpackLoader( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this: any, + this: import('webpack').LoaderContext & { + [AngularPluginSymbol]?: FileEmitterCollection; + }, content: string, - // Source map types are broken in the webpack type definitions - // eslint-disable-next-line @typescript-eslint/no-explicit-any - map: any, + map: string, ) { const callback = this.async(); if (!callback) { throw new Error('Invalid webpack version'); } - const fileEmitter = this._compilation[AngularPluginSymbol] as FileEmitterCollection; - if (typeof fileEmitter !== 'object') { + const fileEmitter = this[AngularPluginSymbol]; + if (!fileEmitter || typeof fileEmitter !== 'object') { if (this.resourcePath.endsWith('.js')) { // Passthrough for JS files when no plugin is used this.callback(undefined, content, map); diff --git a/packages/ngtools/webpack/src/ivy/plugin.ts b/packages/ngtools/webpack/src/ivy/plugin.ts index a8e06228cb5c..1b9f0375f83a 100644 --- a/packages/ngtools/webpack/src/ivy/plugin.ts +++ b/packages/ngtools/webpack/src/ivy/plugin.ts @@ -51,11 +51,6 @@ export interface AngularWebpackPluginOptions { inlineStyleFileExtension?: string; } -// Add support for missing properties in Webpack types as well as the loader's file emitter -interface WebpackCompilation extends Compilation { - [AngularPluginSymbol]: FileEmitterCollection; -} - function initializeNgccProcessor( compiler: Compiler, tsconfig: string, @@ -87,6 +82,7 @@ function hashContent(content: string): Uint8Array { } const PLUGIN_NAME = 'angular-compiler'; +const compilationFileEmitters = new WeakMap(); export class AngularWebpackPlugin { private readonly pluginOptions: AngularWebpackPluginOptions; @@ -151,14 +147,9 @@ export class AngularWebpackPlugin { let ngccProcessor: NgccProcessor | undefined; let resourceLoader: WebpackResourceLoader | undefined; let previousUnused: Set | undefined; - compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (thisCompilation) => { - const compilation = thisCompilation as WebpackCompilation; - + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { // Register plugin to ensure deterministic emit order in multi-plugin usage - if (!compilation[AngularPluginSymbol]) { - compilation[AngularPluginSymbol] = new FileEmitterCollection(); - } - const emitRegistration = compilation[AngularPluginSymbol].register(); + const emitRegistration = this.registerWithCompilation(compilation); this.watchMode = compiler.watchMode; @@ -182,7 +173,7 @@ export class AngularWebpackPlugin { } // Setup and read TypeScript and Angular compiler configuration - const { compilerOptions, rootNames, errors } = this.loadConfiguration(compilation); + const { compilerOptions, rootNames, errors } = this.loadConfiguration(); // Create diagnostics reporter and report configuration file errors const diagnosticsReporter = createDiagnosticsReporter(compilation); @@ -314,6 +305,23 @@ export class AngularWebpackPlugin { }); } + private registerWithCompilation(compilation: Compilation) { + let fileEmitters = compilationFileEmitters.get(compilation); + if (!fileEmitters) { + fileEmitters = new FileEmitterCollection(); + compilationFileEmitters.set(compilation, fileEmitters); + compilation.compiler.webpack.NormalModule.getCompilationHooks(compilation).loader.tap( + PLUGIN_NAME, + (loaderContext: { [AngularPluginSymbol]?: FileEmitterCollection }) => { + loaderContext[AngularPluginSymbol] = fileEmitters; + }, + ); + } + const emitRegistration = fileEmitters.register(); + + return emitRegistration; + } + private markResourceUsed(normalizedResourcePath: string, currentUnused: Set): void { if (!currentUnused.has(normalizedResourcePath)) { return; @@ -331,7 +339,7 @@ export class AngularWebpackPlugin { private async rebuildRequiredFiles( modules: Iterable, - compilation: WebpackCompilation, + compilation: Compilation, fileEmitter: FileEmitter, ): Promise { if (this.requiredFilesToEmit.size === 0) { @@ -377,7 +385,7 @@ export class AngularWebpackPlugin { this.requiredFilesToEmitCache.clear(); } - private loadConfiguration(compilation: WebpackCompilation) { + private loadConfiguration() { const { options: compilerOptions, rootNames,