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
46 changes: 28 additions & 18 deletions packages/angular/build/src/builders/library/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ export async function* executeLibraryBuilder(
target,
);

// Track all referenced files for watch mode
const allWatchedFiles = new Set<string>([
// Track all referenced compilation files for watch mode
const watchedCompilationFiles = new Set<string>([
toPosixPath(tsConfigPath),
toPosixPath(packageJsonPath),
]);

for (const entryPoint of normalizedOptions.entryPoints.values()) {
allWatchedFiles.add(toPosixPath(entryPoint.entryFilePath));
watchedCompilationFiles.add(toPosixPath(entryPoint.entryFilePath));
}

if (isWatchMode) {
Expand All @@ -131,7 +131,7 @@ export async function* executeLibraryBuilder(
poll,
preserveSymlinks,
signal,
watchFiles: allWatchedFiles,
watchFiles: watchedCompilationFiles,
});

context.addTeardown?.(() => void watcher?.close());
Expand All @@ -143,7 +143,7 @@ export async function* executeLibraryBuilder(
{
options: normalizedOptions,
stylesheetBundler,
allWatchedFiles,
watchedCompilationFiles,
isWatchMode,
context,
buildState,
Expand All @@ -162,7 +162,7 @@ export async function* executeLibraryBuilder(
watcher,
normalizedOptions,
stylesheetBundler,
allWatchedFiles,
watchedCompilationFiles,
context,
withProgress,
buildState,
Expand Down Expand Up @@ -190,7 +190,7 @@ async function executeBuild(
buildAction: typeof import('./pipeline/build-action').buildAction,
): Promise<BuilderOutput> {
const startTime = process.hrtime.bigint();
const { context, allWatchedFiles, isWatchMode } = actionContext;
const { context, watchedCompilationFiles, isWatchMode } = actionContext;

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

return { success: false, error: error.message };
} finally {
watcher?.add(Array.from(allWatchedFiles));
if (watcher) {
watcher.add(Array.from(watchedCompilationFiles));

const { assetsToEmit } = actionContext;
if (assetsToEmit?.length) {
watcher.add(assetsToEmit.map((asset) => asset.source));
}
}
}
}

Expand All @@ -217,15 +224,15 @@ async function* runWatchLoop(
watcher: BuildWatcher,
options: NormalizedLibraryOptions,
stylesheetBundler: ReturnType<typeof createComponentStylesheetBundlerForLibrary>,
allWatchedFiles: Set<string>,
watchedCompilationFiles: Set<string>,
context: BuilderContext,
withProgress: typeof withSpinner,
buildState: SingleBuildState,
buildAction: typeof import('./pipeline/build-action').buildAction,
hasModifiedWatchedFile: typeof import('./pipeline/build-action').hasModifiedWatchedFile,
signal?: AbortSignal,
): AsyncIterableIterator<BuilderOutput> {
const { checkAssetChanges } = await import('./pipeline/assets');
const { collectAssetsToEmit } = await import('./pipeline/assets');

const { workspaceRoot, packageJsonPath, assets, clearScreen } = options;
const posixPackageJsonPath = toPosixPath(packageJsonPath);
Expand Down Expand Up @@ -281,7 +288,7 @@ async function* runWatchLoop(
packageJson,
options,
buildState,
allWatchedFiles,
watchedCompilationFiles,
packageJsonPath,
);

Expand All @@ -302,13 +309,15 @@ async function* runWatchLoop(
!buildState.singleProgramCache ||
buildState.hasCompilationError ||
buildState.hasEntryPointsChanges ||
hasModifiedWatchedFile(changedFiles, allWatchedFiles, posixPackageJsonPath);
hasModifiedWatchedFile(changedFiles, watchedCompilationFiles, posixPackageJsonPath);

const assetsToEmit = await collectAssetsToEmit(
assets,
workspaceRoot,
buildState.hasEmittedAssets ? changedFiles : undefined,
);
Comment thread
alan-agius4 marked this conversation as resolved.

if (
!hasSourceChanges &&
!hasPackageJsonChanges &&
!checkAssetChanges(assets, workspaceRoot, changedFiles)
) {
if (!hasSourceChanges && !hasPackageJsonChanges && assetsToEmit.length === 0) {
continue;
}

Expand All @@ -317,11 +326,12 @@ async function* runWatchLoop(
{
options,
stylesheetBundler,
allWatchedFiles,
watchedCompilationFiles,
isWatchMode: true,
context,
buildState,
modifiedFiles: changedFiles,
assetsToEmit,
},
withProgress,
watcher,
Expand Down
56 changes: 3 additions & 53 deletions packages/angular/build/src/builders/library/pipeline/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,23 @@ import type { NormalizedLibraryOptions } from '../types';
import { type DiskOutputFile, createDiskOutputFile } from './utils';

/**
* Resolves and collects configured library assets to be emitted to disk,
* and registers their source paths with the watch set.
* Resolves and collects configured library assets to be emitted to disk.
*
* @param assets The normalized asset patterns.
* @param workspaceRoot The workspace root directory path.
* @param allWatchedFiles Set collecting all watched file paths for watch mode.
* @param modifiedFiles Optional set of modified file paths for incremental copying in watch mode.
* @returns An array of disk file emission descriptors.
*/
export async function collectAssetsToEmit(
assets: NormalizedLibraryOptions['assets'],
workspaceRoot: string,
allWatchedFiles: Set<string>,
modifiedFiles?: ReadonlySet<string>,
): Promise<DiskOutputFile[]> {
if (assets.length === 0) {
if (assets.length === 0 || modifiedFiles?.size === 0) {
return [];
}

if (modifiedFiles) {
if (modifiedFiles.size === 0) {
return [];
}

const matchers = createAssetMatchers(assets, workspaceRoot);
const filesToEmit: DiskOutputFile[] = [];

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

if (statSync(resolvedFile, { throwIfNoEntry: false })?.isFile()) {
filesToEmit.push(createDiskOutputFile(resolvedFile, path.join(asset.output, relative)));
allWatchedFiles.add(posixFile);
}
}
}
Expand All @@ -67,50 +59,8 @@ export async function collectAssetsToEmit(
}

const resolvedAssets = await resolveAssets(assets, workspaceRoot);
const filesToEmit: DiskOutputFile[] = [];

for (const { source, destination } of resolvedAssets) {
filesToEmit.push(createDiskOutputFile(source, destination));
allWatchedFiles.add(toPosixPath(source));
}

return filesToEmit;
}

/**
* Checks whether any configured library assets were modified.
*
* @param assets The normalized asset patterns.
* @param workspaceRoot The workspace root directory path.
* @param changedFiles Set of changed file paths.
* @returns True if any asset file was modified.
*/
export function checkAssetChanges(
assets: NormalizedLibraryOptions['assets'],
workspaceRoot: string,
changedFiles: ReadonlySet<string>,
): boolean {
if (assets.length === 0 || changedFiles.size === 0) {
return false;
}

const matchers = createAssetMatchers(assets, workspaceRoot);

for (const file of changedFiles) {
const resolvedFile = path.isAbsolute(file) ? file : path.resolve(workspaceRoot, file);
const posixFile = toPosixPath(resolvedFile);

for (const { posixInputPrefix, isMatch } of matchers) {
if (posixFile.startsWith(posixInputPrefix)) {
const relative = posixFile.slice(posixInputPrefix.length);
if (isMatch(relative)) {
return true;
}
}
}
}

return false;
return resolvedAssets.map(({ source, destination }) => createDiskOutputFile(source, destination));
}

function createAssetMatchers(assets: NormalizedLibraryOptions['assets'], workspaceRoot: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import type { BuilderContext } from '@angular-devkit/architect';
import { constants, copyFile, mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import type { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/component-stylesheets';
import { emitFilesToDisk } from '../../../tools/esbuild/utils';
import { toPosixPath } from '../../../utils/path';
import type { NormalizedLibraryOptions, SingleBuildState } from '../types';
import { collectAssetsToEmit } from './assets';
import { type BundleEntryPointInput, bundleEntryPoints } from './bundler';
import { compileLibrary } from './compilation';
import { generatePackageManifests } from './package-manifests';
import type { createComponentStylesheetBundlerForLibrary } from './stylesheet-bundler';
import type { OutputFile } from './utils';
import type { DiskOutputFile, OutputFile } from './utils';

/**
* Creates a fresh {@link SingleBuildState} instance.
Expand All @@ -35,13 +35,40 @@ export function createSingleBuildState(): SingleBuildState {
* Context required to execute a single library build iteration.
*/
export interface BuildActionContext {
/** Normalized options for the library build. */
options: NormalizedLibraryOptions;

/** The Architect builder context. */
context: BuilderContext;
stylesheetBundler: ReturnType<typeof createComponentStylesheetBundlerForLibrary>;

/** Bundler instance used to process component stylesheets. */
stylesheetBundler: ComponentStylesheetBundler;

/** Whether the builder is running in watch mode. */
isWatchMode: boolean;
allWatchedFiles: Set<string>;

/**
* Set of file paths tracked for compilation and configuration
* (including `tsconfig.json`, `package.json`, entry points, and referenced source,
* template, and stylesheet files). Updated during compilation and used to determine
* whether file changes require recompiling entry points, excluding asset files so
* asset-only changes do not trigger code compilation.
*/
watchedCompilationFiles: Set<string>;

/** State preserved across incremental builds in watch mode. */
buildState: SingleBuildState;

/** Set of file paths modified since the last build iteration. */
modifiedFiles?: Set<string>;

/**
* Asset files to emit to disk for the current build iteration.
* Pre-collected in the watch loop to avoid redundant asset matching, or resolved and
* populated by `buildAction` when omitted (such as during the initial build) so the
* caller can register their source paths with the file watcher.
*/
assetsToEmit?: DiskOutputFile[];
}

/**
Expand All @@ -57,7 +84,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
context,
stylesheetBundler,
isWatchMode,
allWatchedFiles,
watchedCompilationFiles,
buildState,
modifiedFiles,
} = actionContext;
Expand All @@ -76,7 +103,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
buildState.hasEntryPointsChanges ||
pendingChangedEsmFiles.size > 0 ||
pendingChangedDtsFiles.size > 0 ||
hasModifiedWatchedFile(modifiedFiles, allWatchedFiles, posixPackageJsonPath);
hasModifiedWatchedFile(modifiedFiles, watchedCompilationFiles, posixPackageJsonPath);
const shouldGenerateManifests = !buildState.hasEmittedManifests;

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

for (const file of referencedFiles) {
allWatchedFiles.add(file);
watchedCompilationFiles.add(file);
}

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

filesToEmit.push(
...(await collectAssetsToEmit(
options.assets,
options.workspaceRoot,
allWatchedFiles,
buildState.hasEmittedAssets ? modifiedFiles : undefined,
)),
);
const resolvedAssetsToEmit = (actionContext.assetsToEmit ??= await collectAssetsToEmit(
options.assets,
options.workspaceRoot,
buildState.hasEmittedAssets ? modifiedFiles : undefined,
));
filesToEmit.push(...resolvedAssetsToEmit);

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

export function hasModifiedWatchedFile(
modifiedFiles: ReadonlySet<string>,
allWatchedFiles: ReadonlySet<string>,
watchedCompilationFiles: ReadonlySet<string>,
posixPackageJsonPath: string,
): boolean {
for (const file of modifiedFiles) {
if (file !== posixPackageJsonPath && allWatchedFiles.has(file)) {
if (file !== posixPackageJsonPath && watchedCompilationFiles.has(file)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export function updateWatchedEntryPoints(
packageJson: PackageJsonData,
options: NormalizedLibraryOptions,
buildState: SingleBuildState,
allWatchedFiles: Set<string>,
watchedCompilationFiles: Set<string>,
packageJsonPath: string,
): void {
const newEntryPoints = normalizeEntryPoints(
Expand All @@ -250,7 +250,7 @@ export function updateWatchedEntryPoints(

if (haveEntryPointsChanged(options.entryPoints, newEntryPoints)) {
for (const entryPoint of newEntryPoints.values()) {
allWatchedFiles.add(toPosixPath(entryPoint.entryFilePath));
watchedCompilationFiles.add(toPosixPath(entryPoint.entryFilePath));
}

for (const name of buildState.previousBundleResults.keys()) {
Expand Down
Loading
Loading