FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(@angular/build): re-analyze exports on package.json change in lib… · angular/angular-cli@76dd17d · GitHub

Repository navigation

Commit 76dd17d

Browse files
committed
fix(@angular/build): re-analyze exports on package.json change in library builder
When `package.json` changes in watch mode, the `exports` field is now re-analyzed to detect any added, removed, or modified entry points. New entry points are registered into watched files and trigger a re-compilation, while removed entry points are pruned from cached bundle results.
1 parent 8c273b0 commit 76dd17d

13 files changed

Lines changed: 1237 additions & 276 deletions

File tree

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,11 @@ import { purgeStaleBuildCache } from '../../utils/purge-cache';
2424
import { getSupportedBrowsers } from '../../utils/supported-browsers';
2525
import { assertCompatibleAngularVersion } from '../../utils/version';
2626
import type { BuildWatcher } from '../../utils/watcher';
27-
import {
28-
type NormalizedLibraryOptions,
29-
type PackageJsonData,
30-
normalizeLibraryOptions,
31-
} from './options';
32-
import type { SingleBuildState } from './pipeline/build-action';
27+
import { normalizeLibraryOptions } from './options';
28+
import { updateWatchedEntryPoints } from './pipeline/entry-points';
3329
import type { createComponentStylesheetBundlerForLibrary } from './pipeline/stylesheet-bundler';
3430
import type { Schema as LibraryBuilderOptions } from './schema';
31+
import type { NormalizedLibraryOptions, PackageJsonData, SingleBuildState } from './types';
3532

