Skip to content

Commit

Permalink
feat(@ngtools/webpack): support multiple plugin instances per compila…
Browse files Browse the repository at this point in the history
…tion

This change allows multiple instances of the `AngularWebpackPlugin` to be present in a Webpack configuration.
Each plugin instance should reference a different TypeScript configuration file (`tsconfig.json`) and the TypeScript configuration files should be setup to not include source files present in the other TypeScript configuration files. If files are included in more than one TypeScript configuration, the first plugin present in the Webpack configuration that can emit the file will be used.

Closes: #5072
  • Loading branch information
clydin committed Apr 8, 2021
1 parent 118ffb1 commit 46e9d0e
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 8 deletions.
8 changes: 4 additions & 4 deletions packages/ngtools/webpack/src/ivy/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import { AngularPluginSymbol, FileEmitter } from './symbol';
import { AngularPluginSymbol, FileEmitterCollection } from './symbol';

export function angularWebpackLoader(
this: import('webpack').loader.LoaderContext,
Expand All @@ -20,8 +20,8 @@ export function angularWebpackLoader(
throw new Error('Invalid webpack version');
}

const emitFile = this._compilation[AngularPluginSymbol] as FileEmitter;
if (typeof emitFile !== 'function') {
const fileEmitter = this._compilation[AngularPluginSymbol] as FileEmitterCollection;
if (typeof fileEmitter !== 'object') {
if (this.resourcePath.endsWith('.js')) {
// Passthrough for JS files when no plugin is used
this.callback(undefined, content, map);
Expand All @@ -34,7 +34,7 @@ export function angularWebpackLoader(
return;
}

emitFile(this.resourcePath)
fileEmitter.emit(this.resourcePath)
.then((result) => {
if (!result) {
if (this.resourcePath.endsWith('.js')) {
Expand Down
14 changes: 10 additions & 4 deletions packages/ngtools/webpack/src/ivy/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
augmentProgramWithVersioning,
} from './host';
import { externalizePath, normalizePath } from './paths';
import { AngularPluginSymbol, EmitFileResult, FileEmitter } from './symbol';
import { AngularPluginSymbol, EmitFileResult, FileEmitter, FileEmitterCollection } from './symbol';
import { InputFileSystemSync, createWebpackSystem } from './system';
import { createAotTransformers, createJitTransformers, mergeTransformers } from './transformation';

Expand All @@ -54,7 +54,7 @@ interface WebpackCompilation extends compilation.Compilation {
// tslint:disable-next-line: no-any
compilationDependencies: { add(item: string): any };
rebuildModule(module: compilation.Module, callback: () => void): void;
[AngularPluginSymbol]: FileEmitter;
[AngularPluginSymbol]: FileEmitterCollection;
}

function initializeNgccProcessor(
Expand Down Expand Up @@ -156,6 +156,12 @@ export class AngularWebpackPlugin {
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (thisCompilation) => {
const compilation = thisCompilation as WebpackCompilation;

// Register plugin to ensure deterministic emit order in multi-plugin usage
if (!compilation[AngularPluginSymbol]) {
compilation[AngularPluginSymbol] = new FileEmitterCollection();
}
const emitRegistration = compilation[AngularPluginSymbol].register();

// Store watch mode; assume true if not present (webpack < 4.23.0)
this.watchMode = compiler.watchMode ?? true;

Expand Down Expand Up @@ -294,7 +300,7 @@ export class AngularWebpackPlugin {
});

// Store file emitter for loader usage
compilation[AngularPluginSymbol] = fileEmitter;
emitRegistration.update(fileEmitter);
});
}

Expand Down Expand Up @@ -538,7 +544,7 @@ export class AngularWebpackPlugin {
// tslint:disable-next-line: no-any
(angularProgram as any).reuseTsProgram =
// tslint:disable-next-line: no-any
angularCompiler?.getNextProgram() || (angularCompiler as any)?.getCurrentProgram();
angularCompiler.getNextProgram?.() || (angularCompiler as any).getCurrentProgram?.();

return this.createFileEmitter(
builder,
Expand Down
40 changes: 40 additions & 0 deletions packages/ngtools/webpack/src/ivy/symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,43 @@ export interface EmitFileResult {
}

export type FileEmitter = (file: string) => Promise<EmitFileResult | undefined>;

export class FileEmitterRegistration {
#fileEmitter?: FileEmitter;

update(emitter: FileEmitter): void {
this.#fileEmitter = emitter;
}

emit(file: string): Promise<EmitFileResult | undefined> {
if (!this.#fileEmitter) {
throw new Error('Emit attempted before Angular Webpack plugin initialization.');
}

return this.#fileEmitter(file);
}
}

export class FileEmitterCollection {
#registrations: FileEmitterRegistration[] = [];

register(): FileEmitterRegistration {
const registration = new FileEmitterRegistration();
this.#registrations.push(registration);

return registration;
}

async emit(file: string): Promise<EmitFileResult | undefined> {
if (this.#registrations.length === 1) {
return this.#registrations[0].emit(file);
}

for (const registration of this.#registrations) {
const result = await registration.emit(file);
if (result) {
return result;
}
}
}
}

0 comments on commit 46e9d0e

Please sign in to comment.