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
27 changes: 17 additions & 10 deletions packages/angular/build/src/builders/library/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,10 @@ export async function* executeLibraryBuilder(
);

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

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

if (isWatchMode) {
Expand Down Expand Up @@ -237,6 +234,17 @@ async function executeBuild(

/**
* Runs the watch loop, rebuilding the library as watched files are modified.
*
* @param watcher The file watcher instance.
* @param options The normalized library builder options with POSIX paths.
* @param stylesheetBundler The component stylesheet bundler instance.
* @param watchedCompilationFiles Set of POSIX file paths tracked for compilation.
* @param context The Architect builder context.
* @param withProgress Progress wrapper function (`withSpinner` or `withNoProgress`).
* @param buildState The state preserved across incremental builds.
* @param buildAction The library build action function to execute.
* @param hasModifiedWatchedFile Helper to check whether watched compilation files changed.
* @param signal Optional abort signal to terminate the watch loop.
*/
async function* runWatchLoop(
watcher: BuildWatcher,
Expand All @@ -253,7 +261,6 @@ async function* runWatchLoop(
const { collectAssetsToEmit } = await import('./pipeline/assets');

const { workspaceRoot, packageJsonPath, assets, clearScreen } = options;
const posixPackageJsonPath = toPosixPath(packageJsonPath);

for await (const changes of watcher) {
if (signal?.aborted) {
Expand Down Expand Up @@ -293,13 +300,13 @@ async function* runWatchLoop(

// Check if package.json was modified
let hasPackageJsonChanges = false;
if (changedFiles.has(posixPackageJsonPath)) {
if (changedFiles.has(packageJsonPath)) {
try {
const packageJson = await loadPackageJson(packageJsonPath);
options.packageJson = packageJson;
if (!packageJson.name) {
throw new Error(`The package.json at '${packageJsonPath}' must contain a 'name'.`);
}
options.packageJson = packageJson;
options.packageName = packageJson.name;

updateWatchedEntryPoints(
Expand Down Expand Up @@ -328,7 +335,7 @@ async function* runWatchLoop(
!buildState.singleProgramCache ||
buildState.hasCompilationError ||
buildState.hasEntryPointsChanges ||
hasModifiedWatchedFile(changedFiles, watchedCompilationFiles, posixPackageJsonPath);
hasModifiedWatchedFile(changedFiles, watchedCompilationFiles, packageJsonPath);

const assetsToEmit = await collectAssetsToEmit(
assets,
Expand Down Expand Up @@ -388,7 +395,7 @@ function logMessages(
* @param logger The Architect logger API.
* @param startTime High-resolution timestamp recorded when the build iteration started.
* @param success Whether the build iteration succeeded without errors.
* @param outputPath Optional output directory path to log on successful builds.
* @param outputPath Optional POSIX output directory path to log on successful builds.
*/
function logBuildResult(
logger: logging.LoggerApi,
Expand Down
34 changes: 26 additions & 8 deletions packages/angular/build/src/builders/library/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { normalizeAssetPatterns } from '../../utils';
import { supportColor } from '../../utils/color';
import { assertIsError } from '../../utils/error';
import { normalizeCacheOptions } from '../../utils/normalize-cache';
import { toPosixPath } from '../../utils/path';
import {
generateSearchDirectories,
getTailwindConfig,
Expand All @@ -24,14 +25,25 @@ import { loadPackageJson } from './pipeline/utils';
import type { Schema as LibraryBuilderOptions } from './schema';
import type { NormalizedLibraryOptions, PackageJsonData } from './types';

/**
* Normalizes raw library builder options into {@link NormalizedLibraryOptions} with all
* file and directory paths normalized to POSIX format.
*
* @param context The Architect builder context.
* @param projectName The name of the library project.
* @param options The raw library builder schema options.
* @returns A promise resolving to the normalized library options.
*/
export async function normalizeLibraryOptions(
context: BuilderContext,
projectName: string,
options: LibraryBuilderOptions,
): Promise<NormalizedLibraryOptions> {
const { workspaceRoot } = context;
const workspaceRoot = toPosixPath(context.workspaceRoot);
const projectMetadata = await context.getProjectMetadata(projectName);
const { projectRoot, projectSourceRoot } = getProjectRootPaths(workspaceRoot, projectMetadata);
const projectRootPaths = getProjectRootPaths(workspaceRoot, projectMetadata);
const projectRoot = toPosixPath(projectRootPaths.projectRoot);
const projectSourceRoot = toPosixPath(projectRootPaths.projectSourceRoot);

const {
tsConfig,
Expand All @@ -50,8 +62,8 @@ export async function normalizeLibraryOptions(
clearScreen,
} = options;

const resolvedTsConfigPath = path.resolve(workspaceRoot, tsConfig);
const packageJsonPath = path.join(projectRoot, 'package.json');
const resolvedTsConfigPath = toPosixPath(path.resolve(workspaceRoot, tsConfig));
const packageJsonPath = path.posix.join(projectRoot, 'package.json');

let packageJson: PackageJsonData;
try {
Expand Down Expand Up @@ -99,7 +111,7 @@ export async function normalizeLibraryOptions(
}
defaultAssets.push({
glob: 'README.md',
input: path.dirname(entryPoint.entryFilePath),
input: path.posix.dirname(entryPoint.entryFilePath),
output: entryPoint.name,
});
}
Expand All @@ -109,7 +121,11 @@ export async function normalizeLibraryOptions(
workspaceRoot,
projectRoot,
projectSourceRoot,
);
).map((asset) => ({
...asset,
input: toPosixPath(path.resolve(workspaceRoot, asset.input)),
output: toPosixPath(asset.output),
}));

const cacheOptions = normalizeCacheOptions(
projectMetadata,
Expand All @@ -119,7 +135,7 @@ export async function normalizeLibraryOptions(
);

const styleIncludePaths = (stylePreprocessorOptions?.includePaths ?? []).map((p: string) =>
path.resolve(workspaceRoot, p),
toPosixPath(path.resolve(workspaceRoot, p)),
);

const searchDirectories = await generateSearchDirectories([projectRoot, workspaceRoot]);
Expand All @@ -133,7 +149,9 @@ export async function normalizeLibraryOptions(
projectRoot,
packageName,
packageJson,
outputPath: path.resolve(workspaceRoot, options.outputPath ?? path.join('dist', projectName)),
outputPath: toPosixPath(
path.resolve(workspaceRoot, options.outputPath ?? path.posix.join('dist', projectName)),
),
deleteOutputPath,
packageJsonPath,
tsConfigPath: resolvedTsConfigPath,
Expand Down
29 changes: 12 additions & 17 deletions packages/angular/build/src/builders/library/pipeline/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@
import { statSync } from 'node:fs';
import path from 'node:path';
import picomatch from 'picomatch';
import { toPosixPath } from '../../../utils/path';
import { DEFAULT_ASSET_IGNORE, resolveAssets } from '../../../utils/resolve-assets';
import type { NormalizedLibraryOptions } from '../types';
import { type DiskOutputFile, createDiskOutputFile } from './utils';

/**
* 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 modifiedFiles Optional set of modified file paths for incremental copying in watch mode.
* @param assets The normalized asset patterns with POSIX paths.
* @param workspaceRoot The normalized POSIX workspace root directory path.
* @param modifiedFiles Optional set of modified POSIX file paths for incremental copying in watch mode.
* @returns An array of disk file emission descriptors.
*/
export async function collectAssetsToEmit(
Expand All @@ -32,25 +31,22 @@ export async function collectAssetsToEmit(
}

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

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

for (const { asset, posixInputPrefix, isMatch } of matchers) {
if (!posixFile.startsWith(posixInputPrefix)) {
for (const { asset, prefix, isMatch } of matchers) {
if (!file.startsWith(prefix)) {
continue;
}

const relative = posixFile.slice(posixInputPrefix.length);
const relative = file.slice(prefix.length);
if (!isMatch(relative)) {
continue;
}

if (statSync(resolvedFile, { throwIfNoEntry: false })?.isFile()) {
filesToEmit.push(createDiskOutputFile(resolvedFile, path.join(asset.output, relative)));
if (statSync(file, { throwIfNoEntry: false })?.isFile()) {
filesToEmit.push(createDiskOutputFile(file, path.posix.join(asset.output, relative)));
}
}
}
Comment thread
alan-agius4 marked this conversation as resolved.
Expand All @@ -63,15 +59,14 @@ export async function collectAssetsToEmit(
return resolvedAssets.map(({ source, destination }) => createDiskOutputFile(source, destination));
}

function createAssetMatchers(assets: NormalizedLibraryOptions['assets'], workspaceRoot: string) {
function createAssetMatchers(assets: NormalizedLibraryOptions['assets']) {
return assets.map((asset) => {
const absInput = path.resolve(workspaceRoot, asset.input);
const posixInput = toPosixPath(absInput).replace(/\/+$/, '');
const isMatch = picomatch(asset.glob, {
dot: true,
ignore: [...DEFAULT_ASSET_IGNORE, ...(asset.ignore ?? [])],
});
const prefix = asset.input.endsWith('/') ? asset.input : `${asset.input}/`;

return { asset, posixInputPrefix: `${posixInput}/`, isMatch };
return { asset, prefix, isMatch };
});
}
Comment thread
alan-agius4 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import path from 'node:path';
import type { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/component-stylesheets';
import { emitFilesToDisk } from '../../../utils/emit-files-to-disk';
import { assertIsError } from '../../../utils/error';
import { toPosixPath } from '../../../utils/path';
import type { NormalizedLibraryOptions, SingleBuildState } from '../types';
import { collectAssetsToEmit } from './assets';
import { bundleEntryPoints } from './bundler';
Expand Down Expand Up @@ -49,7 +48,7 @@ export function createSingleBuildState(): SingleBuildState {
* Context required to execute a single library build iteration.
*/
export interface BuildActionContext {
/** Normalized options for the library build. */
/** Normalized options for the library build with POSIX paths. */
options: NormalizedLibraryOptions;

/** The Architect builder context. */
Expand All @@ -62,7 +61,7 @@ export interface BuildActionContext {
isWatchMode: boolean;

/**
* Set of file paths tracked for compilation and configuration
* Set of POSIX 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
Expand All @@ -73,7 +72,7 @@ export interface BuildActionContext {
/** State preserved across incremental builds in watch mode. */
buildState: SingleBuildState;

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

/**
Expand All @@ -91,6 +90,7 @@ export interface BuildActionContext {
* manifest generation, and asset copying.
*
* @param actionContext The build action state and configuration.
* @returns A promise resolving to the build action result containing errors, warnings, and logs.
*/
export async function buildAction(actionContext: BuildActionContext): Promise<BuildActionResult> {
const {
Expand All @@ -106,10 +106,10 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
const warnings: string[] = [];
const logs: string[] = [];

const posixPackageJsonPath = toPosixPath(options.packageJsonPath);
const { packageJsonPath } = options;
const { pendingChangedEsmFiles, pendingChangedDtsFiles } = buildState;

if (!modifiedFiles || modifiedFiles.has(posixPackageJsonPath)) {
if (!modifiedFiles || modifiedFiles.has(packageJsonPath)) {
buildState.hasEmittedManifests = false;
}

Expand All @@ -120,7 +120,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
buildState.hasEntryPointsChanges ||
pendingChangedEsmFiles.size > 0 ||
pendingChangedDtsFiles.size > 0 ||
hasModifiedWatchedFile(modifiedFiles, watchedCompilationFiles, posixPackageJsonPath);
hasModifiedWatchedFile(modifiedFiles, watchedCompilationFiles, packageJsonPath);

try {
if (!buildState.hasEmittedManifests) {
Expand Down Expand Up @@ -172,13 +172,12 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
pendingChangedDtsFiles.add(file);
}

const itemsToBundle = collectEntryPointsToBundle(options, buildState, logs);

let bundleOutput: Awaited<ReturnType<typeof bundleEntryPoints>> | undefined;
let diagnostics: LibraryCompilationDiagnostics;
if (hasStylesheetErrors) {
diagnostics = await diagnosePromise;
} else {
const itemsToBundle = collectEntryPointsToBundle(options, buildState, logs);
try {
[bundleOutput, diagnostics] = await Promise.all([
bundleEntryPoints(itemsToBundle, esmFiles, dtsFiles, {
Expand Down Expand Up @@ -243,7 +242,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
));
filesToEmit.push(...resolvedAssetsToEmit);

await emitFilesToDisk(filesToEmit, (file) => path.join(options.outputPath, file.path));
await emitFilesToDisk(filesToEmit, (file) => path.posix.join(options.outputPath, file.path));

buildState.hasEmittedManifests = true;
buildState.hasEmittedTslib = hasTslibImport;
Expand All @@ -262,16 +261,16 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
*
* @param modifiedFiles Set of POSIX file paths modified since the last build.
* @param watchedCompilationFiles Set of POSIX file paths tracked for compilation.
* @param posixPackageJsonPath The normalized POSIX path to the library's `package.json`.
* @param packageJsonPath The normalized POSIX path to the library's `package.json`.
* @returns `true` if a watched compilation file was modified; otherwise `false`.
*/
export function hasModifiedWatchedFile(
modifiedFiles: ReadonlySet<string>,
watchedCompilationFiles: ReadonlySet<string>,
posixPackageJsonPath: string,
packageJsonPath: string,
): boolean {
for (const file of modifiedFiles) {
if (file !== posixPackageJsonPath && watchedCompilationFiles.has(file)) {
if (file !== packageJsonPath && watchedCompilationFiles.has(file)) {
return true;
}
}
Expand Down
Loading
Loading