7
7
*/
8
8
9
9
import type {
10
+ BuildFailure ,
10
11
Metafile ,
11
12
OnStartResult ,
12
13
OutputFile ,
@@ -33,6 +34,7 @@ import { AngularHostOptions } from './angular-host';
33
34
import { AngularCompilation , AotCompilation , JitCompilation , NoopCompilation } from './compilation' ;
34
35
import { SharedTSCompilationState , getSharedCompilationState } from './compilation-state' ;
35
36
import { ComponentStylesheetBundler } from './component-stylesheets' ;
37
+ import { FileReferenceTracker } from './file-reference-tracker' ;
36
38
import { setupJitPluginCallbacks } from './jit-plugin-callbacks' ;
37
39
import { SourceFileCache } from './source-file-cache' ;
38
40
@@ -84,9 +86,11 @@ export function createCompilerPlugin(
84
86
pluginOptions . sourceFileCache ?. typeScriptFileCache ??
85
87
new Map < string , string | Uint8Array > ( ) ;
86
88
87
- // The stylesheet resources from component stylesheets that will be added to the build results output files
88
- let additionalOutputFiles : OutputFile [ ] = [ ] ;
89
- let additionalMetafiles : Metafile [ ] ;
89
+ // The resources from component stylesheets and web workers that will be added to the build results output files
90
+ const additionalResults = new Map <
91
+ string ,
92
+ { outputFiles ?: OutputFile [ ] ; metafile ?: Metafile ; errors ?: PartialMessage [ ] }
93
+ > ( ) ;
90
94
91
95
// Create new reusable compilation for the appropriate mode based on the `jit` plugin option
92
96
const compilation : AngularCompilation = pluginOptions . noopTypeScriptCompilation
@@ -106,6 +110,10 @@ export function createCompilerPlugin(
106
110
) ;
107
111
let sharedTSCompilationState : SharedTSCompilationState | undefined ;
108
112
113
+ // To fully invalidate files, track resource referenced files and their referencing source
114
+ const referencedFileTracker = new FileReferenceTracker ( ) ;
115
+
116
+ // eslint-disable-next-line max-lines-per-function
109
117
build . onStart ( async ( ) => {
110
118
sharedTSCompilationState = getSharedCompilationState ( ) ;
111
119
if ( ! ( compilation instanceof NoopCompilation ) ) {
@@ -119,14 +127,24 @@ export function createCompilerPlugin(
119
127
// Reset debug performance tracking
120
128
resetCumulativeDurations ( ) ;
121
129
122
- // Reset additional output files
123
- additionalOutputFiles = [ ] ;
124
- additionalMetafiles = [ ] ;
130
+ // Update the reference tracker and generate a full set of modified files for the
131
+ // Angular compiler which does not have direct knowledge of transitive resource
132
+ // dependencies or web worker processing.
133
+ let modifiedFiles ;
134
+ if (
135
+ pluginOptions . sourceFileCache ?. modifiedFiles . size &&
136
+ referencedFileTracker &&
137
+ ! pluginOptions . noopTypeScriptCompilation
138
+ ) {
139
+ // TODO: Differentiate between changed input files and stale output files
140
+ modifiedFiles = referencedFileTracker . update ( pluginOptions . sourceFileCache . modifiedFiles ) ;
141
+ pluginOptions . sourceFileCache . invalidate ( modifiedFiles ) ;
142
+ }
125
143
126
144
// Create Angular compiler host options
127
145
const hostOptions : AngularHostOptions = {
128
146
fileReplacements : pluginOptions . fileReplacements ,
129
- modifiedFiles : pluginOptions . sourceFileCache ?. modifiedFiles ,
147
+ modifiedFiles,
130
148
sourceFileCache : pluginOptions . sourceFileCache ,
131
149
async transformStylesheet ( data , containingFile , stylesheetFile ) {
132
150
let stylesheetResult ;
@@ -142,14 +160,22 @@ export function createCompilerPlugin(
142
160
) ;
143
161
}
144
162
145
- const { contents, resourceFiles, errors, warnings } = stylesheetResult ;
163
+ const { contents, resourceFiles, referencedFiles , errors, warnings } = stylesheetResult ;
146
164
if ( errors ) {
147
165
( result . errors ??= [ ] ) . push ( ...errors ) ;
148
166
}
149
167
( result . warnings ??= [ ] ) . push ( ...warnings ) ;
150
- additionalOutputFiles . push ( ...resourceFiles ) ;
151
- if ( stylesheetResult . metafile ) {
152
- additionalMetafiles . push ( stylesheetResult . metafile ) ;
168
+ additionalResults . set ( stylesheetFile ?? containingFile , {
169
+ outputFiles : resourceFiles ,
170
+ metafile : stylesheetResult . metafile ,
171
+ } ) ;
172
+
173
+ if ( referencedFiles ) {
174
+ referencedFileTracker . add ( containingFile , referencedFiles ) ;
175
+ if ( stylesheetFile ) {
176
+ // Angular AOT compiler needs modified direct resource files to correctly invalidate its analysis
177
+ referencedFileTracker . add ( stylesheetFile , referencedFiles ) ;
178
+ }
153
179
}
154
180
155
181
return contents ;
@@ -159,37 +185,38 @@ export function createCompilerPlugin(
159
185
// The synchronous API must be used due to the TypeScript compilation currently being
160
186
// fully synchronous and this process callback being called from within a TypeScript
161
187
// transformer.
162
- const workerResult = build . esbuild . buildSync ( {
163
- platform : 'browser' ,
164
- write : false ,
165
- bundle : true ,
166
- metafile : true ,
167
- format : 'esm' ,
168
- mainFields : [ 'es2020' , 'es2015' , 'browser' , 'module' , 'main' ] ,
169
- sourcemap : pluginOptions . sourcemap ,
170
- entryNames : 'worker-[hash]' ,
171
- entryPoints : [ fullWorkerPath ] ,
172
- absWorkingDir : build . initialOptions . absWorkingDir ,
173
- outdir : build . initialOptions . outdir ,
174
- minifyIdentifiers : build . initialOptions . minifyIdentifiers ,
175
- minifySyntax : build . initialOptions . minifySyntax ,
176
- minifyWhitespace : build . initialOptions . minifyWhitespace ,
177
- target : build . initialOptions . target ,
178
- } ) ;
188
+ const workerResult = bundleWebWorker ( build , pluginOptions , fullWorkerPath ) ;
179
189
180
190
( result . warnings ??= [ ] ) . push ( ...workerResult . warnings ) ;
181
- additionalOutputFiles . push ( ...workerResult . outputFiles ) ;
182
- if ( workerResult . metafile ) {
183
- additionalMetafiles . push ( workerResult . metafile ) ;
184
- }
185
-
186
191
if ( workerResult . errors . length > 0 ) {
187
192
( result . errors ??= [ ] ) . push ( ...workerResult . errors ) ;
193
+ // Track worker file errors to allow rebuilds on changes
194
+ referencedFileTracker . add (
195
+ containingFile ,
196
+ workerResult . errors
197
+ . map ( ( error ) => error . location ?. file )
198
+ . filter ( ( file ) : file is string => ! ! file )
199
+ . map ( ( file ) => path . join ( build . initialOptions . absWorkingDir ?? '' , file ) ) ,
200
+ ) ;
201
+ additionalResults . set ( fullWorkerPath , { errors : result . errors } ) ;
188
202
189
203
// Return the original path if the build failed
190
204
return workerFile ;
191
205
}
192
206
207
+ assert ( 'outputFiles' in workerResult , 'Invalid web worker bundle result.' ) ;
208
+ additionalResults . set ( fullWorkerPath , {
209
+ outputFiles : workerResult . outputFiles ,
210
+ metafile : workerResult . metafile ,
211
+ } ) ;
212
+
213
+ referencedFileTracker . add (
214
+ containingFile ,
215
+ Object . keys ( workerResult . metafile . inputs ) . map ( ( input ) =>
216
+ path . join ( build . initialOptions . absWorkingDir ?? '' , input ) ,
217
+ ) ,
218
+ ) ;
219
+
193
220
// Return bundled worker file entry name to be used in the built output
194
221
const workerCodeFile = workerResult . outputFiles . find ( ( file ) =>
195
222
file . path . endsWith ( '.js' ) ,
@@ -277,9 +304,20 @@ export function createCompilerPlugin(
277
304
}
278
305
} ) ;
279
306
307
+ // Add errors from failed additional results.
308
+ // This must be done after emit to capture latest web worker results.
309
+ for ( const { errors } of additionalResults . values ( ) ) {
310
+ if ( errors ) {
311
+ ( result . errors ??= [ ] ) . push ( ...errors ) ;
312
+ }
313
+ }
314
+
280
315
// Store referenced files for updated file watching if enabled
281
316
if ( pluginOptions . sourceFileCache ) {
282
- pluginOptions . sourceFileCache . referencedFiles = referencedFiles ;
317
+ pluginOptions . sourceFileCache . referencedFiles = [
318
+ ...referencedFiles ,
319
+ ...referencedFileTracker . referencedFiles ,
320
+ ] ;
283
321
}
284
322
285
323
// Reset the setup warnings so that they are only shown during the first build.
@@ -363,20 +401,20 @@ export function createCompilerPlugin(
363
401
setupJitPluginCallbacks (
364
402
build ,
365
403
stylesheetBundler ,
366
- additionalOutputFiles ,
404
+ additionalResults ,
367
405
styleOptions . inlineStyleLanguage ,
368
406
) ;
369
407
}
370
408
371
409
build . onEnd ( ( result ) => {
372
- // Add any additional output files to the main output files
373
- if ( additionalOutputFiles . length ) {
374
- result . outputFiles ?. push ( ...additionalOutputFiles ) ;
375
- }
410
+ for ( const { outputFiles, metafile } of additionalResults . values ( ) ) {
411
+ // Add any additional output files to the main output files
412
+ if ( outputFiles ?. length ) {
413
+ result . outputFiles ?. push ( ...outputFiles ) ;
414
+ }
376
415
377
- // Combine additional metafiles with main metafile
378
- if ( result . metafile && additionalMetafiles . length ) {
379
- for ( const metafile of additionalMetafiles ) {
416
+ // Combine additional metafiles with main metafile
417
+ if ( result . metafile && metafile ) {
380
418
result . metafile . inputs = { ...result . metafile . inputs , ...metafile . inputs } ;
381
419
result . metafile . outputs = { ...result . metafile . outputs , ...metafile . outputs } ;
382
420
}
@@ -393,6 +431,38 @@ export function createCompilerPlugin(
393
431
} ;
394
432
}
395
433
434
+ function bundleWebWorker (
435
+ build : PluginBuild ,
436
+ pluginOptions : CompilerPluginOptions ,
437
+ workerFile : string ,
438
+ ) {
439
+ try {
440
+ return build . esbuild . buildSync ( {
441
+ platform : 'browser' ,
442
+ write : false ,
443
+ bundle : true ,
444
+ metafile : true ,
445
+ format : 'esm' ,
446
+ mainFields : [ 'es2020' , 'es2015' , 'browser' , 'module' , 'main' ] ,
447
+ logLevel : 'silent' ,
448
+ sourcemap : pluginOptions . sourcemap ,
449
+ entryNames : 'worker-[hash]' ,
450
+ entryPoints : [ workerFile ] ,
451
+ absWorkingDir : build . initialOptions . absWorkingDir ,
452
+ outdir : build . initialOptions . outdir ,
453
+ minifyIdentifiers : build . initialOptions . minifyIdentifiers ,
454
+ minifySyntax : build . initialOptions . minifySyntax ,
455
+ minifyWhitespace : build . initialOptions . minifyWhitespace ,
456
+ target : build . initialOptions . target ,
457
+ } ) ;
458
+ } catch ( error ) {
459
+ if ( error && typeof error === 'object' && 'errors' in error && 'warnings' in error ) {
460
+ return error as BuildFailure ;
461
+ }
462
+ throw error ;
463
+ }
464
+ }
465
+
396
466
function createMissingFileError ( request : string , original : string , root : string ) : PartialMessage {
397
467
const error = {
398
468
text : `File '${ path . relative ( root , request ) } ' is missing from the TypeScript compilation.` ,
0 commit comments