@@ -53,6 +53,16 @@ export interface AngularWebpackPluginOptions {
53
53
inlineStyleFileExtension ?: string ;
54
54
}
55
55
56
+ /**
57
+ * The Angular compilation state that is maintained across each Webpack compilation.
58
+ */
59
+ interface AngularCompilationState {
60
+ ngccProcessor ?: NgccProcessor ;
61
+ resourceLoader ?: WebpackResourceLoader ;
62
+ previousUnused ?: Set < string > ;
63
+ pathsPlugin : TypeScriptPathsPlugin ;
64
+ }
65
+
56
66
function initializeNgccProcessor (
57
67
compiler : Compiler ,
58
68
tsconfig : string ,
@@ -138,9 +148,8 @@ export class AngularWebpackPlugin {
138
148
return this . pluginOptions ;
139
149
}
140
150
141
- // eslint-disable-next-line max-lines-per-function
142
151
apply ( compiler : Compiler ) : void {
143
- const { NormalModuleReplacementPlugin, util } = compiler . webpack ;
152
+ const { NormalModuleReplacementPlugin, WebpackError , util } = compiler . webpack ;
144
153
this . webpackCreateHash = util . createHash ;
145
154
146
155
// Setup file replacements with webpack
@@ -175,171 +184,185 @@ export class AngularWebpackPlugin {
175
184
// Load the compiler-cli if not already available
176
185
compiler . hooks . beforeCompile . tapPromise ( PLUGIN_NAME , ( ) => this . initializeCompilerCli ( ) ) ;
177
186
178
- let ngccProcessor : NgccProcessor | undefined ;
179
- let resourceLoader : WebpackResourceLoader | undefined ;
180
- let previousUnused : Set < string > | undefined ;
187
+ const compilationState : AngularCompilationState = { pathsPlugin } ;
181
188
compiler . hooks . thisCompilation . tap ( PLUGIN_NAME , ( compilation ) => {
182
- // Register plugin to ensure deterministic emit order in multi-plugin usage
183
- const emitRegistration = this . registerWithCompilation ( compilation ) ;
184
- this . watchMode = compiler . watchMode ;
185
-
186
- // Initialize webpack cache
187
- if ( ! this . webpackCache && compilation . options . cache ) {
188
- this . webpackCache = compilation . getCache ( PLUGIN_NAME ) ;
189
- }
190
-
191
- // Initialize the resource loader if not already setup
192
- if ( ! resourceLoader ) {
193
- resourceLoader = new WebpackResourceLoader ( this . watchMode ) ;
189
+ try {
190
+ this . setupCompilation ( compilation , compilationState ) ;
191
+ } catch ( error ) {
192
+ compilation . errors . push (
193
+ new WebpackError (
194
+ `Failed to initialize Angular compilation - ${
195
+ error instanceof Error ? error . message : error
196
+ } `,
197
+ ) ,
198
+ ) ;
194
199
}
200
+ } ) ;
201
+ }
195
202
196
- // Initialize and process eager ngcc if not already setup
197
- if ( ! ngccProcessor ) {
198
- const { processor, errors, warnings } = initializeNgccProcessor (
199
- compiler ,
200
- this . pluginOptions . tsconfig ,
201
- this . compilerNgccModule ,
202
- ) ;
203
+ private setupCompilation ( compilation : Compilation , state : AngularCompilationState ) : void {
204
+ const compiler = compilation . compiler ;
203
205
204
- processor . process ( ) ;
205
- warnings . forEach ( ( warning ) => addWarning ( compilation , warning ) ) ;
206
- errors . forEach ( ( error ) => addError ( compilation , error ) ) ;
206
+ // Register plugin to ensure deterministic emit order in multi-plugin usage
207
+ const emitRegistration = this . registerWithCompilation ( compilation ) ;
208
+ this . watchMode = compiler . watchMode ;
207
209
208
- ngccProcessor = processor ;
209
- }
210
+ // Initialize webpack cache
211
+ if ( ! this . webpackCache && compilation . options . cache ) {
212
+ this . webpackCache = compilation . getCache ( PLUGIN_NAME ) ;
213
+ }
210
214
211
- // Setup and read TypeScript and Angular compiler configuration
212
- const { compilerOptions, rootNames, errors } = this . loadConfiguration ( ) ;
215
+ // Initialize the resource loader if not already setup
216
+ if ( ! state . resourceLoader ) {
217
+ state . resourceLoader = new WebpackResourceLoader ( this . watchMode ) ;
218
+ }
213
219
214
- // Create diagnostics reporter and report configuration file errors
215
- const diagnosticsReporter = createDiagnosticsReporter ( compilation , ( diagnostic ) =>
216
- this . compilerCli . formatDiagnostics ( [ diagnostic ] ) ,
220
+ // Initialize and process eager ngcc if not already setup
221
+ if ( ! state . ngccProcessor ) {
222
+ const { processor, errors, warnings } = initializeNgccProcessor (
223
+ compiler ,
224
+ this . pluginOptions . tsconfig ,
225
+ this . compilerNgccModule ,
217
226
) ;
218
- diagnosticsReporter ( errors ) ;
219
227
220
- // Update TypeScript path mapping plugin with new configuration
221
- pathsPlugin . update ( compilerOptions ) ;
228
+ processor . process ( ) ;
229
+ warnings . forEach ( ( warning ) => addWarning ( compilation , warning ) ) ;
230
+ errors . forEach ( ( error ) => addError ( compilation , error ) ) ;
222
231
223
- // Create a Webpack-based TypeScript compiler host
224
- const system = createWebpackSystem (
225
- // Webpack lacks an InputFileSytem type definition with sync functions
226
- compiler . inputFileSystem as InputFileSystemSync ,
227
- normalizePath ( compiler . context ) ,
228
- ) ;
229
- const host = ts . createIncrementalCompilerHost ( compilerOptions , system ) ;
230
-
231
- // Setup source file caching and reuse cache from previous compilation if present
232
- let cache = this . sourceFileCache ;
233
- let changedFiles ;
234
- if ( cache ) {
235
- changedFiles = new Set < string > ( ) ;
236
- for ( const changedFile of [ ...compiler . modifiedFiles , ...compiler . removedFiles ] ) {
237
- const normalizedChangedFile = normalizePath ( changedFile ) ;
238
- // Invalidate file dependencies
239
- this . fileDependencies . delete ( normalizedChangedFile ) ;
240
- // Invalidate existing cache
241
- cache . invalidate ( normalizedChangedFile ) ;
242
-
243
- changedFiles . add ( normalizedChangedFile ) ;
244
- }
245
- } else {
246
- // Initialize a new cache
247
- cache = new SourceFileCache ( ) ;
248
- // Only store cache if in watch mode
249
- if ( this . watchMode ) {
250
- this . sourceFileCache = cache ;
251
- }
232
+ state . ngccProcessor = processor ;
233
+ }
234
+
235
+ // Setup and read TypeScript and Angular compiler configuration
236
+ const { compilerOptions, rootNames, errors } = this . loadConfiguration ( ) ;
237
+
238
+ // Create diagnostics reporter and report configuration file errors
239
+ const diagnosticsReporter = createDiagnosticsReporter ( compilation , ( diagnostic ) =>
240
+ this . compilerCli . formatDiagnostics ( [ diagnostic ] ) ,
241
+ ) ;
242
+ diagnosticsReporter ( errors ) ;
243
+
244
+ // Update TypeScript path mapping plugin with new configuration
245
+ state . pathsPlugin . update ( compilerOptions ) ;
246
+
247
+ // Create a Webpack-based TypeScript compiler host
248
+ const system = createWebpackSystem (
249
+ // Webpack lacks an InputFileSytem type definition with sync functions
250
+ compiler . inputFileSystem as InputFileSystemSync ,
251
+ normalizePath ( compiler . context ) ,
252
+ ) ;
253
+ const host = ts . createIncrementalCompilerHost ( compilerOptions , system ) ;
254
+
255
+ // Setup source file caching and reuse cache from previous compilation if present
256
+ let cache = this . sourceFileCache ;
257
+ let changedFiles ;
258
+ if ( cache ) {
259
+ changedFiles = new Set < string > ( ) ;
260
+ for ( const changedFile of [ ...compiler . modifiedFiles , ...compiler . removedFiles ] ) {
261
+ const normalizedChangedFile = normalizePath ( changedFile ) ;
262
+ // Invalidate file dependencies
263
+ this . fileDependencies . delete ( normalizedChangedFile ) ;
264
+ // Invalidate existing cache
265
+ cache . invalidate ( normalizedChangedFile ) ;
266
+
267
+ changedFiles . add ( normalizedChangedFile ) ;
252
268
}
253
- augmentHostWithCaching ( host , cache ) ;
269
+ } else {
270
+ // Initialize a new cache
271
+ cache = new SourceFileCache ( ) ;
272
+ // Only store cache if in watch mode
273
+ if ( this . watchMode ) {
274
+ this . sourceFileCache = cache ;
275
+ }
276
+ }
277
+ augmentHostWithCaching ( host , cache ) ;
254
278
255
- const moduleResolutionCache = ts . createModuleResolutionCache (
256
- host . getCurrentDirectory ( ) ,
257
- host . getCanonicalFileName . bind ( host ) ,
258
- compilerOptions ,
259
- ) ;
279
+ const moduleResolutionCache = ts . createModuleResolutionCache (
280
+ host . getCurrentDirectory ( ) ,
281
+ host . getCanonicalFileName . bind ( host ) ,
282
+ compilerOptions ,
283
+ ) ;
260
284
261
- // Setup source file dependency collection
262
- augmentHostWithDependencyCollection ( host , this . fileDependencies , moduleResolutionCache ) ;
263
-
264
- // Setup on demand ngcc
265
- augmentHostWithNgcc ( host , ngccProcessor , moduleResolutionCache ) ;
266
-
267
- // Setup resource loading
268
- resourceLoader . update ( compilation , changedFiles ) ;
269
- augmentHostWithResources ( host , resourceLoader , {
270
- directTemplateLoading : this . pluginOptions . directTemplateLoading ,
271
- inlineStyleFileExtension : this . pluginOptions . inlineStyleFileExtension ,
272
- } ) ;
273
-
274
- // Setup source file adjustment options
275
- augmentHostWithReplacements ( host , this . pluginOptions . fileReplacements , moduleResolutionCache ) ;
276
- augmentHostWithSubstitutions ( host , this . pluginOptions . substitutions ) ;
277
-
278
- // Create the file emitter used by the webpack loader
279
- const { fileEmitter, builder, internalFiles } = this . pluginOptions . jitMode
280
- ? this . updateJitProgram ( compilerOptions , rootNames , host , diagnosticsReporter )
281
- : this . updateAotProgram (
282
- compilerOptions ,
283
- rootNames ,
284
- host ,
285
- diagnosticsReporter ,
286
- resourceLoader ,
287
- ) ;
285
+ // Setup source file dependency collection
286
+ augmentHostWithDependencyCollection ( host , this . fileDependencies , moduleResolutionCache ) ;
288
287
289
- // Set of files used during the unused TypeScript file analysis
290
- const currentUnused = new Set < string > ( ) ;
288
+ // Setup on demand ngcc
289
+ augmentHostWithNgcc ( host , state . ngccProcessor , moduleResolutionCache ) ;
291
290
292
- for ( const sourceFile of builder . getSourceFiles ( ) ) {
293
- if ( internalFiles ?. has ( sourceFile ) ) {
294
- continue ;
295
- }
291
+ // Setup resource loading
292
+ state . resourceLoader . update ( compilation , changedFiles ) ;
293
+ augmentHostWithResources ( host , state . resourceLoader , {
294
+ directTemplateLoading : this . pluginOptions . directTemplateLoading ,
295
+ inlineStyleFileExtension : this . pluginOptions . inlineStyleFileExtension ,
296
+ } ) ;
296
297
297
- // Ensure all program files are considered part of the compilation and will be watched.
298
- // Webpack does not normalize paths. Therefore, we need to normalize the path with FS seperators.
299
- compilation . fileDependencies . add ( externalizePath ( sourceFile . fileName ) ) ;
298
+ // Setup source file adjustment options
299
+ augmentHostWithReplacements ( host , this . pluginOptions . fileReplacements , moduleResolutionCache ) ;
300
+ augmentHostWithSubstitutions ( host , this . pluginOptions . substitutions ) ;
301
+
302
+ // Create the file emitter used by the webpack loader
303
+ const { fileEmitter, builder, internalFiles } = this . pluginOptions . jitMode
304
+ ? this . updateJitProgram ( compilerOptions , rootNames , host , diagnosticsReporter )
305
+ : this . updateAotProgram (
306
+ compilerOptions ,
307
+ rootNames ,
308
+ host ,
309
+ diagnosticsReporter ,
310
+ state . resourceLoader ,
311
+ ) ;
300
312
301
- // Add all non-declaration files to the initial set of unused files. The set will be
302
- // analyzed and pruned after all Webpack modules are finished building.
303
- if ( ! sourceFile . isDeclarationFile ) {
304
- currentUnused . add ( normalizePath ( sourceFile . fileName ) ) ;
305
- }
313
+ // Set of files used during the unused TypeScript file analysis
314
+ const currentUnused = new Set < string > ( ) ;
315
+
316
+ for ( const sourceFile of builder . getSourceFiles ( ) ) {
317
+ if ( internalFiles ?. has ( sourceFile ) ) {
318
+ continue ;
306
319
}
307
320
308
- compilation . hooks . finishModules . tapPromise ( PLUGIN_NAME , async ( modules ) => {
309
- // Rebuild any remaining AOT required modules
310
- await this . rebuildRequiredFiles ( modules , compilation , fileEmitter ) ;
321
+ // Ensure all program files are considered part of the compilation and will be watched.
322
+ // Webpack does not normalize paths. Therefore, we need to normalize the path with FS seperators.
323
+ compilation . fileDependencies . add ( externalizePath ( sourceFile . fileName ) ) ;
311
324
312
- // Clear out the Webpack compilation to avoid an extra retaining reference
313
- resourceLoader ?. clearParentCompilation ( ) ;
325
+ // Add all non-declaration files to the initial set of unused files. The set will be
326
+ // analyzed and pruned after all Webpack modules are finished building.
327
+ if ( ! sourceFile . isDeclarationFile ) {
328
+ currentUnused . add ( normalizePath ( sourceFile . fileName ) ) ;
329
+ }
330
+ }
314
331
315
- // Analyze program for unused files
316
- if ( compilation . errors . length > 0 ) {
317
- return ;
318
- }
332
+ compilation . hooks . finishModules . tapPromise ( PLUGIN_NAME , async ( modules ) => {
333
+ // Rebuild any remaining AOT required modules
334
+ await this . rebuildRequiredFiles ( modules , compilation , fileEmitter ) ;
319
335
320
- for ( const webpackModule of modules ) {
321
- const resource = ( webpackModule as NormalModule ) . resource ;
322
- if ( resource ) {
323
- this . markResourceUsed ( normalizePath ( resource ) , currentUnused ) ;
324
- }
325
- }
336
+ // Clear out the Webpack compilation to avoid an extra retaining reference
337
+ state . resourceLoader ?. clearParentCompilation ( ) ;
326
338
327
- for ( const unused of currentUnused ) {
328
- if ( previousUnused && previousUnused . has ( unused ) ) {
329
- continue ;
330
- }
331
- addWarning (
332
- compilation ,
333
- ` ${ unused } is part of the TypeScript compilation but it's unused.\n` +
334
- `Add only entry points to the 'files' or 'include' properties in your tsconfig.` ,
335
- ) ;
339
+ // Analyze program for unused files
340
+ if ( compilation . errors . length > 0 ) {
341
+ return ;
342
+ }
343
+
344
+ for ( const webpackModule of modules ) {
345
+ const resource = ( webpackModule as NormalModule ) . resource ;
346
+ if ( resource ) {
347
+ this . markResourceUsed ( normalizePath ( resource ) , currentUnused ) ;
336
348
}
337
- previousUnused = currentUnused ;
338
- } ) ;
349
+ }
339
350
340
- // Store file emitter for loader usage
341
- emitRegistration . update ( fileEmitter ) ;
351
+ for ( const unused of currentUnused ) {
352
+ if ( state . previousUnused ?. has ( unused ) ) {
353
+ continue ;
354
+ }
355
+ addWarning (
356
+ compilation ,
357
+ `${ unused } is part of the TypeScript compilation but it's unused.\n` +
358
+ `Add only entry points to the 'files' or 'include' properties in your tsconfig.` ,
359
+ ) ;
360
+ }
361
+ state . previousUnused = currentUnused ;
342
362
} ) ;
363
+
364
+ // Store file emitter for loader usage
365
+ emitRegistration . update ( fileEmitter ) ;
343
366
}
344
367
345
368
private registerWithCompilation ( compilation : Compilation ) {
0 commit comments