@@ -165,15 +165,13 @@ export function createCompilerPlugin(
165165 name : 'angular-compiler' ,
166166 // eslint-disable-next-line max-lines-per-function
167167 async setup ( build : PluginBuild ) : Promise < void > {
168- let setupWarnings : PartialMessage [ ] | undefined ;
168+ let setupWarnings : PartialMessage [ ] | undefined = [ ] ;
169169
170170 // Initialize a worker pool for JavaScript transformations
171171 const javascriptTransformer = new JavaScriptTransformer ( pluginOptions , maxWorkers ) ;
172172
173- const { GLOBAL_DEFS_FOR_TERSER_WITH_AOT , readConfiguration } =
174- await AngularCompilation . loadCompilerCli ( ) ;
175-
176173 // Setup defines based on the values provided by the Angular compiler-cli
174+ const { GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = await AngularCompilation . loadCompilerCli ( ) ;
177175 build . initialOptions . define ??= { } ;
178176 for ( const [ key , value ] of Object . entries ( GLOBAL_DEFS_FOR_TERSER_WITH_AOT ) ) {
179177 if ( key in build . initialOptions . define ) {
@@ -189,71 +187,26 @@ export function createCompilerPlugin(
189187 build . initialOptions . define [ key ] = value . toString ( ) ;
190188 }
191189
192- // The tsconfig is loaded in setup instead of in start to allow the esbuild target build option to be modified.
193- // esbuild build options can only be modified in setup prior to starting the build.
194- const {
195- options : compilerOptions ,
196- rootNames,
197- errors : configurationDiagnostics ,
198- } = profileSync ( 'NG_READ_CONFIG' , ( ) =>
199- readConfiguration ( pluginOptions . tsconfig , {
200- noEmitOnError : false ,
201- suppressOutputPathCheck : true ,
202- outDir : undefined ,
203- inlineSources : pluginOptions . sourcemap ,
204- inlineSourceMap : pluginOptions . sourcemap ,
205- sourceMap : false ,
206- mapRoot : undefined ,
207- sourceRoot : undefined ,
208- declaration : false ,
209- declarationMap : false ,
210- allowEmptyCodegenFiles : false ,
211- annotationsAs : 'decorators' ,
212- enableResourceInlining : false ,
213- } ) ,
214- ) ;
215-
216- if ( compilerOptions . target === undefined || compilerOptions . target < ts . ScriptTarget . ES2022 ) {
217- // If 'useDefineForClassFields' is already defined in the users project leave the value as is.
218- // Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995
219- // which breaks the deprecated `@Effects` NGRX decorator and potentially other existing code as well.
220- compilerOptions . target = ts . ScriptTarget . ES2022 ;
221- compilerOptions . useDefineForClassFields ??= false ;
222-
223- ( setupWarnings ??= [ ] ) . push ( {
224- text :
225- 'TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and ' +
226- '"false" respectively by the Angular CLI.\n' +
227- `NOTE: You can set the "target" to "ES2022" in the project's tsconfig to remove this warning.` ,
228- location : { file : pluginOptions . tsconfig } ,
229- notes : [
230- {
231- text :
232- 'To control ECMA version and features use the Browerslist configuration. ' +
233- 'For more information, see https://angular.io/guide/build#configuring-browser-compatibility' ,
234- } ,
235- ] ,
236- } ) ;
237- }
238-
239190 // The file emitter created during `onStart` that will be used during the build in `onLoad` callbacks for TS files
240191 let fileEmitter : FileEmitter | undefined ;
241192
242193 // The stylesheet resources from component stylesheets that will be added to the build results output files
243194 let stylesheetResourceFiles : OutputFile [ ] = [ ] ;
244-
245195 let stylesheetMetafiles : Metafile [ ] ;
246196
247- let compilation : AngularCompilation | undefined ;
197+ // Create new reusable compilation for the appropriate mode based on the `jit` plugin option
198+ const compilation : AngularCompilation = pluginOptions . jit
199+ ? new JitCompilation ( )
200+ : new AotCompilation ( ) ;
201+
202+ // Determines if TypeScript should process JavaScript files based on tsconfig `allowJs` option
203+ let shouldTsIgnoreJs = true ;
248204
249205 build . onStart ( async ( ) => {
250206 const result : OnStartResult = {
251207 warnings : setupWarnings ,
252208 } ;
253209
254- // Reset the setup warnings so that they are only shown during the first build.
255- setupWarnings = undefined ;
256-
257210 // Reset debug performance tracking
258211 resetCumulativeDurations ( ) ;
259212
@@ -293,21 +246,48 @@ export function createCompilerPlugin(
293246 } ,
294247 } ;
295248
296- // Create new compilation if first build; otherwise, use existing for rebuilds
297- if ( pluginOptions . jit ) {
298- compilation ??= new JitCompilation ( ) ;
299- } else {
300- compilation ??= new AotCompilation ( ) ;
301- }
302-
303249 // Initialize the Angular compilation for the current build.
304250 // In watch mode, previous build state will be reused.
305- const { affectedFiles } = await compilation . initialize (
306- rootNames ,
307- compilerOptions ,
308- hostOptions ,
309- configurationDiagnostics ,
310- ) ;
251+ const {
252+ affectedFiles,
253+ compilerOptions : { allowJs } ,
254+ } = await compilation . initialize ( pluginOptions . tsconfig , hostOptions , ( compilerOptions ) => {
255+ if (
256+ compilerOptions . target === undefined ||
257+ compilerOptions . target < ts . ScriptTarget . ES2022
258+ ) {
259+ // If 'useDefineForClassFields' is already defined in the users project leave the value as is.
260+ // Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995
261+ // which breaks the deprecated `@Effects` NGRX decorator and potentially other existing code as well.
262+ compilerOptions . target = ts . ScriptTarget . ES2022 ;
263+ compilerOptions . useDefineForClassFields ??= false ;
264+
265+ // Only add the warning on the initial build
266+ setupWarnings ?. push ( {
267+ text :
268+ 'TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and ' +
269+ '"false" respectively by the Angular CLI.' ,
270+ location : { file : pluginOptions . tsconfig } ,
271+ notes : [
272+ {
273+ text :
274+ 'To control ECMA version and features use the Browerslist configuration. ' +
275+ 'For more information, see https://angular.io/guide/build#configuring-browser-compatibility' ,
276+ } ,
277+ ] ,
278+ } ) ;
279+ }
280+
281+ return {
282+ ...compilerOptions ,
283+ noEmitOnError : false ,
284+ inlineSources : pluginOptions . sourcemap ,
285+ inlineSourceMap : pluginOptions . sourcemap ,
286+ mapRoot : undefined ,
287+ sourceRoot : undefined ,
288+ } ;
289+ } ) ;
290+ shouldTsIgnoreJs = ! allowJs ;
311291
312292 // Clear affected files from the cache (if present)
313293 if ( pluginOptions . sourceFileCache ) {
@@ -319,8 +299,7 @@ export function createCompilerPlugin(
319299 }
320300
321301 profileSync ( 'NG_DIAGNOSTICS_TOTAL' , ( ) => {
322- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
323- for ( const diagnostic of compilation ! . collectDiagnostics ( ) ) {
302+ for ( const diagnostic of compilation . collectDiagnostics ( ) ) {
324303 const message = convertTypeScriptDiagnostic ( diagnostic ) ;
325304 if ( diagnostic . category === ts . DiagnosticCategory . Error ) {
326305 ( result . errors ??= [ ] ) . push ( message ) ;
@@ -332,67 +311,73 @@ export function createCompilerPlugin(
332311
333312 fileEmitter = compilation . createFileEmitter ( ) ;
334313
314+ // Reset the setup warnings so that they are only shown during the first build.
315+ setupWarnings = undefined ;
316+
335317 return result ;
336318 } ) ;
337319
338- build . onLoad (
339- { filter : compilerOptions . allowJs ? / \. [ c m ] ? [ j t ] s x ? $ / : / \. [ c m ] ? t s x ? $ / } ,
340- ( args ) =>
341- profileAsync (
342- 'NG_EMIT_TS*' ,
343- async ( ) => {
344- assert . ok ( fileEmitter , 'Invalid plugin execution order' ) ;
345-
346- const request = pluginOptions . fileReplacements ?. [ args . path ] ?? args . path ;
347-
348- // The filename is currently used as a cache key. Since the cache is memory only,
349- // the options cannot change and do not need to be represented in the key. If the
350- // cache is later stored to disk, then the options that affect transform output
351- // would need to be added to the key as well as a check for any change of content.
352- let contents = pluginOptions . sourceFileCache ?. typeScriptFileCache . get (
353- pathToFileURL ( request ) . href ,
354- ) ;
320+ build . onLoad ( { filter : / \. [ c m ] ? [ j t ] s x ? $ / } , ( args ) =>
321+ profileAsync (
322+ 'NG_EMIT_TS*' ,
323+ async ( ) => {
324+ assert . ok ( fileEmitter , 'Invalid plugin execution order' ) ;
355325
356- if ( contents === undefined ) {
357- const typescriptResult = await fileEmitter ( request ) ;
358- if ( ! typescriptResult ?. content ) {
359- // No TS result indicates the file is not part of the TypeScript program.
360- // If allowJs is enabled and the file is JS then defer to the next load hook.
361- if ( compilerOptions . allowJs && / \. [ c m ] ? j s $ / . test ( request ) ) {
362- return undefined ;
363- }
364-
365- // Otherwise return an error
366- return {
367- errors : [
368- createMissingFileError (
369- request ,
370- args . path ,
371- build . initialOptions . absWorkingDir ?? '' ,
372- ) ,
373- ] ,
374- } ;
375- }
326+ const request = pluginOptions . fileReplacements ?. [ args . path ] ?? args . path ;
376327
377- contents = await javascriptTransformer . transformData (
378- request ,
379- typescriptResult . content ,
380- true /* skipLinker */ ,
381- ) ;
328+ // Skip TS load attempt if JS TypeScript compilation not enabled and file is JS
329+ if ( shouldTsIgnoreJs && / \. [ c m ] ? j s $ / . test ( request ) ) {
330+ return undefined ;
331+ }
382332
383- pluginOptions . sourceFileCache ?. typeScriptFileCache . set (
384- pathToFileURL ( request ) . href ,
385- contents ,
386- ) ;
333+ // The filename is currently used as a cache key. Since the cache is memory only,
334+ // the options cannot change and do not need to be represented in the key. If the
335+ // cache is later stored to disk, then the options that affect transform output
336+ // would need to be added to the key as well as a check for any change of content.
337+ let contents = pluginOptions . sourceFileCache ?. typeScriptFileCache . get (
338+ pathToFileURL ( request ) . href ,
339+ ) ;
340+
341+ if ( contents === undefined ) {
342+ const typescriptResult = await fileEmitter ( request ) ;
343+ if ( ! typescriptResult ?. content ) {
344+ // No TS result indicates the file is not part of the TypeScript program.
345+ // If allowJs is enabled and the file is JS then defer to the next load hook.
346+ if ( ! shouldTsIgnoreJs && / \. [ c m ] ? j s $ / . test ( request ) ) {
347+ return undefined ;
348+ }
349+
350+ // Otherwise return an error
351+ return {
352+ errors : [
353+ createMissingFileError (
354+ request ,
355+ args . path ,
356+ build . initialOptions . absWorkingDir ?? '' ,
357+ ) ,
358+ ] ,
359+ } ;
387360 }
388361
389- return {
362+ contents = await javascriptTransformer . transformData (
363+ request ,
364+ typescriptResult . content ,
365+ true /* skipLinker */ ,
366+ ) ;
367+
368+ pluginOptions . sourceFileCache ?. typeScriptFileCache . set (
369+ pathToFileURL ( request ) . href ,
390370 contents ,
391- loader : 'js' ,
392- } ;
393- } ,
394- true ,
395- ) ,
371+ ) ;
372+ }
373+
374+ return {
375+ contents,
376+ loader : 'js' ,
377+ } ;
378+ } ,
379+ true ,
380+ ) ,
396381 ) ;
397382
398383 build . onLoad ( { filter : / \. [ c m ] ? j s $ / } , ( args ) =>
0 commit comments