@@ -90,6 +90,8 @@ export function createCompilerPlugin(
9090 const compilation : AngularCompilation = pluginOptions . noopTypeScriptCompilation
9191 ? new NoopCompilation ( )
9292 : await createAngularCompilation ( ! ! pluginOptions . jit ) ;
93+ // Compilation is initially assumed to have errors until emitted
94+ let hasCompilationErrors = true ;
9395
9496 // Determines if TypeScript should process JavaScript files based on tsconfig `allowJs` option
9597 let shouldTsIgnoreJs = true ;
@@ -233,66 +235,32 @@ export function createCompilerPlugin(
233235
234236 // Initialize the Angular compilation for the current build.
235237 // 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+ } ) ;
272258
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 ;
284261
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+ }
296264
297265 if ( compilation instanceof NoopCompilation ) {
298266 await sharedTSCompilationState . waitUntilReady ;
@@ -301,19 +269,32 @@ export function createCompilerPlugin(
301269 }
302270
303271 const diagnostics = await compilation . diagnoseFiles ( ) ;
304- if ( diagnostics . errors ) {
272+ if ( diagnostics . errors ?. length ) {
305273 ( result . errors ??= [ ] ) . push ( ...diagnostics . errors ) ;
306274 }
307- if ( diagnostics . warnings ) {
275+ if ( diagnostics . warnings ?. length ) {
308276 ( result . warnings ??= [ ] ) . push ( ...diagnostics . warnings ) ;
309277 }
310278
311279 // 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+ }
317298
318299 // Add errors from failed additional results.
319300 // This must be done after emit to capture latest web worker results.
@@ -331,6 +312,8 @@ export function createCompilerPlugin(
331312 ] ;
332313 }
333314
315+ hasCompilationErrors = ! ! result . errors ?. length ;
316+
334317 // Reset the setup warnings so that they are only shown during the first build.
335318 setupWarnings = undefined ;
336319
@@ -354,6 +337,12 @@ export function createCompilerPlugin(
354337 let contents = typeScriptFileCache . get ( pathToFileURL ( request ) . href ) ;
355338
356339 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+
357346 // No TS result indicates the file is not part of the TypeScript program.
358347 // If allowJs is enabled and the file is JS then defer to the next load hook.
359348 if ( ! shouldTsIgnoreJs && / \. [ c m ] ? j s $ / . test ( request ) ) {
@@ -446,6 +435,69 @@ export function createCompilerPlugin(
446435 } ;
447436}
448437
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+
449501function bundleWebWorker (
450502 build : PluginBuild ,
451503 pluginOptions : CompilerPluginOptions ,
0 commit comments