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
@@ -283,11 +309,13 @@ export class BuilderState {
283309
284310 /**
285311 * Records active bundle results across entry points after compilation and bundling succeed,
286- * clearing pending compilation/bundle flags.
312+ * clearing pending compilation/bundle flags and queuing any previously emitted bundle files
313+ * that are no longer present for deletion.
287314 *
288315 * @param bundleResults Newly bundled results keyed by entry point name.
289316 */
290317 recordBundleOutput ( bundleResults : ReadonlyMap < string , BundleResult > ) : void {
318+ const hadEntryPointsChanges = this . hasEntryPointsChanges ;
291319 this . hasCompilationError = false ;
292320 this . hasEntryPointsChanges = false ;
293321 this . pendingChangedEsmFiles . clear ( ) ;
@@ -296,6 +324,123 @@ export class BuilderState {
296324 for ( const [ name , bundleResult ] of bundleResults ) {
297325 this . previousBundleResults . set ( name , bundleResult ) ;
298326 }
327+
328+ if ( bundleResults . size === 0 && ! hadEntryPointsChanges && this . #emittedBundleFiles. size > 0 ) {
329+ return ;
330+ }
331+
332+ const nextBundleFiles = new Set < string > ( ) ;
333+ for ( const result of this . previousBundleResults . values ( ) ) {
334+ for ( const file of result . esmOutputFiles ) {
335+ nextBundleFiles . add ( file ) ;
336+ }
337+ for ( const file of result . dtsOutputFiles ) {
338+ nextBundleFiles . add ( file ) ;
339+ }
340+ }
341+
342+ for ( const prevFile of this . #emittedBundleFiles) {
343+ if ( ! nextBundleFiles . has ( prevFile ) ) {
344+ this . #filesToDelete. add ( prevFile ) ;
345+ }
346+ }
347+
348+ this . #nextBundleFiles = nextBundleFiles ;
349+ }
350+
351+ /**
352+ * Computes the active manifest output file set and queues any previously emitted
353+ * manifest files that are no longer present for deletion.
354+ *
355+ * @param manifestFiles Manifest output files generated in this iteration.
356+ */
357+ recordManifestOutput ( manifestFiles : readonly OutputFile [ ] ) : void {
358+ const nextManifestFiles = new Set ( manifestFiles . map ( ( file ) => file . path ) ) ;
359+ for ( const prevFile of this . #emittedManifestFiles) {
360+ if ( ! nextManifestFiles . has ( prevFile ) ) {
361+ this . #filesToDelete. add ( prevFile ) ;
362+ }
363+ }
364+
365+ this . #nextManifestFiles = nextManifestFiles ;
366+ }
367+
368+ /**
369+ * Resolves and collects configured library assets to be emitted to or deleted from disk.
370+ *
371+ * @param options Normalized library options containing `assets` and `workspaceRoot`.
372+ * @param modifiedFiles Optional set of modified POSIX file paths in watch mode.
373+ * @returns The collected asset files to emit and delete.
374+ */
375+ collectAssets (
376+ options : Pick < NormalizedLibraryOptions , 'assets' | 'workspaceRoot' > ,
377+ modifiedFiles ?: ReadonlySet < string > ,
378+ ) : Promise < CollectedAssets > {
379+ let effectiveModifiedFiles : ReadonlySet < string > | undefined ;
380+ if ( this . #hasEmittedAssets && modifiedFiles ) {
381+ for ( const file of modifiedFiles ) {
382+ this . #pendingAssetFiles. add ( file ) ;
383+ }
384+ effectiveModifiedFiles = this . #pendingAssetFiles;
385+ }
386+
387+ return collectAssetsToEmit (
388+ options . assets ,
389+ options . workspaceRoot ,
390+ this . #emittedAssets,
391+ effectiveModifiedFiles ,
392+ this . #assetMatchers,
393+ ) ;
394+ }
395+
396+ /**
397+ * Records emitted bundle, manifest, and asset output files after disk emission,
398+ * queuing any stale or deleted output paths for removal from disk.
399+ *
400+ * @param collectedAssets Collected asset files emitted and deleted in this iteration.
401+ * @returns Read-only set of relative POSIX output paths queued for deletion from disk.
402+ */
403+ recordEmittedOutputs ( collectedAssets : CollectedAssets ) : ReadonlySet < string > {
404+ const { filesToDelete : toDelete , filesToEmit : toEmit } = collectedAssets ;
405+
406+ for ( const deletedAsset of toDelete ) {
407+ this . #emittedAssets. delete ( deletedAsset . source ) ;
408+ }
409+
410+ for ( const emittedAsset of toEmit ) {
411+ let outputs = this . #emittedAssets. get ( emittedAsset . source ) ;
412+ if ( ! outputs ) {
413+ outputs = new Set ( ) ;
414+ this . #emittedAssets. set ( emittedAsset . source , outputs ) ;
415+ }
416+ outputs . add ( emittedAsset . path ) ;
417+ }
418+
419+ if ( toDelete . length ) {
420+ const activeAssetOutputs = new Set < string > ( ) ;
421+ for ( const outputs of this . #emittedAssets. values ( ) ) {
422+ for ( const output of outputs ) {
423+ activeAssetOutputs . add ( output ) ;
424+ }
425+ }
426+
427+ for ( const deletedAsset of toDelete ) {
428+ if ( ! activeAssetOutputs . has ( deletedAsset . path ) ) {
429+ this . #filesToDelete. add ( deletedAsset . path ) ;
430+ }
431+ }
432+ }
433+
434+ this . #emittedBundleFiles = this . #nextBundleFiles ?? this . #emittedBundleFiles;
435+ this . #emittedManifestFiles = this . #nextManifestFiles ?? this . #emittedManifestFiles;
436+ this . #nextBundleFiles = undefined ;
437+ this . #nextManifestFiles = undefined ;
438+ this . hasEmittedManifests = true ;
439+ this . #hasEmittedTslib = this . hasTslibImport ;
440+ this . #hasEmittedAssets = true ;
441+ this . #pendingAssetFiles. clear ( ) ;
442+
443+ return this . #filesToDelete;
299444 }
300445
301446 /**
@@ -305,17 +450,24 @@ export class BuilderState {
305450 await this . #singleProgramCache?. compilationInstance . close ?.( ) ;
306451 this . #singleProgramCache = undefined ;
307452 this . #compilationMode = undefined ;
308- this . hasEmittedTslib = false ;
309- this . hasEmittedAssets = false ;
453+ this . # hasEmittedTslib = false ;
454+ this . # hasEmittedAssets = false ;
310455 this . hasCompilationError = false ;
311456 this . hasEmittedManifests = false ;
312457 this . hasEntryPointsChanges = false ;
313458 this . previousBundleResults . clear ( ) ;
314459 this . pendingChangedEsmFiles . clear ( ) ;
315460 this . pendingChangedDtsFiles . clear ( ) ;
316461 this . #pendingCompilationFiles. clear ( ) ;
462+ this . #pendingAssetFiles. clear ( ) ;
317463 this . #referencingFiles. clear ( ) ;
318464 this . #referencedByFile. clear ( ) ;
319465 this . #watchedCompilationFiles. clear ( ) ;
466+ this . #emittedBundleFiles. clear ( ) ;
467+ this . #emittedManifestFiles. clear ( ) ;
468+ this . #emittedAssets. clear ( ) ;
469+ this . #nextBundleFiles = undefined ;
470+ this . #nextManifestFiles = undefined ;
471+ this . #filesToDelete. clear ( ) ;
320472 }
321473}
0 commit comments