Skip to content

Commit aee35d4

Browse files
authored
refactor(@angular/build): consolidate library asset collection and watch check
Remove `checkAssetChanges` and reuse the assets collected by `collectAssetsToEmit` in watch mode so asset matching only runs once per watch iteration.
1 parent f61de66 commit aee35d4

5 files changed

Lines changed: 93 additions & 102 deletions

File tree

‎packages/angular/build/src/builders/library/builder.ts‎

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@ export async function* executeLibraryBuilder(
107107
target,
108108
);
109109

110-
// Track all referenced files for watch mode
111-
const allWatchedFiles = new Set<string>([
110+
// Track all referenced compilation files for watch mode
111+
const watchedCompilationFiles = new Set<string>([
112112
toPosixPath(tsConfigPath),
113113
toPosixPath(packageJsonPath),
114114
]);
115115

116116
for (const entryPoint of normalizedOptions.entryPoints.values()) {
117-
allWatchedFiles.add(toPosixPath(entryPoint.entryFilePath));
117+
watchedCompilationFiles.add(toPosixPath(entryPoint.entryFilePath));
118118
}
119119

120120
if (isWatchMode) {
@@ -131,7 +131,7 @@ export async function* executeLibraryBuilder(
131131
poll,
132132
preserveSymlinks,
133133
signal,
134-
watchFiles: allWatchedFiles,
134+
watchFiles: watchedCompilationFiles,
135135
});
136136

137137
context.addTeardown?.(() => void watcher?.close());
@@ -143,7 +143,7 @@ export async function* executeLibraryBuilder(
143143
{
144144
options: normalizedOptions,
145145
stylesheetBundler,
146-
allWatchedFiles,
146+
watchedCompilationFiles,
147147
isWatchMode,
148148
context,
149149
buildState,
@@ -162,7 +162,7 @@ export async function* executeLibraryBuilder(
162162
watcher,
163163
normalizedOptions,
164164
stylesheetBundler,
165-
allWatchedFiles,
165+
watchedCompilationFiles,
166166
context,
167167
withProgress,
168168
buildState,
@@ -190,7 +190,7 @@ async function executeBuild(
190190
buildAction: typeof import('./pipeline/build-action').buildAction,
191191
): Promise<BuilderOutput> {
192192
const startTime = process.hrtime.bigint();
193-
const { context, allWatchedFiles, isWatchMode } = actionContext;
193+
const { context, watchedCompilationFiles, isWatchMode } = actionContext;
194194

195195
try {
196196
await withProgress(message, () => buildAction(actionContext));
@@ -206,7 +206,14 @@ async function executeBuild(
206206

207207
return { success: false, error: error.message };
208208
} finally {
209-
watcher?.add(Array.from(allWatchedFiles));
209+
if (watcher) {
210+
watcher.add(Array.from(watchedCompilationFiles));
211+
212+
const { assetsToEmit } = actionContext;
213+
if (assetsToEmit?.length) {
214+
watcher.add(assetsToEmit.map((asset) => asset.source));
215+
}
216+
}
210217
}
211218
}
212219

@@ -217,15 +224,15 @@ async function* runWatchLoop(
217224
watcher: BuildWatcher,
218225
options: NormalizedLibraryOptions,
219226
stylesheetBundler: ReturnType<typeof createComponentStylesheetBundlerForLibrary>,
220-
allWatchedFiles: Set<string>,
227+
watchedCompilationFiles: Set<string>,
221228
context: BuilderContext,
222229
withProgress: typeof withSpinner,
223230
buildState: SingleBuildState,
224231
buildAction: typeof import('./pipeline/build-action').buildAction,
225232
hasModifiedWatchedFile: typeof import('./pipeline/build-action').hasModifiedWatchedFile,
226233
signal?: AbortSignal,
227234
): AsyncIterableIterator<BuilderOutput> {
228-
const { checkAssetChanges } = await import('./pipeline/assets');
235+
const { collectAssetsToEmit } = await import('./pipeline/assets');
229236

230237
const { workspaceRoot, packageJsonPath, assets, clearScreen } = options;
231238
const posixPackageJsonPath = toPosixPath(packageJsonPath);
@@ -281,7 +288,7 @@ async function* runWatchLoop(
281288
packageJson,
282289
options,
283290
buildState,
284-
allWatchedFiles,
291+
watchedCompilationFiles,
285292
packageJsonPath,
286293
);
287294

@@ -302,13 +309,15 @@ async function* runWatchLoop(
302309
!buildState.singleProgramCache ||
303310
buildState.hasCompilationError ||
304311
buildState.hasEntryPointsChanges ||
305-
hasModifiedWatchedFile(changedFiles, allWatchedFiles, posixPackageJsonPath);
312+
hasModifiedWatchedFile(changedFiles, watchedCompilationFiles, posixPackageJsonPath);
313+
314+
const assetsToEmit = await collectAssetsToEmit(
315+
assets,
316+
workspaceRoot,
317+
buildState.hasEmittedAssets ? changedFiles : undefined,
318+
);
306319

307-
if (
308-
!hasSourceChanges &&
309-
!hasPackageJsonChanges &&
310-
!checkAssetChanges(assets, workspaceRoot, changedFiles)
311-
) {
320+
if (!hasSourceChanges && !hasPackageJsonChanges && assetsToEmit.length === 0) {
312321
continue;
313322
}
314323

@@ -317,11 +326,12 @@ async function* runWatchLoop(
317326
{
318327
options,
319328
stylesheetBundler,
320-
allWatchedFiles,
329+
watchedCompilationFiles,
321330
isWatchMode: true,
322331
context,
323332
buildState,
324333
modifiedFiles: changedFiles,
334+
assetsToEmit,
325335
},
326336
withProgress,
327337
watcher,

‎packages/angular/build/src/builders/library/pipeline/assets.ts‎

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,23 @@ import type { NormalizedLibraryOptions } from '../types';
1515
import { type DiskOutputFile, createDiskOutputFile } from './utils';
1616

1717
/**
18-
* Resolves and collects configured library assets to be emitted to disk,
19-
* and registers their source paths with the watch set.
18+
* Resolves and collects configured library assets to be emitted to disk.
2019
*
2120
* @param assets The normalized asset patterns.
2221
* @param workspaceRoot The workspace root directory path.
23-
* @param allWatchedFiles Set collecting all watched file paths for watch mode.
2422
* @param modifiedFiles Optional set of modified file paths for incremental copying in watch mode.
2523
* @returns An array of disk file emission descriptors.
2624
*/
2725
export async function collectAssetsToEmit(
2826
assets: NormalizedLibraryOptions['assets'],
2927
workspaceRoot: string,
30-
allWatchedFiles: Set<string>,
3128
modifiedFiles?: ReadonlySet<string>,
3229
): Promise<DiskOutputFile[]> {
33-
if (assets.length === 0) {
30+
if (assets.length === 0 || modifiedFiles?.size === 0) {
3431
return [];
3532
}
3633

3734
if (modifiedFiles) {
38-
if (modifiedFiles.size === 0) {
39-
return [];
40-
}
41-
4235
const matchers = createAssetMatchers(assets, workspaceRoot);
4336
const filesToEmit: DiskOutputFile[] = [];
4437

@@ -58,7 +51,6 @@ export async function collectAssetsToEmit(
5851

5952
if (statSync(resolvedFile, { throwIfNoEntry: false })?.isFile()) {
6053
filesToEmit.push(createDiskOutputFile(resolvedFile, path.join(asset.output, relative)));
61-
allWatchedFiles.add(posixFile);
6254
}
6355
}
6456
}
@@ -67,50 +59,8 @@ export async function collectAssetsToEmit(
6759
}
6860

6961
const resolvedAssets = await resolveAssets(assets, workspaceRoot);
70-
const filesToEmit: DiskOutputFile[] = [];
71-
72-
for (const { source, destination } of resolvedAssets) {
73-
filesToEmit.push(createDiskOutputFile(source, destination));
74-
allWatchedFiles.add(toPosixPath(source));
75-
}
76-
77-
return filesToEmit;
78-
}
79-
80-
/**
81-
* Checks whether any configured library assets were modified.
82-
*
83-
* @param assets The normalized asset patterns.
84-
* @param workspaceRoot The workspace root directory path.
85-
* @param changedFiles Set of changed file paths.
86-
* @returns True if any asset file was modified.
87-
*/
88-
export function checkAssetChanges(
89-
assets: NormalizedLibraryOptions['assets'],
90-
workspaceRoot: string,
91-
changedFiles: ReadonlySet<string>,
92-
): boolean {
93-
if (assets.length === 0 || changedFiles.size === 0) {
94-
return false;
95-
}
96-
97-
const matchers = createAssetMatchers(assets, workspaceRoot);
98-
99-
for (const file of changedFiles) {
100-
const resolvedFile = path.isAbsolute(file) ? file : path.resolve(workspaceRoot, file);
101-
const posixFile = toPosixPath(resolvedFile);
102-
103-
for (const { posixInputPrefix, isMatch } of matchers) {
104-
if (posixFile.startsWith(posixInputPrefix)) {
105-
const relative = posixFile.slice(posixInputPrefix.length);
106-
if (isMatch(relative)) {
107-
return true;
108-
}
109-
}
110-
}
111-
}
11262

113-
return false;
63+
return resolvedAssets.map(({ source, destination }) => createDiskOutputFile(source, destination));
11464
}
11565

11666
function createAssetMatchers(assets: NormalizedLibraryOptions['assets'], workspaceRoot: string) {

‎packages/angular/build/src/builders/library/pipeline/build-action.ts‎

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
import type { BuilderContext } from '@angular-devkit/architect';
1010
import { constants, copyFile, mkdir, writeFile } from 'node:fs/promises';
1111
import path from 'node:path';
12+
import type { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/component-stylesheets';
1213
import { emitFilesToDisk } from '../../../tools/esbuild/utils';
1314
import { toPosixPath } from '../../../utils/path';
1415
import type { NormalizedLibraryOptions, SingleBuildState } from '../types';
1516
import { collectAssetsToEmit } from './assets';
1617
import { type BundleEntryPointInput, bundleEntryPoints } from './bundler';
1718
import { compileLibrary } from './compilation';
1819
import { generatePackageManifests } from './package-manifests';
19-
import type { createComponentStylesheetBundlerForLibrary } from './stylesheet-bundler';
20-
import type { OutputFile } from './utils';
20+
import type { DiskOutputFile, OutputFile } from './utils';
2121

2222
/**
2323
* Creates a fresh {@link SingleBuildState} instance.
@@ -35,13 +35,40 @@ export function createSingleBuildState(): SingleBuildState {
3535
* Context required to execute a single library build iteration.
3636
*/
3737
export interface BuildActionContext {
38+
/** Normalized options for the library build. */
3839
options: NormalizedLibraryOptions;
40+
41+
/** The Architect builder context. */
3942
context: BuilderContext;
40-
stylesheetBundler: ReturnType<typeof createComponentStylesheetBundlerForLibrary>;
43+
44+
/** Bundler instance used to process component stylesheets. */
45+
stylesheetBundler: ComponentStylesheetBundler;
46+
47+
/** Whether the builder is running in watch mode. */
4148
isWatchMode: boolean;
42-
allWatchedFiles: Set<string>;
49+
50+
/**
51+
* Set of file paths tracked for compilation and configuration
52+
* (including `tsconfig.json`, `package.json`, entry points, and referenced source,
53+
* template, and stylesheet files). Updated during compilation and used to determine
54+
* whether file changes require recompiling entry points, excluding asset files so
55+
* asset-only changes do not trigger code compilation.
56+
*/
57+
watchedCompilationFiles: Set<string>;
58+
59+
/** State preserved across incremental builds in watch mode. */
4360
buildState: SingleBuildState;
61+
62+
/** Set of file paths modified since the last build iteration. */
4463
modifiedFiles?: Set<string>;
64+
65+
/**
66+
* Asset files to emit to disk for the current build iteration.
67+
* Pre-collected in the watch loop to avoid redundant asset matching, or resolved and
68+
* populated by `buildAction` when omitted (such as during the initial build) so the
69+
* caller can register their source paths with the file watcher.
70+
*/
71+
assetsToEmit?: DiskOutputFile[];
4572
}
4673

4774
/**
@@ -57,7 +84,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
5784
context,
5885
stylesheetBundler,
5986
isWatchMode,
60-
allWatchedFiles,
87+
watchedCompilationFiles,
6188
buildState,
6289
modifiedFiles,
6390
} = actionContext;
@@ -76,7 +103,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
76103
buildState.hasEntryPointsChanges ||
77104
pendingChangedEsmFiles.size > 0 ||
78105
pendingChangedDtsFiles.size > 0 ||
79-
hasModifiedWatchedFile(modifiedFiles, allWatchedFiles, posixPackageJsonPath);
106+
hasModifiedWatchedFile(modifiedFiles, watchedCompilationFiles, posixPackageJsonPath);
80107
const shouldGenerateManifests = !buildState.hasEmittedManifests;
81108

82109
if (shouldGenerateManifests) {
@@ -107,7 +134,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
107134
buildState.singleProgramCache = cache;
108135

109136
for (const file of referencedFiles) {
110-
allWatchedFiles.add(file);
137+
watchedCompilationFiles.add(file);
111138
}
112139

113140
for (const file of changedEsmFiles) {
@@ -170,14 +197,12 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
170197
filesToEmit.push(...generatePackageManifests(options, isWatchMode));
171198
}
172199

173-
filesToEmit.push(
174-
...(await collectAssetsToEmit(
175-
options.assets,
176-
options.workspaceRoot,
177-
allWatchedFiles,
178-
buildState.hasEmittedAssets ? modifiedFiles : undefined,
179-
)),
180-
);
200+
const resolvedAssetsToEmit = (actionContext.assetsToEmit ??= await collectAssetsToEmit(
201+
options.assets,
202+
options.workspaceRoot,
203+
buildState.hasEmittedAssets ? modifiedFiles : undefined,
204+
));
205+
filesToEmit.push(...resolvedAssetsToEmit);
181206

182207
await emitFilesToDisk<OutputFile>(filesToEmit, async (file) => {
183208
const fullFilePath = path.join(options.outputPath, file.path);
@@ -200,11 +225,11 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
200225

201226
export function hasModifiedWatchedFile(
202227
modifiedFiles: ReadonlySet<string>,
203-
allWatchedFiles: ReadonlySet<string>,
228+
watchedCompilationFiles: ReadonlySet<string>,
204229
posixPackageJsonPath: string,
205230
): boolean {
206231
for (const file of modifiedFiles) {
207-
if (file !== posixPackageJsonPath && allWatchedFiles.has(file)) {
232+
if (file !== posixPackageJsonPath && watchedCompilationFiles.has(file)) {
208233
return true;
209234
}
210235
}

‎packages/angular/build/src/builders/library/pipeline/entry-points.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export function updateWatchedEntryPoints(
238238
packageJson: PackageJsonData,
239239
options: NormalizedLibraryOptions,
240240
buildState: SingleBuildState,
241-
allWatchedFiles: Set<string>,
241+
watchedCompilationFiles: Set<string>,
242242
packageJsonPath: string,
243243
): void {
244244
const newEntryPoints = normalizeEntryPoints(
@@ -250,7 +250,7 @@ export function updateWatchedEntryPoints(
250250

251251
if (haveEntryPointsChanged(options.entryPoints, newEntryPoints)) {
252252
for (const entryPoint of newEntryPoints.values()) {
253-
allWatchedFiles.add(toPosixPath(entryPoint.entryFilePath));
253+
watchedCompilationFiles.add(toPosixPath(entryPoint.entryFilePath));
254254
}
255255

256256
for (const name of buildState.previousBundleResults.keys()) {

0 commit comments

Comments
 (0)