@@ -10,6 +10,7 @@ import {
1010 BuildContext ,
1111 BuildFailure ,
1212 BuildOptions ,
13+ BuildResult ,
1314 Message ,
1415 Metafile ,
1516 OutputFile ,
@@ -66,7 +67,9 @@ function isEsBuildFailure(value: unknown): value is BuildFailure {
6667export class BundlerContext {
6768 #esbuildContext?: BuildContext < { metafile : true ; write : false } > ;
6869 #esbuildOptions?: BuildOptions & { metafile : true ; write : false } ;
70+ #esbuildResult?: BundleContextResult ;
6971 #optionsFactory: BundlerOptionsFactory < BuildOptions & { metafile : true ; write : false } > ;
72+ #shouldCacheResult: boolean ;
7073
7174 #loadCache?: MemoryLoadResultCache ;
7275 readonly watchFiles = new Set < string > ( ) ;
@@ -77,6 +80,8 @@ export class BundlerContext {
7780 options : BuildOptions | BundlerOptionsFactory ,
7881 private initialFilter ?: ( initial : Readonly < InitialFileRecord > ) => boolean ,
7982 ) {
83+ // To cache the results an option factory is needed to capture the full set of dependencies
84+ this . #shouldCacheResult = incremental && typeof options === 'function' ;
8085 this . #optionsFactory = ( ...args ) => {
8186 const baseOptions = typeof options === 'function' ? options ( ...args ) : options ;
8287
@@ -142,6 +147,20 @@ export class BundlerContext {
142147 * warnings and errors for the attempted build.
143148 */
144149 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( ) {
145164 // Create esbuild options if not present
146165 if ( this . #esbuildOptions === undefined ) {
147166 if ( this . incremental ) {
@@ -150,6 +169,10 @@ export class BundlerContext {
150169 this . #esbuildOptions = this . #optionsFactory( this . #loadCache) ;
151170 }
152171
172+ if ( this . incremental ) {
173+ this . watchFiles . clear ( ) ;
174+ }
175+
153176 let result ;
154177 try {
155178 if ( this . #esbuildContext) {
@@ -167,6 +190,8 @@ export class BundlerContext {
167190 } catch ( failure ) {
168191 // Build failures will throw an exception which contains errors/warnings
169192 if ( isEsBuildFailure ( failure ) ) {
193+ this . #addErrorsToWatch( failure ) ;
194+
170195 return failure ;
171196 } else {
172197 throw failure ;
@@ -177,7 +202,6 @@ export class BundlerContext {
177202 // While this should technically not be linked to incremental mode, incremental is only
178203 // currently enabled with watch mode where watch files are needed.
179204 if ( this . incremental ) {
180- this . watchFiles . clear ( ) ;
181205 // Add input files except virtual angular files which do not exist on disk
182206 Object . keys ( result . metafile . inputs )
183207 . filter ( ( input ) => ! input . startsWith ( 'angular:' ) )
@@ -194,6 +218,8 @@ export class BundlerContext {
194218
195219 // Return if the build encountered any errors
196220 if ( result . errors . length ) {
221+ this . #addErrorsToWatch( result ) ;
222+
197223 return {
198224 errors : result . errors ,
199225 warnings : result . warnings ,
@@ -281,6 +307,28 @@ export class BundlerContext {
281307 } ;
282308 }
283309
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+ */
284332 invalidate ( files : Iterable < string > ) : boolean {
285333 if ( ! this . incremental ) {
286334 return false ;
@@ -296,6 +344,10 @@ export class BundlerContext {
296344 invalid ||= this . watchFiles . has ( file ) ;
297345 }
298346
347+ if ( invalid ) {
348+ this . #esbuildResult = undefined ;
349+ }
350+
299351 return invalid ;
300352 }
301353
@@ -307,6 +359,7 @@ export class BundlerContext {
307359 async dispose ( ) : Promise < void > {
308360 try {
309361 this . #esbuildOptions = undefined ;
362+ this . #esbuildResult = undefined ;
310363 this . #loadCache = undefined ;
311364 await this . #esbuildContext?. dispose ( ) ;
312365 } finally {
0 commit comments