@@ -10,6 +10,7 @@ import {
10
10
BuildContext ,
11
11
BuildFailure ,
12
12
BuildOptions ,
13
+ BuildResult ,
13
14
Message ,
14
15
Metafile ,
15
16
OutputFile ,
@@ -66,7 +67,9 @@ function isEsBuildFailure(value: unknown): value is BuildFailure {
66
67
export class BundlerContext {
67
68
#esbuildContext?: BuildContext < { metafile : true ; write : false } > ;
68
69
#esbuildOptions?: BuildOptions & { metafile : true ; write : false } ;
70
+ #esbuildResult?: BundleContextResult ;
69
71
#optionsFactory: BundlerOptionsFactory < BuildOptions & { metafile : true ; write : false } > ;
72
+ #shouldCacheResult: boolean ;
70
73
71
74
#loadCache?: MemoryLoadResultCache ;
72
75
readonly watchFiles = new Set < string > ( ) ;
@@ -77,6 +80,8 @@ export class BundlerContext {
77
80
options : BuildOptions | BundlerOptionsFactory ,
78
81
private initialFilter ?: ( initial : Readonly < InitialFileRecord > ) => boolean ,
79
82
) {
83
+ // To cache the results an option factory is needed to capture the full set of dependencies
84
+ this . #shouldCacheResult = incremental && typeof options === 'function' ;
80
85
this . #optionsFactory = ( ...args ) => {
81
86
const baseOptions = typeof options === 'function' ? options ( ...args ) : options ;
82
87
@@ -142,6 +147,20 @@ export class BundlerContext {
142
147
* warnings and errors for the attempted build.
143
148
*/
144
149
async bundle ( ) : Promise < BundleContextResult > {
150
+ // Return existing result if present
151
+ if ( this . #esbuildResult) {
152
+ return this . #esbuildResult;
153
+ }
154
+
155
+ const result = await this . #performBundle( ) ;
156
+ if ( this . #shouldCacheResult) {
157
+ this . #esbuildResult = result ;
158
+ }
159
+
160
+ return result ;
161
+ }
162
+
163
+ async #performBundle( ) {
145
164
// Create esbuild options if not present
146
165
if ( this . #esbuildOptions === undefined ) {
147
166
if ( this . incremental ) {
@@ -150,6 +169,10 @@ export class BundlerContext {
150
169
this . #esbuildOptions = this . #optionsFactory( this . #loadCache) ;
151
170
}
152
171
172
+ if ( this . incremental ) {
173
+ this . watchFiles . clear ( ) ;
174
+ }
175
+
153
176
let result ;
154
177
try {
155
178
if ( this . #esbuildContext) {
@@ -167,6 +190,8 @@ export class BundlerContext {
167
190
} catch ( failure ) {
168
191
// Build failures will throw an exception which contains errors/warnings
169
192
if ( isEsBuildFailure ( failure ) ) {
193
+ this . #addErrorsToWatch( failure ) ;
194
+
170
195
return failure ;
171
196
} else {
172
197
throw failure ;
@@ -177,7 +202,6 @@ export class BundlerContext {
177
202
// While this should technically not be linked to incremental mode, incremental is only
178
203
// currently enabled with watch mode where watch files are needed.
179
204
if ( this . incremental ) {
180
- this . watchFiles . clear ( ) ;
181
205
// Add input files except virtual angular files which do not exist on disk
182
206
Object . keys ( result . metafile . inputs )
183
207
. filter ( ( input ) => ! input . startsWith ( 'angular:' ) )
@@ -194,6 +218,8 @@ export class BundlerContext {
194
218
195
219
// Return if the build encountered any errors
196
220
if ( result . errors . length ) {
221
+ this . #addErrorsToWatch( result ) ;
222
+
197
223
return {
198
224
errors : result . errors ,
199
225
warnings : result . warnings ,
@@ -281,6 +307,28 @@ export class BundlerContext {
281
307
} ;
282
308
}
283
309
310
+ #addErrorsToWatch( result : BuildFailure | BuildResult ) : void {
311
+ for ( const error of result . errors ) {
312
+ let file = error . location ?. file ;
313
+ if ( file ) {
314
+ this . watchFiles . add ( join ( this . workspaceRoot , file ) ) ;
315
+ }
316
+ for ( const note of error . notes ) {
317
+ file = note . location ?. file ;
318
+ if ( file ) {
319
+ this . watchFiles . add ( join ( this . workspaceRoot , file ) ) ;
320
+ }
321
+ }
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Invalidate a stored bundler result based on the previous watch files
327
+ * and a list of changed files.
328
+ * The context must be created with incremental mode enabled for results
329
+ * to be stored.
330
+ * @returns True, if the result was invalidated; False, otherwise.
331
+ */
284
332
invalidate ( files : Iterable < string > ) : boolean {
285
333
if ( ! this . incremental ) {
286
334
return false ;
@@ -296,6 +344,10 @@ export class BundlerContext {
296
344
invalid ||= this . watchFiles . has ( file ) ;
297
345
}
298
346
347
+ if ( invalid ) {
348
+ this . #esbuildResult = undefined ;
349
+ }
350
+
299
351
return invalid ;
300
352
}
301
353
@@ -307,6 +359,7 @@ export class BundlerContext {
307
359
async dispose ( ) : Promise < void > {
308
360
try {
309
361
this . #esbuildOptions = undefined ;
362
+ this . #esbuildResult = undefined ;
310
363
this . #loadCache = undefined ;
311
364
await this . #esbuildContext?. dispose ( ) ;
312
365
} finally {
0 commit comments