Skip to content

Commit 389f597

Browse files
committed
feat(@ngtools/webpack): support multiple plugin instances per compilation
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: angular#5072
1 parent 18626d5 commit 389f597

File tree

3 files changed

+54
-8
lines changed

3 files changed

+54
-8
lines changed

packages/ngtools/webpack/src/ivy/loader.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import * as path from 'path';
9-
import { AngularPluginSymbol, FileEmitter } from './symbol';
9+
import { AngularPluginSymbol, FileEmitterCollection } from './symbol';
1010

1111
export function angularWebpackLoader(
1212
this: import('webpack').loader.LoaderContext,
@@ -20,8 +20,8 @@ export function angularWebpackLoader(
2020
throw new Error('Invalid webpack version');
2121
}
2222

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

37-
emitFile(this.resourcePath)
37+
fileEmitter.emit(this.resourcePath)
3838
.then((result) => {
3939
if (!result) {
4040
if (this.resourcePath.endsWith('.js')) {

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
augmentProgramWithVersioning,
2828
} from './host';
2929
import { externalizePath, normalizePath } from './paths';
30-
import { AngularPluginSymbol, EmitFileResult, FileEmitter } from './symbol';
30+
import { AngularPluginSymbol, EmitFileResult, FileEmitter, FileEmitterCollection } from './symbol';
3131
import { InputFileSystemSync, createWebpackSystem } from './system';
3232
import { createAotTransformers, createJitTransformers, mergeTransformers } from './transformation';
3333

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

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

159+
// Register plugin to ensure deterministic emit order in multi-plugin usage
160+
if (!compilation[AngularPluginSymbol]) {
161+
compilation[AngularPluginSymbol] = new FileEmitterCollection();
162+
}
163+
const emitRegistration = compilation[AngularPluginSymbol].register();
164+
159165
// Store watch mode; assume true if not present (webpack < 4.23.0)
160166
this.watchMode = compiler.watchMode ?? true;
161167

@@ -294,7 +300,7 @@ export class AngularWebpackPlugin {
294300
});
295301

296302
// Store file emitter for loader usage
297-
compilation[AngularPluginSymbol] = fileEmitter;
303+
emitRegistration.update(fileEmitter);
298304
});
299305
}
300306

@@ -538,7 +544,7 @@ export class AngularWebpackPlugin {
538544
// tslint:disable-next-line: no-any
539545
(angularProgram as any).reuseTsProgram =
540546
// tslint:disable-next-line: no-any
541-
angularCompiler?.getNextProgram() || (angularCompiler as any)?.getCurrentProgram();
547+
angularCompiler.getNextProgram?.() || (angularCompiler as any).getCurrentProgram?.();
542548

543549
return this.createFileEmitter(
544550
builder,

packages/ngtools/webpack/src/ivy/symbol.ts

+40
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,43 @@ export interface EmitFileResult {
1515
}
1616

1717
export type FileEmitter = (file: string) => Promise<EmitFileResult | undefined>;
18+
19+
export class FileEmitterRegistration {
20+
#fileEmitter?: FileEmitter;
21+
22+
update(emitter: FileEmitter): void {
23+
this.#fileEmitter = emitter;
24+
}
25+
26+
emit(file: string): Promise<EmitFileResult | undefined> {
27+
if (!this.#fileEmitter) {
28+
throw new Error('Emit attempted before Angular Webpack plugin initialization.');
29+
}
30+
31+
return this.#fileEmitter(file);
32+
}
33+
}
34+
35+
export class FileEmitterCollection {
36+
#registrations: FileEmitterRegistration[] = [];
37+
38+
register(): FileEmitterRegistration {
39+
const registration = new FileEmitterRegistration();
40+
this.#registrations.push(registration);
41+
42+
return registration;
43+
}
44+
45+
async emit(file: string): Promise<EmitFileResult | undefined> {
46+
if (this.#registrations.length === 1) {
47+
return this.#registrations[0].emit(file);
48+
}
49+
50+
for (const registration of this.#registrations) {
51+
const result = await registration.emit(file);
52+
if (result) {
53+
return result;
54+
}
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)