88
99import { toPosixPath } from '../../utils/path' ;
1010import { ExecutionResult } from './execution-result' ;
11+ import {
12+ type AssetMatcher ,
13+ type CollectedAssets ,
14+ collectAssetsToEmit ,
15+ createAssetMatchers ,
16+ } from './pipeline/assets' ;
17+ import type { OutputFile } from './pipeline/utils' ;
1118import type {
1219 BundleResult ,
1320 LibraryCompilationOutput ,
@@ -17,17 +24,29 @@ import type {
1724
1825/**
1926 * Encapsulates incremental compilation state, transitive resource reference tracking,
20- * watched compilation file tracking , and execution results across watch-mode rebuilds.
27+ * asset matchers , and emitted output file tracking across watch-mode rebuilds.
2128 */
2229export class BuilderState {
2330 readonly #referencingFiles = new Map < string , Set < string > > ( ) ;
2431 readonly #referencedByFile = new Map < string , Set < string > > ( ) ;
2532 readonly #watchedCompilationFiles = new Set < string > ( ) ;
2633 readonly #pendingCompilationFiles = new Set < string > ( ) ;
34+ readonly #pendingAssetFiles = new Set < string > ( ) ;
35+ readonly #assetMatchers?: readonly AssetMatcher [ ] ;
36+
37+ #emittedBundleFiles = new Set < string > ( ) ;
38+ #emittedManifestFiles = new Set < string > ( ) ;
39+ readonly #emittedAssets = new Map < string , Set < string > > ( ) ;
40+ #hasEmittedAssets = false ;
2741
2842 #executionResult = new ExecutionResult ( ) ;
43+ #nextBundleFiles?: Set < string > ;
44+ #nextManifestFiles?: Set < string > ;
45+ readonly #filesToDelete = new Set < string > ( ) ;
46+
2947 #singleProgramCache?: SingleProgramCache ;
3048 #compilationMode?: 'partial' | 'full' ;
49+ #hasEmittedTslib = false ;
3150
3251 /** Map of entry point names to their previous bundle results. */
3352 readonly previousBundleResults = new Map < string , BundleResult > ( ) ;
@@ -44,18 +63,12 @@ export class BuilderState {
4463 /** Whether package.json and secondary entry point manifests have been emitted. */
4564 hasEmittedManifests = false ;
4665
47- /** Whether the last emitted root package.json included a tslib dependency. */
48- hasEmittedTslib = false ;
49-
50- /** Whether static assets have been emitted in a prior iteration. */
51- hasEmittedAssets = false ;
52-
5366 /** Whether entry points in package.json changed since the last compilation. */
5467 hasEntryPointsChanges = false ;
5568
5669 constructor (
5770 options ?: Partial <
58- Pick < NormalizedLibraryOptions , 'tsConfigPath' | 'packageJsonPath' | 'entryPoints' >
71+ Pick < NormalizedLibraryOptions , 'tsConfigPath' | 'packageJsonPath' | 'entryPoints' | 'assets' >
5972 > ,
6073 ) {
6174 if ( options ?. tsConfigPath ) {
@@ -69,6 +82,8 @@ export class BuilderState {
6982 this . #watchedCompilationFiles. add ( entryPoint . entryFilePath ) ;
7083 }
7184 }
85+
86+ this . #assetMatchers = options ?. assets ? createAssetMatchers ( options . assets ) : undefined ;
7287 }
7388
7489 /** Cached unified TypeScript and Angular compilation state. */
@@ -81,6 +96,11 @@ export class BuilderState {
8196 return this . #compilationMode;
8297 }
8398
99+ /** Whether the last emitted root package.json included a tslib dependency. */
100+ get hasEmittedTslib ( ) : boolean {
101+ return this . #hasEmittedTslib;
102+ }
103+
84104 /** Whether any active entry point bundle imports `tslib`. */
85105 get hasTslibImport ( ) : boolean {
86106 for ( const result of this . previousBundleResults . values ( ) ) {
@@ -109,12 +129,15 @@ export class BuilderState {
109129 }
110130
111131 /**
112- * Resets the execution result for a new build iteration.
132+ * Resets the execution result and staged output state for a new build iteration.
113133 *
114134 * @returns The fresh `ExecutionResult` instance for the current build iteration.
115135 */
116136 resetExecutionResult ( ) : ExecutionResult {
117137 this . #executionResult = new ExecutionResult ( ) ;
138+ this . #nextBundleFiles = undefined ;
139+ this . #nextManifestFiles = undefined ;
140+ this . #filesToDelete. clear ( ) ;
118141
119142 return this . #executionResult;
120143 }
@@ -209,6 +232,9 @@ export class BuilderState {
209232 if ( file !== packageJsonPath ) {
210233 this . #pendingCompilationFiles. add ( file ) ;
211234 }
235+ if ( this . #hasEmittedAssets) {
236+ this . #pendingAssetFiles. add ( file ) ;
237+ }
212238 }
213239 }
214240
@@ -281,11 +307,13 @@ export class BuilderState {
281307
282308 /**
283309 * Records active bundle results across entry points after compilation and bundling succeed,
284- * clearing pending compilation/bundle flags.
310+ * clearing pending compilation/bundle flags and queuing any previously emitted bundle files
311+ * that are no longer present for deletion.
285312 *
286313 * @param bundleResults Newly bundled results keyed by entry point name.
287314 */
288315 recordBundleOutput ( bundleResults : ReadonlyMap < string , BundleResult > ) : void {
316+ const hadEntryPointsChanges = this . hasEntryPointsChanges ;
289317 this . hasCompilationError = false ;
290318 this . hasEntryPointsChanges = false ;
291319 this . pendingChangedEsmFiles . clear ( ) ;
@@ -294,6 +322,123 @@ export class BuilderState {
294322 for ( const [ name , bundleResult ] of bundleResults ) {
295323 this . previousBundleResults . set ( name , bundleResult ) ;
296324 }
325+
326+ if ( bundleResults . size === 0 && ! hadEntryPointsChanges && this . #emittedBundleFiles. size > 0 ) {
327+ return ;
328+ }
329+
330+ const nextBundleFiles = new Set < string > ( ) ;
331+ for ( const result of this . previousBundleResults . values ( ) ) {
332+ for ( const file of result . esmOutputFiles ) {
333+ nextBundleFiles . add ( file ) ;
334+ }
335+ for ( const file of result . dtsOutputFiles ) {
336+ nextBundleFiles . add ( file ) ;
337+ }
338+ }
339+
340+ for ( const prevFile of this . #emittedBundleFiles) {
341+ if ( ! nextBundleFiles . has ( prevFile ) ) {
342+ this . #filesToDelete. add ( prevFile ) ;
343+ }
344+ }
345+
346+ this . #nextBundleFiles = nextBundleFiles ;
347+ }
348+
349+ /**
350+ * Computes the active manifest output file set and queues any previously emitted
351+ * manifest files that are no longer present for deletion.
352+ *
353+ * @param manifestFiles Manifest output files generated in this iteration.
354+ */
355+ recordManifestOutput ( manifestFiles : readonly OutputFile [ ] ) : void {
356+ const nextManifestFiles = new Set ( manifestFiles . map ( ( file ) => file . path ) ) ;
357+ for ( const prevFile of this . #emittedManifestFiles) {
358+ if ( ! nextManifestFiles . has ( prevFile ) ) {
359+ this . #filesToDelete. add ( prevFile ) ;
360+ }
361+ }
362+
363+ this . #nextManifestFiles = nextManifestFiles ;
364+ }
365+
366+ /**
367+ * Resolves and collects configured library assets to be emitted to or deleted from disk.
368+ *
369+ * @param options Normalized library options containing `assets` and `workspaceRoot`.
370+ * @param modifiedFiles Optional set of modified POSIX file paths in watch mode.
371+ * @returns The collected asset files to emit and delete.
372+ */
373+ collectAssets (
374+ options : Pick < NormalizedLibraryOptions , 'assets' | 'workspaceRoot' > ,
375+ modifiedFiles ?: ReadonlySet < string > ,
376+ ) : Promise < CollectedAssets > {
377+ let effectiveModifiedFiles : ReadonlySet < string > | undefined ;
378+ if ( this . #hasEmittedAssets && modifiedFiles ) {
379+ for ( const file of modifiedFiles ) {
380+ this . #pendingAssetFiles. add ( file ) ;
381+ }
382+ effectiveModifiedFiles = this . #pendingAssetFiles;
383+ }
384+
385+ return collectAssetsToEmit (
386+ options . assets ,
387+ options . workspaceRoot ,
388+ this . #emittedAssets,
389+ effectiveModifiedFiles ,
390+ this . #assetMatchers,
391+ ) ;
392+ }
393+
394+ /**
395+ * Records emitted bundle, manifest, and asset output files after disk emission,
396+ * queuing any stale or deleted output paths for removal from disk.
397+ *
398+ * @param collectedAssets Collected asset files emitted and deleted in this iteration.
399+ * @returns Read-only set of relative POSIX output paths queued for deletion from disk.
400+ */
401+ recordEmittedOutputs ( collectedAssets : CollectedAssets ) : ReadonlySet < string > {
402+ const { filesToDelete : toDelete , filesToEmit : toEmit } = collectedAssets ;
403+
404+ for ( const deletedAsset of toDelete ) {
405+ this . #emittedAssets. delete ( deletedAsset . source ) ;
406+ }
407+
408+ for ( const emittedAsset of toEmit ) {
409+ let outputs = this . #emittedAssets. get ( emittedAsset . source ) ;
410+ if ( ! outputs ) {
411+ outputs = new Set ( ) ;
412+ this . #emittedAssets. set ( emittedAsset . source , outputs ) ;
413+ }
414+ outputs . add ( emittedAsset . path ) ;
415+ }
416+
417+ if ( toDelete . length ) {
418+ const activeAssetOutputs = new Set < string > ( ) ;
419+ for ( const outputs of this . #emittedAssets. values ( ) ) {
420+ for ( const output of outputs ) {
421+ activeAssetOutputs . add ( output ) ;
422+ }
423+ }
424+
425+ for ( const deletedAsset of toDelete ) {
426+ if ( ! activeAssetOutputs . has ( deletedAsset . path ) ) {
427+ this . #filesToDelete. add ( deletedAsset . path ) ;
428+ }
429+ }
430+ }
431+
432+ this . #emittedBundleFiles = this . #nextBundleFiles ?? this . #emittedBundleFiles;
433+ this . #emittedManifestFiles = this . #nextManifestFiles ?? this . #emittedManifestFiles;
434+ this . #nextBundleFiles = undefined ;
435+ this . #nextManifestFiles = undefined ;
436+ this . hasEmittedManifests = true ;
437+ this . #hasEmittedTslib = this . hasTslibImport ;
438+ this . #hasEmittedAssets = true ;
439+ this . #pendingAssetFiles. clear ( ) ;
440+
441+ return this . #filesToDelete;
297442 }
298443
299444 /**
@@ -303,17 +448,24 @@ export class BuilderState {
303448 await this . #singleProgramCache?. compilationInstance . close ?.( ) ;
304449 this . #singleProgramCache = undefined ;
305450 this . #compilationMode = undefined ;
306- this . hasEmittedTslib = false ;
307- this . hasEmittedAssets = false ;
451+ this . # hasEmittedTslib = false ;
452+ this . # hasEmittedAssets = false ;
308453 this . hasCompilationError = false ;
309454 this . hasEmittedManifests = false ;
310455 this . hasEntryPointsChanges = false ;
311456 this . previousBundleResults . clear ( ) ;
312457 this . pendingChangedEsmFiles . clear ( ) ;
313458 this . pendingChangedDtsFiles . clear ( ) ;
314459 this . #pendingCompilationFiles. clear ( ) ;
460+ this . #pendingAssetFiles. clear ( ) ;
315461 this . #referencingFiles. clear ( ) ;
316462 this . #referencedByFile. clear ( ) ;
317463 this . #watchedCompilationFiles. clear ( ) ;
464+ this . #emittedBundleFiles. clear ( ) ;
465+ this . #emittedManifestFiles. clear ( ) ;
466+ this . #emittedAssets. clear ( ) ;
467+ this . #nextBundleFiles = undefined ;
468+ this . #nextManifestFiles = undefined ;
469+ this . #filesToDelete. clear ( ) ;
318470 }
319471}
0 commit comments