@@ -90,6 +90,8 @@ export function createCompilerPlugin(
90
90
const compilation : AngularCompilation = pluginOptions . noopTypeScriptCompilation
91
91
? new NoopCompilation ( )
92
92
: await createAngularCompilation ( ! ! pluginOptions . jit ) ;
93
+ // Compilation is initially assumed to have errors until emitted
94
+ let hasCompilationErrors = true ;
93
95
94
96
// Determines if TypeScript should process JavaScript files based on tsconfig `allowJs` option
95
97
let shouldTsIgnoreJs = true ;
@@ -233,66 +235,32 @@ export function createCompilerPlugin(
233
235
234
236
// Initialize the Angular compilation for the current build.
235
237
// In watch mode, previous build state will be reused.
236
- const {
237
- compilerOptions : { allowJs } ,
238
- referencedFiles,
239
- } = await compilation . initialize ( tsconfigPath , hostOptions , ( compilerOptions ) => {
240
- // target of 9 is ES2022 (using the number avoids an expensive import of typescript just for an enum)
241
- if ( compilerOptions . target === undefined || compilerOptions . target < 9 ) {
242
- // If 'useDefineForClassFields' is already defined in the users project leave the value as is.
243
- // Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995
244
- // which breaks the deprecated `@Effects` NGRX decorator and potentially other existing code as well.
245
- compilerOptions . target = 9 ;
246
- compilerOptions . useDefineForClassFields ??= false ;
247
-
248
- // Only add the warning on the initial build
249
- setupWarnings ?. push ( {
250
- text :
251
- 'TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and ' +
252
- '"false" respectively by the Angular CLI.' ,
253
- location : { file : pluginOptions . tsconfig } ,
254
- notes : [
255
- {
256
- text :
257
- 'To control ECMA version and features use the Browerslist configuration. ' +
258
- 'For more information, see https://angular.io/guide/build#configuring-browser-compatibility' ,
259
- } ,
260
- ] ,
261
- } ) ;
262
- }
263
-
264
- if ( compilerOptions . compilationMode === 'partial' ) {
265
- setupWarnings ?. push ( {
266
- text : 'Angular partial compilation mode is not supported when building applications.' ,
267
- location : null ,
268
- notes : [ { text : 'Full compilation mode will be used instead.' } ] ,
269
- } ) ;
270
- compilerOptions . compilationMode = 'full' ;
271
- }
238
+ let referencedFiles ;
239
+ try {
240
+ const initializationResult = await compilation . initialize (
241
+ tsconfigPath ,
242
+ hostOptions ,
243
+ createCompilerOptionsTransformer ( setupWarnings , pluginOptions , preserveSymlinks ) ,
244
+ ) ;
245
+ shouldTsIgnoreJs = ! initializationResult . compilerOptions . allowJs ;
246
+ referencedFiles = initializationResult . referencedFiles ;
247
+ } catch ( error ) {
248
+ ( result . errors ??= [ ] ) . push ( {
249
+ text : 'Angular compilation initialization failed.' ,
250
+ location : null ,
251
+ notes : [
252
+ {
253
+ text : error instanceof Error ? error . stack ?? error . message : `${ error } ` ,
254
+ location : null ,
255
+ } ,
256
+ ] ,
257
+ } ) ;
272
258
273
- // Enable incremental compilation by default if caching is enabled
274
- if ( pluginOptions . sourceFileCache ?. persistentCachePath ) {
275
- compilerOptions . incremental ??= true ;
276
- // Set the build info file location to the configured cache directory
277
- compilerOptions . tsBuildInfoFile = path . join (
278
- pluginOptions . sourceFileCache ?. persistentCachePath ,
279
- '.tsbuildinfo' ,
280
- ) ;
281
- } else {
282
- compilerOptions . incremental = false ;
283
- }
259
+ // Initialization failure prevents further compilation steps
260
+ hasCompilationErrors = true ;
284
261
285
- return {
286
- ...compilerOptions ,
287
- noEmitOnError : false ,
288
- inlineSources : pluginOptions . sourcemap ,
289
- inlineSourceMap : pluginOptions . sourcemap ,
290
- mapRoot : undefined ,
291
- sourceRoot : undefined ,
292
- preserveSymlinks,
293
- } ;
294
- } ) ;
295
- shouldTsIgnoreJs = ! allowJs ;
262
+ return result ;
263
+ }
296
264
297
265
if ( compilation instanceof NoopCompilation ) {
298
266
await sharedTSCompilationState . waitUntilReady ;
@@ -301,19 +269,32 @@ export function createCompilerPlugin(
301
269
}
302
270
303
271
const diagnostics = await compilation . diagnoseFiles ( ) ;
304
- if ( diagnostics . errors ) {
272
+ if ( diagnostics . errors ?. length ) {
305
273
( result . errors ??= [ ] ) . push ( ...diagnostics . errors ) ;
306
274
}
307
- if ( diagnostics . warnings ) {
275
+ if ( diagnostics . warnings ?. length ) {
308
276
( result . warnings ??= [ ] ) . push ( ...diagnostics . warnings ) ;
309
277
}
310
278
311
279
// Update TypeScript file output cache for all affected files
312
- await profileAsync ( 'NG_EMIT_TS' , async ( ) => {
313
- for ( const { filename, contents } of await compilation . emitAffectedFiles ( ) ) {
314
- typeScriptFileCache . set ( pathToFileURL ( filename ) . href , contents ) ;
315
- }
316
- } ) ;
280
+ try {
281
+ await profileAsync ( 'NG_EMIT_TS' , async ( ) => {
282
+ for ( const { filename, contents } of await compilation . emitAffectedFiles ( ) ) {
283
+ typeScriptFileCache . set ( pathToFileURL ( filename ) . href , contents ) ;
284
+ }
285
+ } ) ;
286
+ } catch ( error ) {
287
+ ( result . errors ??= [ ] ) . push ( {
288
+ text : 'Angular compilation emit failed.' ,
289
+ location : null ,
290
+ notes : [
291
+ {
292
+ text : error instanceof Error ? error . stack ?? error . message : `${ error } ` ,
293
+ location : null ,
294
+ } ,
295
+ ] ,
296
+ } ) ;
297
+ }
317
298
318
299
// Add errors from failed additional results.
319
300
// This must be done after emit to capture latest web worker results.
@@ -331,6 +312,8 @@ export function createCompilerPlugin(
331
312
] ;
332
313
}
333
314
315
+ hasCompilationErrors = ! ! result . errors ?. length ;
316
+
334
317
// Reset the setup warnings so that they are only shown during the first build.
335
318
setupWarnings = undefined ;
336
319
@@ -354,6 +337,12 @@ export function createCompilerPlugin(
354
337
let contents = typeScriptFileCache . get ( pathToFileURL ( request ) . href ) ;
355
338
356
339
if ( contents === undefined ) {
340
+ // If the Angular compilation had errors the file may not have been emitted.
341
+ // To avoid additional errors about missing files, return empty contents.
342
+ if ( hasCompilationErrors ) {
343
+ return { contents : '' , loader : 'js' } ;
344
+ }
345
+
357
346
// No TS result indicates the file is not part of the TypeScript program.
358
347
// If allowJs is enabled and the file is JS then defer to the next load hook.
359
348
if ( ! shouldTsIgnoreJs && / \. [ c m ] ? j s $ / . test ( request ) ) {
@@ -446,6 +435,69 @@ export function createCompilerPlugin(
446
435
} ;
447
436
}
448
437
438
+ function createCompilerOptionsTransformer (
439
+ setupWarnings : PartialMessage [ ] | undefined ,
440
+ pluginOptions : CompilerPluginOptions ,
441
+ preserveSymlinks : boolean | undefined ,
442
+ ) : Parameters < AngularCompilation [ 'initialize' ] > [ 2 ] {
443
+ return ( compilerOptions ) => {
444
+ // target of 9 is ES2022 (using the number avoids an expensive import of typescript just for an enum)
445
+ if ( compilerOptions . target === undefined || compilerOptions . target < 9 ) {
446
+ // If 'useDefineForClassFields' is already defined in the users project leave the value as is.
447
+ // Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995
448
+ // which breaks the deprecated `@Effects` NGRX decorator and potentially other existing code as well.
449
+ compilerOptions . target = 9 ;
450
+ compilerOptions . useDefineForClassFields ??= false ;
451
+
452
+ // Only add the warning on the initial build
453
+ setupWarnings ?. push ( {
454
+ text :
455
+ 'TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and ' +
456
+ '"false" respectively by the Angular CLI.' ,
457
+ location : { file : pluginOptions . tsconfig } ,
458
+ notes : [
459
+ {
460
+ text :
461
+ 'To control ECMA version and features use the Browerslist configuration. ' +
462
+ 'For more information, see https://angular.io/guide/build#configuring-browser-compatibility' ,
463
+ } ,
464
+ ] ,
465
+ } ) ;
466
+ }
467
+
468
+ if ( compilerOptions . compilationMode === 'partial' ) {
469
+ setupWarnings ?. push ( {
470
+ text : 'Angular partial compilation mode is not supported when building applications.' ,
471
+ location : null ,
472
+ notes : [ { text : 'Full compilation mode will be used instead.' } ] ,
473
+ } ) ;
474
+ compilerOptions . compilationMode = 'full' ;
475
+ }
476
+
477
+ // Enable incremental compilation by default if caching is enabled
478
+ if ( pluginOptions . sourceFileCache ?. persistentCachePath ) {
479
+ compilerOptions . incremental ??= true ;
480
+ // Set the build info file location to the configured cache directory
481
+ compilerOptions . tsBuildInfoFile = path . join (
482
+ pluginOptions . sourceFileCache ?. persistentCachePath ,
483
+ '.tsbuildinfo' ,
484
+ ) ;
485
+ } else {
486
+ compilerOptions . incremental = false ;
487
+ }
488
+
489
+ return {
490
+ ...compilerOptions ,
491
+ noEmitOnError : false ,
492
+ inlineSources : pluginOptions . sourcemap ,
493
+ inlineSourceMap : pluginOptions . sourcemap ,
494
+ mapRoot : undefined ,
495
+ sourceRoot : undefined ,
496
+ preserveSymlinks,
497
+ } ;
498
+ } ;
499
+ }
500
+
449
501
function bundleWebWorker (
450
502
build : PluginBuild ,
451
503
pluginOptions : CompilerPluginOptions ,
0 commit comments