Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 161 additions & 13 deletions packages/angular/build/src/builders/library/builder-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

import { toPosixPath } from '../../utils/path';
import { ExecutionResult } from './execution-result';
import {
type AssetMatcher,
type CollectedAssets,
collectAssetsToEmit,
createAssetMatchers,
} from './pipeline/assets';
import type { OutputFile } from './pipeline/utils';
import type {
BundleResult,
LibraryCompilationOutput,
Expand All @@ -17,17 +24,27 @@ import type {

/**
* Encapsulates incremental compilation state, transitive resource reference tracking,
* watched compilation file tracking, and execution results across watch-mode rebuilds.
* asset matchers, and emitted output file tracking across watch-mode rebuilds.
*/
export class BuilderState {
readonly #referencingFiles = new Map<string, Set<string>>();
readonly #referencedByFile = new Map<string, Set<string>>();
readonly #watchedCompilationFiles = new Set<string>();
readonly #pendingCompilationFiles = new Set<string>();
readonly #pendingAssetFiles = new Set<string>();
readonly #assetMatchers?: readonly AssetMatcher[];

#emittedBundleFiles = new Set<string>();
#emittedManifestFiles = new Set<string>();
readonly #emittedAssets = new Map<string, Set<string>>();
#hasEmittedAssets = false;

#executionResult = new ExecutionResult();
#hasUpdatedBundles = false;

#singleProgramCache?: SingleProgramCache;
#compilationMode?: 'partial' | 'full';
#hasEmittedTslib = false;

/** Map of entry point names to their previous bundle results. */
readonly previousBundleResults = new Map<string, BundleResult>();
Expand All @@ -44,18 +61,12 @@ export class BuilderState {
/** Whether package.json and secondary entry point manifests have been emitted. */
hasEmittedManifests = false;

/** Whether the last emitted root package.json included a tslib dependency. */
hasEmittedTslib = false;

/** Whether static assets have been emitted in a prior iteration. */
hasEmittedAssets = false;

/** Whether entry points in package.json changed since the last compilation. */
hasEntryPointsChanges = false;

constructor(
options?: Partial<
Pick<NormalizedLibraryOptions, 'tsConfigPath' | 'packageJsonPath' | 'entryPoints'>
Pick<NormalizedLibraryOptions, 'tsConfigPath' | 'packageJsonPath' | 'entryPoints' | 'assets'>
>,
) {
if (options?.tsConfigPath) {
Expand All @@ -69,6 +80,8 @@ export class BuilderState {
this.#watchedCompilationFiles.add(entryPoint.entryFilePath);
}
}

this.#assetMatchers = options?.assets ? createAssetMatchers(options.assets) : undefined;
}

/** Cached unified TypeScript and Angular compilation state. */
Expand All @@ -81,6 +94,11 @@ export class BuilderState {
return this.#compilationMode;
}

/** Whether the last emitted root package.json included a tslib dependency. */
get hasEmittedTslib(): boolean {
return this.#hasEmittedTslib;
}

/** Whether any active entry point bundle imports `tslib`. */
get hasTslibImport(): boolean {
for (const result of this.previousBundleResults.values()) {
Expand Down Expand Up @@ -109,12 +127,13 @@ export class BuilderState {
}

/**
* Resets the execution result for a new build iteration.
* Resets the execution result and staged output state for a new build iteration.
*
* @returns The fresh `ExecutionResult` instance for the current build iteration.
*/
resetExecutionResult(): ExecutionResult {
this.#executionResult = new ExecutionResult();
this.#hasUpdatedBundles = false;

return this.#executionResult;
}
Expand Down Expand Up @@ -221,6 +240,9 @@ export class BuilderState {
if (file !== packageJsonPath) {
this.#pendingCompilationFiles.add(file);
}
if (this.#hasEmittedAssets) {
this.#pendingAssetFiles.add(file);
}
}
}

Expand Down Expand Up @@ -294,12 +316,13 @@ export class BuilderState {
}

/**
* Records active bundle results across entry points after compilation and bundling succeed,
* clearing pending compilation/bundle flags.
* Records active bundle results across entry points after compilation and bundling succeed
* and clears pending compilation/bundle flags.
*
* @param bundleResults Newly bundled results keyed by entry point name.
*/
recordBundleOutput(bundleResults: ReadonlyMap<string, BundleResult>): void {
const hadEntryPointsChanges = this.hasEntryPointsChanges;
this.hasCompilationError = false;
this.hasEntryPointsChanges = false;
this.pendingChangedEsmFiles.clear();
Expand All @@ -308,6 +331,126 @@ export class BuilderState {
for (const [name, bundleResult] of bundleResults) {
this.previousBundleResults.set(name, bundleResult);
}

if (bundleResults.size > 0 || hadEntryPointsChanges || this.#emittedBundleFiles.size === 0) {
this.#hasUpdatedBundles = true;
}
}

/**
* Resolves and collects configured library assets to be emitted to or deleted from disk.
*
* @param options Normalized library options containing `assets` and `workspaceRoot`.
* @param modifiedFiles Optional set of modified POSIX file paths in watch mode.
* @returns The collected asset files to emit and delete.
*/
collectAssets(
options: Pick<NormalizedLibraryOptions, 'assets' | 'workspaceRoot'>,
modifiedFiles?: ReadonlySet<string>,
): Promise<CollectedAssets> {
let effectiveModifiedFiles: ReadonlySet<string> | undefined;
if (this.#hasEmittedAssets && modifiedFiles) {
for (const file of modifiedFiles) {
this.#pendingAssetFiles.add(file);
}
effectiveModifiedFiles = this.#pendingAssetFiles;
}

return collectAssetsToEmit(
options.assets,
options.workspaceRoot,
this.#emittedAssets,
effectiveModifiedFiles,
this.#assetMatchers,
);
}

/**
* Records emitted bundle, manifest, and asset output files after disk emission,
* returning any stale or deleted output paths to remove from disk.
*
* @param collectedAssets Collected asset files emitted and deleted in this iteration.
* @param manifestFiles Optional manifest output files generated in this iteration.
* @returns Read-only set of relative POSIX output paths to delete from disk.
*/
recordEmittedOutputs(
collectedAssets: CollectedAssets,
manifestFiles?: readonly OutputFile[],
): ReadonlySet<string> {
const filesToDelete = new Set<string>();

if (this.#hasUpdatedBundles) {
this.#hasUpdatedBundles = false;
const nextBundleFiles = new Set<string>();
for (const result of this.previousBundleResults.values()) {
for (const file of result.esmOutputFiles) {
nextBundleFiles.add(file);
}
for (const file of result.dtsOutputFiles) {
nextBundleFiles.add(file);
}
}

for (const prevFile of this.#emittedBundleFiles) {
if (!nextBundleFiles.has(prevFile)) {
filesToDelete.add(prevFile);
}
}

this.#emittedBundleFiles = nextBundleFiles;
}

if (manifestFiles) {
const nextManifestFiles = new Set(manifestFiles.map((file) => file.path));
for (const prevFile of this.#emittedManifestFiles) {
if (!nextManifestFiles.has(prevFile)) {
filesToDelete.add(prevFile);
}
}

this.#emittedManifestFiles = nextManifestFiles;
}

const { filesToDelete: toDelete, filesToEmit: toEmit } = collectedAssets;

for (const emittedAsset of toEmit) {
let outputs = this.#emittedAssets.get(emittedAsset.source);
if (!outputs) {
outputs = new Set();
this.#emittedAssets.set(emittedAsset.source, outputs);
}
outputs.add(emittedAsset.path);
}

if (toDelete.length) {
const candidateDeletes = new Set<string>();
for (const source of toDelete) {
const outputs = this.#emittedAssets.get(source);
if (outputs) {
for (const output of outputs) {
candidateDeletes.add(output);
}
this.#emittedAssets.delete(source);
}
}

for (const outputs of this.#emittedAssets.values()) {
for (const output of outputs) {
candidateDeletes.delete(output);
}
}

for (const output of candidateDeletes) {
filesToDelete.add(output);
}
}

this.hasEmittedManifests = true;
this.#hasEmittedTslib = this.hasTslibImport;
this.#hasEmittedAssets = true;
this.#pendingAssetFiles.clear();

return filesToDelete;
}

/**
Expand All @@ -317,17 +460,22 @@ export class BuilderState {
await this.#singleProgramCache?.compilationInstance.close?.();
this.#singleProgramCache = undefined;
this.#compilationMode = undefined;
this.hasEmittedTslib = false;
this.hasEmittedAssets = false;
this.#hasEmittedTslib = false;
this.#hasEmittedAssets = false;
this.hasCompilationError = false;
this.hasEmittedManifests = false;
this.hasEntryPointsChanges = false;
this.previousBundleResults.clear();
this.pendingChangedEsmFiles.clear();
this.pendingChangedDtsFiles.clear();
this.#pendingCompilationFiles.clear();
this.#pendingAssetFiles.clear();
this.#referencingFiles.clear();
this.#referencedByFile.clear();
this.#watchedCompilationFiles.clear();
this.#emittedBundleFiles.clear();
this.#emittedManifestFiles.clear();
this.#emittedAssets.clear();
this.#hasUpdatedBundles = false;
}
}
Loading
Loading