3633
/**
3734
* Executes the library builder to compile, bundle, and package an Angular library into the Angular Package Format (APF).
@@ -275,6 +272,19 @@ async function* runWatchLoop(
275272
try {
276273
const packageJson = await loadPackageJson(packageJsonPath);
277274
options.packageJson = packageJson;
275+
if (!packageJson.name) {
276+
throw new Error(`The package.json at '${packageJsonPath}' must contain a 'name'.`);
277+
}
278+
options.packageName = packageJson.name;
279+
280+
updateWatchedEntryPoints(
281+
packageJson,
282+
options,
283+
buildState,
284+
allWatchedFiles,
285+
packageJsonPath,
286+
);
287+
278288
hasPackageJsonChanges = true;
279289
} catch (error) {
280290
assertIsError(error);
@@ -290,7 +300,8 @@ async function* runWatchLoop(
290300

291301
const hasSourceChanges =
292302
!buildState.singleProgramCache ||
293-
Boolean(buildState.hasCompilationError) ||
303+
buildState.hasCompilationError ||
304+
buildState.hasEntryPointsChanges ||
294305
hasModifiedWatchedFile(changedFiles, allWatchedFiles, posixPackageJsonPath);
295306

296307
if (

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

Lines changed: 3 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -14,66 +14,16 @@ import { normalizeAssetPatterns } from '../../utils';
1414
import { supportColor } from '../../utils/color';
1515
import { assertIsError } from '../../utils/error';
1616
import { normalizeCacheOptions } from '../../utils/normalize-cache';
17-
import { isSubDirectory, toPosixPath } from '../../utils/path';
17+
import { isSubDirectory } from '../../utils/path';
1818
import {
19-
type PostcssConfiguration,
2019
generateSearchDirectories,
2120
getTailwindConfig,
2221
loadPostcssConfiguration,
2322
} from '../../utils/postcss-configuration';
2423
import { getProjectRootPaths } from '../../utils/project-metadata';
25-
import { type NormalizedEntryPoint, getEntryPointBundleName } from './pipeline/entry-points';
24+
import { normalizeEntryPoints } from './pipeline/entry-points';
2625
import type { Schema as LibraryBuilderOptions } from './schema';
27-
28-
export type { NormalizedEntryPoint } from './pipeline/entry-points';
29-
30-
export interface PackageJsonData {
31-
name: string;
32-
version?: string;
33-
type?: string;
34-
main?: string;
35-
module?: string;
36-
typings?: string;
37-
types?: string;
38-
sideEffects?: boolean | string[];
39-
exports?: string | Record<string, unknown>;
40-
scripts?: Record<string, string>;
41-
workspaces?: unknown;
42-
dependencies?: Record<string, string>;
43-
optionalDependencies?: Record<string, string>;
44-
peerDependencies?: Record<string, string>;
45-
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
46-
[key: string]: unknown;
47-
}
48-
49-
export interface NormalizedLibraryOptions {
50-
workspaceRoot: string;
51-
projectRoot: string;
52-
packageName: string;
53-
packageJson: PackageJsonData;
54-
outputPath: string;
55-
deleteOutputPath: boolean;
56-
packageJsonPath: string;
57-
tsConfigPath: string;
58-
entryPoints: Map<string, NormalizedEntryPoint>;
59-
inlineStyleLanguage: 'css' | 'less' | 'sass' | 'scss';
60-
styleIncludePaths: string[];
61-
sass?: StylesheetPluginsass;
62-
assets: ReturnType<typeof normalizeAssetPatterns>;
63-
compilationMode: 'partial' | 'full';
64-
declarationMap: boolean;
65-
allowedNonPeerDependencies: RegExp[];
66-
keepLifecycleScripts: boolean;
67-
watch: boolean;
68-
poll?: number;
69-
preserveSymlinks: boolean;
70-
progress: boolean;
71-
clearScreen?: boolean;
72-
cacheOptions: ReturnType<typeof normalizeCacheOptions>;
73-
postcssConfiguration?: { config: PostcssConfiguration; configPath: string };
74-
tailwindConfiguration?: { file: string; package: string };
75-
colors: boolean;
76-
}
26+
import type { NormalizedLibraryOptions, PackageJsonData } from './types';
7727

7828
export async function normalizeLibraryOptions(
7929
context: BuilderContext,
@@ -217,147 +167,3 @@ export async function normalizeLibraryOptions(
217167
tailwindConfiguration,
218168
};
219169
}
220-
221-
/**
222-
* Normalizes a single entry point specification.
223-
*
224-
* @param key The entry point key from package.json exports (e.g. '.' or './testing').
225-
* @param posixKey Normalized POSIX key without trailing slashes.
226-
* @param isPrimary Whether this is the primary entry point.
227-
* @param targetPath The relative file path string from exports.
228-
* @param projectRoot The library project root directory.
229-
* @param packageName The root package name (e.g. `@my/lib`).
230-
* @returns The normalized entry point descriptor.
231-
*/
232-
function normalizeEntryPoint(
233-
key: string,
234-
posixKey: string,
235-
isPrimary: boolean,
236-
targetPath: string,
237-
projectRoot: string,
238-
packageName: string,
239-
): NormalizedEntryPoint {
240-
const name = isPrimary
241-
? '.'
242-
: posixKey[0] === '.' && posixKey[1] === '/'
243-
? posixKey.slice(2)
244-
: posixKey;
245-
246-
if (name !== '.' && (path.posix.isAbsolute(name) || name.includes('..'))) {
247-
throw new Error(
248-
`Invalid entry point key '${key}'. Entry point keys must be relative subpaths without '..' (e.g. './testing').`,
249-
);
250-
}
251-
252-
const subpath = isPrimary ? '.' : `./${name}`;
253-
const displayName = isPrimary ? packageName : `${packageName}/${name}`;
254-
const bundleName = getEntryPointBundleName(packageName, name);
255-
256-
const entryFilePath = path.resolve(projectRoot, targetPath);
257-
258-
if (!/(?<!\.d)\.(?:ts|mts)$/.test(entryFilePath)) {
259-
throw new Error(
260-
`Entry point '${key}' file path must be a TypeScript file ('.ts' or '.mts'): '${entryFilePath}'.`,
261-
);
262-
}
263-
264-
return {
265-
subpath,
266-
name,
267-
displayName,
268-
bundleName,
269-
entryFilePath,
270-
isPrimary,
271-
};
272-
}
273-
274-
/**
275-
* Normalizes all entry points from the library's `package.json` `exports` field.
276-
*
277-
* @param rawExports The `exports` field from `package.json`.
278-
* @param projectRoot The library project root directory.
279-
* @param packageJsonPath Path to `package.json` for error reporting.
280-
* @param packageName The root package name (e.g. `@my/lib`).
281-
* @returns A Map of normalized entry points keyed by name.
282-
*/
283-
function normalizeEntryPoints(
284-
rawExports: PackageJsonData['exports'],
285-
projectRoot: string,
286-
packageJsonPath: string,
287-
packageName: string,
288-
): Map<string, NormalizedEntryPoint> {
289-
if (!rawExports || (typeof rawExports !== 'string' && typeof rawExports !== 'object')) {
290-
throw new Error(
291-
`The 'package.json' at '${packageJsonPath}' must contain an 'exports' field defining the primary entry point ('.').`,
292-
);
293-
}
294-
295-
const exportsRecord = typeof rawExports === 'string' ? { '.': rawExports } : rawExports;
296-
297-
const entryPoints = new Map<string, NormalizedEntryPoint>();
298-
let hasPrimary = false;
299-
300-
for (const [key, value] of Object.entries(exportsRecord)) {
301-
let target: string | undefined;
302-
303-
if (typeof value === 'string') {
304-
target = value;
305-
} else if (
306-
typeof value === 'object' &&
307-
value !== null &&
308-
!Array.isArray(value) &&
309-
typeof (value as Record<string, unknown>)['default'] === 'string'
310-
) {
311-
target = (value as Record<string, unknown>)['default'] as string;
312-
}
313-
314-
const posixKey = toPosixPath(key).replace(/\/+$/, '');
315-
const isPrimary = posixKey === '.' || posixKey === '';
316-
317-
if (!target) {
318-
if (isPrimary) {
319-
throw new Error(
320-
`The primary entry point '.' in '${packageJsonPath}' must specify a string path ` +
321-
`or a 'default' condition pointing to a TypeScript file.`,
322-
);
323-
}
324-
325-
// Non-JS/TS conditional export (e.g., sass/style-only subpath); preserve in package.json without compiling.
326-
continue;
327-
}
328-
329-
if (!isPrimary && !/\.m?ts$/.test(target)) {
330-
// Static asset, stylesheet, or package.json export; preserve in package.json without compiling.
331-
continue;
332-
}
333-
334-
const entryPoint = normalizeEntryPoint(
335-
key,
336-
posixKey,
337-
isPrimary,
338-
target,
339-
projectRoot,
340-
packageName,
341-
);
342-
343-
if (entryPoints.has(entryPoint.name)) {
344-
throw new Error(
345-
`Duplicate entry point detected: '${key}' resolves to the same name ('${entryPoint.name}') as an existing entry point.`,
346-
);
347-
}
348-
349-
entryPoints.set(entryPoint.name, entryPoint);
350-
351-
if (entryPoint.isPrimary) {
352-
hasPrimary = true;
353-
}
354-
}
355-
356-
if (!hasPrimary) {
357-
throw new Error(
358-
`The 'exports' field in '${packageJsonPath}' must contain a primary entry point with key '.'.`,
359-
);
360-
}
361-
362-
return entryPoints;
363-
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import path from 'node:path';
1111
import picomatch from 'picomatch';
1212
import { toPosixPath } from '../../../utils/path';
1313
import { DEFAULT_ASSET_IGNORE, resolveAssets } from '../../../utils/resolve-assets';
14-
import type { NormalizedLibraryOptions } from '../options';
14+
import type { NormalizedLibraryOptions } from '../types';
1515
import { type DiskOutputFile, createDiskOutputFile } from './utils';
1616

1717
/**

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

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,14 @@ import { constants, copyFile, mkdir, writeFile } from 'node:fs/promises';
1111
import path from 'node:path';
1212
import { emitFilesToDisk } from '../../../tools/esbuild/utils';
1313
import { toPosixPath } from '../../../utils/path';
14-
import type { NormalizedLibraryOptions } from '../options';
14+
import type { NormalizedLibraryOptions, SingleBuildState } from '../types';
1515
import { collectAssetsToEmit } from './assets';
16-
import { type BundleEntryPointInput, type BundleResult, bundleEntryPoints } from './bundler';
17-
import { type SingleProgramCache, compileLibrary } from './compilation';
16+
import { type BundleEntryPointInput, bundleEntryPoints } from './bundler';
17+
import { compileLibrary } from './compilation';
1818
import { generatePackageManifests } from './package-manifests';
1919
import type { createComponentStylesheetBundlerForLibrary } from './stylesheet-bundler';
2020
import type { OutputFile } from './utils';
2121

22-
/**
23-
* State preserved across incremental builds in watch mode.
24-
*/
25-
export interface SingleBuildState {
26-
singleProgramCache?: SingleProgramCache;
27-
previousBundleResults: Map<string, BundleResult>;
28-
pendingChangedEsmFiles: Set<string>;
29-
pendingChangedDtsFiles: Set<string>;
30-
hasCompilationError?: boolean;
31-
hasEmittedManifests?: boolean;
32-
hasEmittedAssets?: boolean;
33-
directoryExists: Set<string>;
34-
}
35-
3622
/**
3723
* Creates a fresh {@link SingleBuildState} instance.
3824
*/
@@ -86,7 +72,8 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
8672
const shouldCompileEntryPoints =
8773
!modifiedFiles ||
8874
!buildState.singleProgramCache ||
89-
Boolean(buildState.hasCompilationError) ||
75+
buildState.hasCompilationError ||
76+
buildState.hasEntryPointsChanges ||
9077
pendingChangedEsmFiles.size > 0 ||
9178
pendingChangedDtsFiles.size > 0 ||
9279
hasModifiedWatchedFile(modifiedFiles, allWatchedFiles, posixPackageJsonPath);
@@ -100,6 +87,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<vo
10087

10188
if (shouldCompileEntryPoints) {
10289
buildState.hasCompilationError = true;
90+
buildState.hasEntryPointsChanges = false;
10391

10492
const {
10593
esmFiles,

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,14 @@ import {
1717
} from 'rolldown';
1818
import { dts } from 'rolldown-plugin-dts';
1919
import { toPosixPath } from '../../../utils/path';
20-
import type { NormalizedEntryPoint, NormalizedLibraryOptions } from '../options';
20+
import type { BundleResult, NormalizedEntryPoint, NormalizedLibraryOptions } from '../types';
2121
import {
2222
FESM_OUTPUT_DIR,
2323
type MemoryOutputFile,
2424
TYPES_OUTPUT_DIR,
2525
createMemoryOutputFile,
2626
} from './utils';
2727

28-
/**
29-
* Cached module ID sets for a bundled entry point.
30-
*/
31-
export interface BundleResult {
32-
/** Exact set of virtual ESM module IDs bundled into this entry point. */
33-
esmModuleIds: ReadonlySet<string>;
34-
35-
/** Exact set of virtual DTS module IDs bundled into this entry point. */
36-
dtsModuleIds: ReadonlySet<string>;
37-
}
38-
3928
/**
4029
* Output of the entry point bundling process.
4130
*/

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

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,11 @@ import {
1515
import type { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/component-stylesheets';
1616
import { useTypeChecking } from '../../../utils/environment-options';
1717
import { toPosixPath } from '../../../utils/path';
18-
import type { NormalizedEntryPoint, NormalizedLibraryOptions } from '../options';
18+
import type { NormalizedEntryPoint, NormalizedLibraryOptions, SingleProgramCache } from '../types';
1919
import { isDeclarationFile, isDeclarationSourceMapFile } from './utils';
2020

2121
const EMITTED_EXTENSIONS = ['.js', '.mjs', '.cjs', '.d.ts', '.d.mts', '.d.cts'];
2222

23-
/**
24-
* Cached state for the single unified library compilation.
25-
*/
26-
export interface SingleProgramCache {
27-
/** The active Angular compilation instance. */
28-
readonly compilationInstance: AngularCompilation;
29-
30-
/** In-memory map of emitted JavaScript files keyed by relative output path. */
31-
readonly esmFiles: Map<string, string>;
32-
33-
/** In-memory map of emitted TypeScript declaration files keyed by relative output path. */
34-
readonly dtsFiles: Map<string, string>;
35-
36-
/** Set of file paths that failed during stylesheet bundling or compilation. */
37-
readonly failedFiles?: ReadonlySet<string>;
38-
}
39-
4023
/**
4124
* Output of the unified library compilation step.
4225
*/

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL