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 }
@@ -221,6 +244,9 @@ export class BuilderState {
221244 if ( file !== packageJsonPath ) {
222245 this . #pendingCompilationFiles. add ( file ) ;
223246 }
247+ if ( this . #hasEmittedAssets) {
248+ this . #pendingAssetFiles. add ( file ) ;
249+ }
224250 }
225251 }
226252
@@ -295,11 +321,13 @@ export class BuilderState {
295321
296322 /**
297323 * Records active bundle results across entry points after compilation and bundling succeed,
298- * clearing pending compilation/bundle flags.
324+ * clearing pending compilation/bundle flags and queuing any previously emitted bundle files
325+ * that are no longer present for deletion.
299326 *
300327 * @param bundleResults Newly bundled results keyed by entry point name.
301328 */
302329 recordBundleOutput ( bundleResults : ReadonlyMap < string , BundleResult > ) : void {
330+ const hadEntryPointsChanges = this . hasEntryPointsChanges ;
303331 this . hasCompilationError = false ;
304332 this . hasEntryPointsChanges = false ;
305333 this . pendingChangedEsmFiles . clear ( ) ;
@@ -308,6 +336,123 @@ export class BuilderState {
308336 for ( const [ name , bundleResult ] of bundleResults ) {
309337 this . previousBundleResults . set ( name , bundleResult ) ;
310338 }
339+
340+ if ( bundleResults . size === 0 && ! hadEntryPointsChanges && this . #emittedBundleFiles. size > 0 ) {
341+ return ;
342+ }
343+
344+ const nextBundleFiles = new Set < string > ( ) ;
345+ for ( const result of this . previousBundleResults . values ( ) ) {
346+ for ( const file of result . esmOutputFiles ) {
347+ nextBundleFiles . add ( file ) ;
348+ }
349+ for ( const file of result . dtsOutputFiles ) {
350+ nextBundleFiles . add ( file ) ;
351+ }
352+ }
353+
354+ for ( const prevFile of this . #emittedBundleFiles) {
355+ if ( ! nextBundleFiles . has ( prevFile ) ) {
356+ this . #filesToDelete. add ( prevFile ) ;
357+ }
358+ }
359+
360+ this . #nextBundleFiles = nextBundleFiles ;
361+ }
362+
363+ /**
364+ * Computes the active manifest output file set and queues any previously emitted
365+ * manifest files that are no longer present for deletion.
366+ *
367+ * @param manifestFiles Manifest output files generated in this iteration.
368+ */
369+ recordManifestOutput ( manifestFiles : readonly OutputFile [ ] ) : void {
370+ const nextManifestFiles = new Set ( manifestFiles . map ( ( file ) => file . path ) ) ;
371+ for ( const prevFile of this . #emittedManifestFiles) {
372+ if ( ! nextManifestFiles . has ( prevFile ) ) {
373+ this . #filesToDelete. add ( prevFile ) ;
374+ }
375+ }
376+
377+ this . #nextManifestFiles = nextManifestFiles ;
378+ }
379+
380+ /**
381+ * Resolves and collects configured library assets to be emitted to or deleted from disk.
382+ *
383+ * @param options Normalized library options containing `assets` and `workspaceRoot`.
384+ * @param modifiedFiles Optional set of modified POSIX file paths in watch mode.
385+ * @returns The collected asset files to emit and delete.
386+ */
387+ collectAssets (
388+ options : Pick < NormalizedLibraryOptions , 'assets' | 'workspaceRoot' > ,
389+ modifiedFiles ?: ReadonlySet < string > ,
390+ ) : Promise < CollectedAssets > {
391+ let effectiveModifiedFiles : ReadonlySet < string > | undefined ;
392+ if ( this . #hasEmittedAssets && modifiedFiles ) {
393+ for ( const file of modifiedFiles ) {
394+ this . #pendingAssetFiles. add ( file ) ;
395+ }
396+ effectiveModifiedFiles = this . #pendingAssetFiles;
397+ }
398+
399+ return collectAssetsToEmit (
400+ options . assets ,
401+ options . workspaceRoot ,
402+ this . #emittedAssets,
403+ effectiveModifiedFiles ,
404+ this . #assetMatchers,
405+ ) ;
406+ }
407+
408+ /**
409+ * Records emitted bundle, manifest, and asset output files after disk emission,
410+ * queuing any stale or deleted output paths for removal from disk.
411+ *
412+ * @param collectedAssets Collected asset files emitted and deleted in this iteration.
413+ * @returns Read-only set of relative POSIX output paths queued for deletion from disk.
414+ */
415+ recordEmittedOutputs ( collectedAssets : CollectedAssets ) : ReadonlySet < string > {
416+ const { filesToDelete : toDelete , filesToEmit : toEmit } = collectedAssets ;
417+
418+ for ( const deletedAsset of toDelete ) {
419+ this . #emittedAssets. delete ( deletedAsset . source ) ;
420+ }
421+
422+ for ( const emittedAsset of toEmit ) {
423+ let outputs = this . #emittedAssets. get ( emittedAsset . source ) ;
424+ if ( ! outputs ) {
425+ outputs = new Set ( ) ;
426+ this . #emittedAssets. set ( emittedAsset . source , outputs ) ;
427+ }
428+ outputs . add ( emittedAsset . path ) ;
429+ }
430+
431+ if ( toDelete . length ) {
432+ const activeAssetOutputs = new Set < string > ( ) ;
433+ for ( const outputs of this . #emittedAssets. values ( ) ) {
434+ for ( const output of outputs ) {
435+ activeAssetOutputs . add ( output ) ;
436+ }
437+ }
438+
439+ for ( const deletedAsset of toDelete ) {
440+ if ( ! activeAssetOutputs . has ( deletedAsset . path ) ) {
441+ this . #filesToDelete. add ( deletedAsset . path ) ;
442+ }
443+ }
444+ }
445+
446+ this . #emittedBundleFiles = this . #nextBundleFiles ?? this . #emittedBundleFiles;
447+ this . #emittedManifestFiles = this . #nextManifestFiles ?? this . #emittedManifestFiles;
448+ this . #nextBundleFiles = undefined ;
449+ this . #nextManifestFiles = undefined ;
450+ this . hasEmittedManifests = true ;
451+ this . #hasEmittedTslib = this . hasTslibImport ;
452+ this . #hasEmittedAssets = true ;
453+ this . #pendingAssetFiles. clear ( ) ;
454+
455+ return this . #filesToDelete;
311456 }
312457
313458 /**
@@ -317,17 +462,24 @@ export class BuilderState {
317462 await this . #singleProgramCache?. compilationInstance . close ?.( ) ;
318463 this . #singleProgramCache = undefined ;
319464 this . #compilationMode = undefined ;
320- this . hasEmittedTslib = false ;
321- this . hasEmittedAssets = false ;
465+ this . # hasEmittedTslib = false ;
466+ this . # hasEmittedAssets = false ;
322467 this . hasCompilationError = false ;
323468 this . hasEmittedManifests = false ;
324469 this . hasEntryPointsChanges = false ;
325470 this . previousBundleResults . clear ( ) ;
326471 this . pendingChangedEsmFiles . clear ( ) ;
327472 this . pendingChangedDtsFiles . clear ( ) ;
328473 this . #pendingCompilationFiles. clear ( ) ;
474+ this . #pendingAssetFiles. clear ( ) ;
329475 this . #referencingFiles. clear ( ) ;
330476 this . #referencedByFile. clear ( ) ;
331477 this . #watchedCompilationFiles. clear ( ) ;
478+ this . #emittedBundleFiles. clear ( ) ;
479+ this . #emittedManifestFiles. clear ( ) ;
480+ this . #emittedAssets. clear ( ) ;
481+ this . #nextBundleFiles = undefined ;
482+ this . #nextManifestFiles = undefined ;
483+ this . #filesToDelete. clear ( ) ;
332484 }
333485}
0 commit comments