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,27 @@ 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+ #hasUpdatedBundles = false ;
44+
2945 #singleProgramCache?: SingleProgramCache ;
3046 #compilationMode?: 'partial' | 'full' ;
47+ #hasEmittedTslib = false ;
3148
3249 /** Map of entry point names to their previous bundle results. */
3350 readonly previousBundleResults = new Map < string , BundleResult > ( ) ;
@@ -44,18 +61,12 @@ export class BuilderState {
4461 /** Whether package.json and secondary entry point manifests have been emitted. */
4562 hasEmittedManifests = false ;
4663
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-
5364 /** Whether entry points in package.json changed since the last compilation. */
5465 hasEntryPointsChanges = false ;
5566
5667 constructor (
5768 options ?: Partial <
58- Pick < NormalizedLibraryOptions , 'tsConfigPath' | 'packageJsonPath' | 'entryPoints' >
69+ Pick < NormalizedLibraryOptions , 'tsConfigPath' | 'packageJsonPath' | 'entryPoints' | 'assets' >
5970 > ,
6071 ) {
6172 if ( options ?. tsConfigPath ) {
@@ -69,6 +80,8 @@ export class BuilderState {
6980 this . #watchedCompilationFiles. add ( entryPoint . entryFilePath ) ;
7081 }
7182 }
83+
84+ this . #assetMatchers = options ?. assets ? createAssetMatchers ( options . assets ) : undefined ;
7285 }
7386
7487 /** Cached unified TypeScript and Angular compilation state. */
@@ -81,6 +94,11 @@ export class BuilderState {
8194 return this . #compilationMode;
8295 }
8396
97+ /** Whether the last emitted root package.json included a tslib dependency. */
98+ get hasEmittedTslib ( ) : boolean {
99+ return this . #hasEmittedTslib;
100+ }
101+
84102 /** Whether any active entry point bundle imports `tslib`. */
85103 get hasTslibImport ( ) : boolean {
86104 for ( const result of this . previousBundleResults . values ( ) ) {
@@ -109,12 +127,13 @@ export class BuilderState {
109127 }
110128
111129 /**
112- * Resets the execution result for a new build iteration.
130+ * Resets the execution result and staged output state for a new build iteration.
113131 *
114132 * @returns The fresh `ExecutionResult` instance for the current build iteration.
115133 */
116134 resetExecutionResult ( ) : ExecutionResult {
117135 this . #executionResult = new ExecutionResult ( ) ;
136+ this . #hasUpdatedBundles = false ;
118137
119138 return this . #executionResult;
120139 }
@@ -221,6 +240,9 @@ export class BuilderState {
221240 if ( file !== packageJsonPath ) {
222241 this . #pendingCompilationFiles. add ( file ) ;
223242 }
243+ if ( this . #hasEmittedAssets) {
244+ this . #pendingAssetFiles. add ( file ) ;
245+ }
224246 }
225247 }
226248
@@ -294,12 +316,13 @@ export class BuilderState {
294316 }
295317
296318 /**
297- * Records active bundle results across entry points after compilation and bundling succeed,
298- * clearing pending compilation/bundle flags.
319+ * Records active bundle results across entry points after compilation and bundling succeed
320+ * and clears pending compilation/bundle flags.
299321 *
300322 * @param bundleResults Newly bundled results keyed by entry point name.
301323 */
302324 recordBundleOutput ( bundleResults : ReadonlyMap < string , BundleResult > ) : void {
325+ const hadEntryPointsChanges = this . hasEntryPointsChanges ;
303326 this . hasCompilationError = false ;
304327 this . hasEntryPointsChanges = false ;
305328 this . pendingChangedEsmFiles . clear ( ) ;
@@ -308,6 +331,126 @@ export class BuilderState {
308331 for ( const [ name , bundleResult ] of bundleResults ) {
309332 this . previousBundleResults . set ( name , bundleResult ) ;
310333 }
334+
335+ if ( bundleResults . size > 0 || hadEntryPointsChanges || this . #emittedBundleFiles. size === 0 ) {
336+ this . #hasUpdatedBundles = true ;
337+ }
338+ }
339+
340+ /**
341+ * Resolves and collects configured library assets to be emitted to or deleted from disk.
342+ *
343+ * @param options Normalized library options containing `assets` and `workspaceRoot`.
344+ * @param modifiedFiles Optional set of modified POSIX file paths in watch mode.
345+ * @returns The collected asset files to emit and delete.
346+ */
347+ collectAssets (
348+ options : Pick < NormalizedLibraryOptions , 'assets' | 'workspaceRoot' > ,
349+ modifiedFiles ?: ReadonlySet < string > ,
350+ ) : Promise < CollectedAssets > {
351+ let effectiveModifiedFiles : ReadonlySet < string > | undefined ;
352+ if ( this . #hasEmittedAssets && modifiedFiles ) {
353+ for ( const file of modifiedFiles ) {
354+ this . #pendingAssetFiles. add ( file ) ;
355+ }
356+ effectiveModifiedFiles = this . #pendingAssetFiles;
357+ }
358+
359+ return collectAssetsToEmit (
360+ options . assets ,
361+ options . workspaceRoot ,
362+ this . #emittedAssets,
363+ effectiveModifiedFiles ,
364+ this . #assetMatchers,
365+ ) ;
366+ }
367+
368+ /**
369+ * Records emitted bundle, manifest, and asset output files after disk emission,
370+ * returning any stale or deleted output paths to remove from disk.
371+ *
372+ * @param collectedAssets Collected asset files emitted and deleted in this iteration.
373+ * @param manifestFiles Optional manifest output files generated in this iteration.
374+ * @returns Read-only set of relative POSIX output paths to delete from disk.
375+ */
376+ recordEmittedOutputs (
377+ collectedAssets : CollectedAssets ,
378+ manifestFiles ?: readonly OutputFile [ ] ,
379+ ) : ReadonlySet < string > {
380+ const filesToDelete = new Set < string > ( ) ;
381+
382+ if ( this . #hasUpdatedBundles) {
383+ this . #hasUpdatedBundles = false ;
384+ const nextBundleFiles = new Set < string > ( ) ;
385+ for ( const result of this . previousBundleResults . values ( ) ) {
386+ for ( const file of result . esmOutputFiles ) {
387+ nextBundleFiles . add ( file ) ;
388+ }
389+ for ( const file of result . dtsOutputFiles ) {
390+ nextBundleFiles . add ( file ) ;
391+ }
392+ }
393+
394+ for ( const prevFile of this . #emittedBundleFiles) {
395+ if ( ! nextBundleFiles . has ( prevFile ) ) {
396+ filesToDelete . add ( prevFile ) ;
397+ }
398+ }
399+
400+ this . #emittedBundleFiles = nextBundleFiles ;
401+ }
402+
403+ if ( manifestFiles ) {
404+ const nextManifestFiles = new Set ( manifestFiles . map ( ( file ) => file . path ) ) ;
405+ for ( const prevFile of this . #emittedManifestFiles) {
406+ if ( ! nextManifestFiles . has ( prevFile ) ) {
407+ filesToDelete . add ( prevFile ) ;
408+ }
409+ }
410+
411+ this . #emittedManifestFiles = nextManifestFiles ;
412+ }
413+
414+ const { filesToDelete : toDelete , filesToEmit : toEmit } = collectedAssets ;
415+
416+ for ( const emittedAsset of toEmit ) {
417+ let outputs = this . #emittedAssets. get ( emittedAsset . source ) ;
418+ if ( ! outputs ) {
419+ outputs = new Set ( ) ;
420+ this . #emittedAssets. set ( emittedAsset . source , outputs ) ;
421+ }
422+ outputs . add ( emittedAsset . path ) ;
423+ }
424+
425+ if ( toDelete . length ) {
426+ const candidateDeletes = new Set < string > ( ) ;
427+ for ( const source of toDelete ) {
428+ const outputs = this . #emittedAssets. get ( source ) ;
429+ if ( outputs ) {
430+ for ( const output of outputs ) {
431+ candidateDeletes . add ( output ) ;
432+ }
433+ this . #emittedAssets. delete ( source ) ;
434+ }
435+ }
436+
437+ for ( const outputs of this . #emittedAssets. values ( ) ) {
438+ for ( const output of outputs ) {
439+ candidateDeletes . delete ( output ) ;
440+ }
441+ }
442+
443+ for ( const output of candidateDeletes ) {
444+ filesToDelete . add ( output ) ;
445+ }
446+ }
447+
448+ this . hasEmittedManifests = true ;
449+ this . #hasEmittedTslib = this . hasTslibImport ;
450+ this . #hasEmittedAssets = true ;
451+ this . #pendingAssetFiles. clear ( ) ;
452+
453+ return filesToDelete ;
311454 }
312455
313456 /**
@@ -317,17 +460,22 @@ export class BuilderState {
317460 await this . #singleProgramCache?. compilationInstance . close ?.( ) ;
318461 this . #singleProgramCache = undefined ;
319462 this . #compilationMode = undefined ;
320- this . hasEmittedTslib = false ;
321- this . hasEmittedAssets = false ;
463+ this . # hasEmittedTslib = false ;
464+ this . # hasEmittedAssets = false ;
322465 this . hasCompilationError = false ;
323466 this . hasEmittedManifests = false ;
324467 this . hasEntryPointsChanges = false ;
325468 this . previousBundleResults . clear ( ) ;
326469 this . pendingChangedEsmFiles . clear ( ) ;
327470 this . pendingChangedDtsFiles . clear ( ) ;
328471 this . #pendingCompilationFiles. clear ( ) ;
472+ this . #pendingAssetFiles. clear ( ) ;
329473 this . #referencingFiles. clear ( ) ;
330474 this . #referencedByFile. clear ( ) ;
331475 this . #watchedCompilationFiles. clear ( ) ;
476+ this . #emittedBundleFiles. clear ( ) ;
477+ this . #emittedManifestFiles. clear ( ) ;
478+ this . #emittedAssets. clear ( ) ;
479+ this . #hasUpdatedBundles = false ;
332480 }
333481}
0 commit comments