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,