@@ -165,15 +165,13 @@ export function createCompilerPlugin(
165
165
name : 'angular-compiler' ,
166
166
// eslint-disable-next-line max-lines-per-function
167
167
async setup ( build : PluginBuild ) : Promise < void > {
168
- let setupWarnings : PartialMessage [ ] | undefined ;
168
+ let setupWarnings : PartialMessage [ ] | undefined = [ ] ;
169
169
170
170
// Initialize a worker pool for JavaScript transformations
171
171
const javascriptTransformer = new JavaScriptTransformer ( pluginOptions , maxWorkers ) ;
172
172
173
- const { GLOBAL_DEFS_FOR_TERSER_WITH_AOT , readConfiguration } =
174
- await AngularCompilation . loadCompilerCli ( ) ;
175
-
176
173
// Setup defines based on the values provided by the Angular compiler-cli
174
+ const { GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = await AngularCompilation . loadCompilerCli ( ) ;
177
175
build . initialOptions . define ??= { } ;
178
176
for ( const [ key , value ] of Object . entries ( GLOBAL_DEFS_FOR_TERSER_WITH_AOT ) ) {
179
177
if ( key in build . initialOptions . define ) {
@@ -189,71 +187,26 @@ export function createCompilerPlugin(
189
187
build . initialOptions . define [ key ] = value . toString ( ) ;
190
188
}
191
189
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
-
239
190
// The file emitter created during `onStart` that will be used during the build in `onLoad` callbacks for TS files
240
191
let fileEmitter : FileEmitter | undefined ;
241
192
242
193
// The stylesheet resources from component stylesheets that will be added to the build results output files
243
194
let stylesheetResourceFiles : OutputFile [ ] = [ ] ;
244
-
245
195
let stylesheetMetafiles : Metafile [ ] ;
246
196
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 ;
248
204
249
205
build . onStart ( async ( ) => {
250
206
const result : OnStartResult = {
251
207
warnings : setupWarnings ,
252
208
} ;
253
209
254
- // Reset the setup warnings so that they are only shown during the first build.
255
- setupWarnings = undefined ;
256
-
257
210
// Reset debug performance tracking
258
211
resetCumulativeDurations ( ) ;
259
212
@@ -293,21 +246,48 @@ export function createCompilerPlugin(
293
246
} ,
294
247
} ;
295
248
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
-
303
249
// Initialize the Angular compilation for the current build.
304
250
// 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 ;
311
291
312
292
// Clear affected files from the cache (if present)
313
293
if ( pluginOptions . sourceFileCache ) {
@@ -319,8 +299,7 @@ export function createCompilerPlugin(
319
299
}
320
300
321
301
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 ( ) ) {
324
303
const message = convertTypeScriptDiagnostic ( diagnostic ) ;
325
304
if ( diagnostic . category === ts . DiagnosticCategory . Error ) {
326
305
( result . errors ??= [ ] ) . push ( message ) ;
@@ -332,67 +311,73 @@ export function createCompilerPlugin(
332
311
333
312
fileEmitter = compilation . createFileEmitter ( ) ;
334
313
314
+ // Reset the setup warnings so that they are only shown during the first build.
315
+ setupWarnings = undefined ;
316
+
335
317
return result ;
336
318
} ) ;
337
319
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' ) ;
355
325
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 ;
376
327
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
+ }
382
332
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
+ } ;
387
360
}
388
361
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 ,
390
370
contents ,
391
- loader : 'js' ,
392
- } ;
393
- } ,
394
- true ,
395
- ) ,
371
+ ) ;
372
+ }
373
+
374
+ return {
375
+ contents,
376
+ loader : 'js' ,
377
+ } ;
378
+ } ,
379
+ true ,
380
+ ) ,
396
381
) ;
397
382
398
383
build . onLoad ( { filter : / \. [ c m ] ? j s $ / } , ( args ) =>
0 commit comments