Skip to content

Commit 83cf2bf

Browse files
committed
refactor(@angular/build): simplify library asset resolution and watch handling
Use `resolveAssets` in `collectAssetsToEmit` and reuse collected assets in watch mode instead of running duplicate `picomatch` asset matchers via `checkAssetChanges`.
1 parent 76dd17d commit 83cf2bf

4 files changed

Lines changed: 27 additions & 105 deletions

File tree

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ async function* runWatchLoop(
225225
hasModifiedWatchedFile: typeof import('./pipeline/build-action').hasModifiedWatchedFile,
226226
signal?: AbortSignal,
227227
): AsyncIterableIterator<BuilderOutput> {
228-
const { checkAssetChanges } = await import('./pipeline/assets');
228+
const { collectAssetsToEmit } = await import('./pipeline/assets');
229229

230230
const { workspaceRoot, packageJsonPath, assets, clearScreen } = options;
231231
const posixPackageJsonPath = toPosixPath(packageJsonPath);
@@ -304,11 +304,13 @@ async function* runWatchLoop(
304304
buildState.hasEntryPointsChanges ||
305305
hasModifiedWatchedFile(changedFiles, allWatchedFiles, posixPackageJsonPath);
306306

307-
if (
308-
!hasSourceChanges &&
309-
!hasPackageJsonChanges &&
310-
!checkAssetChanges(assets, workspaceRoot, changedFiles)
311-
) {
307+
const assetsToEmit = await collectAssetsToEmit(
308+
assets,
309+
workspaceRoot,
310+
buildState.hasEmittedAssets ? changedFiles : undefined,
311+
);
312+
313+
if (!hasSourceChanges && !hasPackageJsonChanges && assetsToEmit.length === 0) {
312314
continue;
313315
}
314316

@@ -322,6 +324,7 @@ async function* runWatchLoop(
322324
context,
323325
buildState,
324326
modifiedFiles: changedFiles,
327+
assetsToEmit,
325328
},
326329
withProgress,
327330
watcher,

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

Lines changed: 6 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -6,122 +6,36 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { statSync } from 'node:fs';
10-
import path from 'node:path';
11-
import picomatch from 'picomatch';
129
import { toPosixPath } from '../../../utils/path';
13-
import { DEFAULT_ASSET_IGNORE, resolveAssets } from '../../../utils/resolve-assets';
10+
import { resolveAssets } from '../../../utils/resolve-assets';
1411
import type { NormalizedLibraryOptions } from '../types';
1512
import { type DiskOutputFile, createDiskOutputFile } from './utils';
1613

1714
/**
18-
* Resolves and collects configured library assets to be emitted to disk,
19-
* and registers their source paths with the watch set.
15+
* Resolves and collects configured library assets to be emitted to disk.
2016
*
2117
* @param assets The normalized asset patterns.
2218
* @param workspaceRoot The workspace root directory path.
23-
* @param allWatchedFiles Set collecting all watched file paths for watch mode.
2419
* @param modifiedFiles Optional set of modified file paths for incremental copying in watch mode.
2520
* @returns An array of disk file emission descriptors.
2621
*/
2722
export async function collectAssetsToEmit(
2823
assets: NormalizedLibraryOptions['assets'],
2924
workspaceRoot: string,
30-
allWatchedFiles: Set<string>,
3125
modifiedFiles?: ReadonlySet<string>,
3226
): Promise<DiskOutputFile[]> {
33-
if (assets.length === 0) {
27+
if (assets.length === 0 || modifiedFiles?.size === 0) {
3428
return [];
3529
}
3630

37-
if (modifiedFiles) {
38-
if (modifiedFiles.size === 0) {
39-
return [];
40-
}
41-
42-
const matchers = createAssetMatchers(assets, workspaceRoot);
43-
const filesToEmit: DiskOutputFile[] = [];
44-
45-
for (const file of modifiedFiles) {
46-
const resolvedFile = path.isAbsolute(file) ? file : path.resolve(workspaceRoot, file);
47-
const posixFile = toPosixPath(resolvedFile);
48-
49-
for (const { asset, posixInputPrefix, isMatch } of matchers) {
50-
if (!posixFile.startsWith(posixInputPrefix)) {
51-
continue;
52-
}
53-
54-
const relative = posixFile.slice(posixInputPrefix.length);
55-
if (!isMatch(relative)) {
56-
continue;
57-
}
58-
59-
if (statSync(resolvedFile, { throwIfNoEntry: false })?.isFile()) {
60-
filesToEmit.push(createDiskOutputFile(resolvedFile, path.join(asset.output, relative)));
61-
allWatchedFiles.add(posixFile);
62-
}
63-
}
64-
}
65-
66-
return filesToEmit;
67-
}
68-
6931
const resolvedAssets = await resolveAssets(assets, workspaceRoot);
7032
const filesToEmit: DiskOutputFile[] = [];
7133

7234
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-
}
35+
if (!modifiedFiles || modifiedFiles.has(toPosixPath(source))) {
36+
filesToEmit.push(createDiskOutputFile(source, destination));
11037
}
11138
}
11239

113-
return false;
114-
}
115-
116-
function createAssetMatchers(assets: NormalizedLibraryOptions['assets'], workspaceRoot: string) {
117-
return assets.map((asset) => {
118-
const absInput = path.resolve(workspaceRoot, asset.input);
119-
const posixInput = toPosixPath(absInput).replace(/\/+$/, '');
120-
const isMatch = picomatch(asset.glob, {
121-
dot: true,
122-
ignore: [...DEFAULT_ASSET_IGNORE, ...(asset.ignore ?? [])],
123-
});
124-
125-
return { asset, posixInputPrefix: `${posixInput}/`, isMatch };
126-
});
40+
return filesToEmit;
12741
}

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { type BundleEntryPointInput, bundleEntryPoints } from './bundler';
1717
import { compileLibrary } from './compilation';
1818
import { generatePackageManifests } from './package-manifests';
1919
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.
@@ -42,6 +42,7 @@ export interface BuildActionContext {
4242
allWatchedFiles: Set<string>;
4343
buildState: SingleBuildState;
4444
modifiedFiles?: Set<string>;
45+
assetsToEmit?: DiskOutputFile[];
4546
}
4647

4748
/**
@@ -60,6 +61,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
6061
allWatchedFiles,
6162
buildState,
6263
modifiedFiles,
64+
assetsToEmit,
6365
} = actionContext;
6466

6567
const posixPackageJsonPath = toPosixPath(options.packageJsonPath);
@@ -170,14 +172,17 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
170172
filesToEmit.push(...generatePackageManifests(options, isWatchMode));
171173
}
172174

173-
filesToEmit.push(
174-
...(await collectAssetsToEmit(
175+
const resolvedAssetsToEmit =
176+
assetsToEmit ??
177+
(await collectAssetsToEmit(
175178
options.assets,
176179
options.workspaceRoot,
177-
allWatchedFiles,
178180
buildState.hasEmittedAssets ? modifiedFiles : undefined,
179-
)),
180-
);
181+
));
182+
for (const asset of resolvedAssetsToEmit) {
183+
allWatchedFiles.add(toPosixPath(asset.source));
184+
}
185+
filesToEmit.push(...resolvedAssetsToEmit);
181186

182187
await emitFilesToDisk<OutputFile>(filesToEmit, async (file) => {
183188
const fullFilePath = path.join(options.outputPath, file.path);

‎packages/angular/build/src/utils/resolve-assets.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { isSubDirectory } from './path';
1313
/**
1414
* Default glob ignore patterns for assets.
1515
*/
16-
export const DEFAULT_ASSET_IGNORE = ['.gitkeep', '**/.DS_Store', '**/Thumbs.db'] as const;
16+
const DEFAULT_ASSET_IGNORE = ['.gitkeep', '**/.DS_Store', '**/Thumbs.db'] as const;
1717

1818
export async function resolveAssets(
1919
entries: {

0 commit comments

Comments
 (0)