Skip to content

Commit 4081e23

Browse files
committed
fix(ng-packagr): revert parallel entry point scheduling
Reverts commits cd0b052, eff8bb0, and d1a9a3d. Entry points share a single mutable TypeScript ModuleResolutionCache and sourcesFileCache across the package build. Running entry point compilations concurrently interleaves NgtscProgram creation and template type-checking against the shared caches while sibling declaration bundles are still being generated, which can cause cross-entry-point imports to fall back to raw TypeScript source files and trigger TS6059 errors. Closes #3451
1 parent d1a9a3d commit 4081e23

11 files changed

Lines changed: 125 additions & 466 deletions

‎src/lib/graph/build-graph.ts‎

Lines changed: 10 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,10 @@ export interface Traversable<T> {
2525
* Technically, it's implemented as a map-like collection with references between map entries.
2626
*/
2727
export class BuildGraph implements Traversable<Node> {
28-
readonly isParallel: boolean = false;
29-
readonly store: Map<string, Node>;
30-
private _watcher?: FSWatcher;
28+
private store = new Map<string, Node>();
29+
watcher?: FSWatcher;
3130

32-
constructor(store?: Map<string, Node>) {
33-
this.store = store ?? new Map<string, Node>();
34-
}
35-
36-
get watcher(): FSWatcher | undefined {
37-
return this._watcher;
38-
}
39-
40-
set watcher(watcher: FSWatcher | undefined) {
41-
this._watcher = watcher;
42-
}
43-
44-
put(value: Node | Node[]) {
31+
public put(value: Node | Node[]) {
4532
if (value instanceof Array) {
4633
for (const node of value) {
4734
this.insert(node);
@@ -70,23 +57,23 @@ export class BuildGraph implements Traversable<Node> {
7057
this.store.set(node.url, node);
7158
}
7259

73-
get(url: string): Node {
60+
public get(url: string): Node {
7461
return this.store.get(url);
7562
}
7663

77-
has(url: string): boolean {
64+
public has(url: string): boolean {
7865
return this.store.has(url);
7966
}
8067

81-
entries(): Node[] {
68+
public entries(): Node[] {
8269
return Array.from(this.store.values());
8370
}
8471

85-
values(): IterableIterator<Node> {
72+
public values(): IterableIterator<Node> {
8673
return this.store.values();
8774
}
8875

89-
some<T extends Node = Node>(by: ComplexPredicate<Node, T>): boolean {
76+
public some<T extends Node = Node>(by: ComplexPredicate<Node, T>): boolean {
9077
for (const node of this.store.values()) {
9178
if (by(node)) {
9279
return true;
@@ -96,7 +83,7 @@ export class BuildGraph implements Traversable<Node> {
9683
return false;
9784
}
9885

99-
filter<T extends Node = Node>(by: ComplexPredicate<Node, T>): T[] {
86+
public filter<T extends Node = Node>(by: ComplexPredicate<Node, T>): T[] {
10087
const result: T[] = [];
10188

10289
for (const node of this.store.values()) {
@@ -108,7 +95,7 @@ export class BuildGraph implements Traversable<Node> {
10895
return result;
10996
}
11097

111-
find<T extends Node = Node>(by: ComplexPredicate<Node, T>): T | undefined {
98+
public find<T extends Node = Node>(by: ComplexPredicate<Node, T>): T | undefined {
11299
for (const node of this.store.values()) {
113100
if (by(node)) {
114101
return node as T;
@@ -122,25 +109,3 @@ export class BuildGraph implements Traversable<Node> {
122109
return this.store.size;
123110
}
124111
}
125-
126-
export class ScopedBuildGraph<T extends Node = Node> extends BuildGraph {
127-
constructor(
128-
readonly parentGraph: BuildGraph,
129-
readonly activeEntryPoint: T,
130-
override readonly isParallel = false,
131-
) {
132-
super(parentGraph.store);
133-
}
134-
135-
override get watcher(): FSWatcher | undefined {
136-
return this.parentGraph.watcher;
137-
}
138-
139-
override set watcher(watcher: FSWatcher | undefined) {
140-
this.parentGraph.watcher = watcher;
141-
}
142-
}
143-
144-
export function isScopedBuildGraph<T extends Node = Node>(graph: BuildGraph): graph is ScopedBuildGraph<T> {
145-
return graph instanceof ScopedBuildGraph;
146-
}

‎src/lib/ng-package/entry-point/compile-ngc.transform.ts‎

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,48 @@
11
import { dirname } from 'node:path';
2+
import ora from 'ora';
23
import ts from 'typescript';
4+
import { isInProgress } from '../../graph/select';
35
import { Transform, transformFromPromise } from '../../graph/transform';
46
import { compileSourceFiles } from '../../ngc/compile-source-files';
57
import { StylesheetProcessor as StylesheetProcessorClass } from '../../styles/stylesheet-processor';
68
import { setDependenciesTsConfigPaths } from '../../ts/tsconfig';
7-
import { openSpinner } from '../../utils/spinner';
8-
import { findPackageNode, getActiveEntryPoint, isEntryPoint } from '../nodes';
9+
import { EntryPointNode, PackageNode, isEntryPoint, isPackage } from '../nodes';
910
import { NgPackagrOptions } from '../options.di';
1011

1112
export const compileNgcTransformFactory = (
1213
StylesheetProcessor: typeof StylesheetProcessorClass,
1314
options: NgPackagrOptions,
1415
): Transform => {
1516
return transformFromPromise(async graph => {
16-
const spinner = openSpinner(undefined, graph);
17+
const spinner = ora({
18+
hideCursor: false,
19+
discardStdin: false,
20+
});
21+
22+
const entryPoints: EntryPointNode[] = [];
23+
let entryPoint: EntryPointNode;
24+
let ngPackageNode: PackageNode;
25+
26+
for (const node of graph.values()) {
27+
if (isEntryPoint(node)) {
28+
entryPoints.push(node);
29+
30+
if (isInProgress(node)) {
31+
entryPoint = node;
32+
}
33+
} else if (isPackage(node)) {
34+
ngPackageNode = node;
35+
}
36+
}
1737

18-
const entryPoints = graph.filter(isEntryPoint);
19-
const ngPackageNode = findPackageNode(graph);
20-
const entryPoint = getActiveEntryPoint(graph);
2138
const projectBasePath = ngPackageNode.data.primary.basePath;
2239

2340
try {
2441
// Add paths mappings for dependencies
2542
const tsConfig = setDependenciesTsConfigPaths(entryPoint.data.tsConfig, entryPoints);
2643

2744
// Compile TypeScript sources
28-
const { esm2022, declarations } = entryPoint.data.destinationFiles;
45+
const { esm2022: esm2022, declarations } = entryPoint.data.destinationFiles;
2946
const { basePath, cssUrl, styleIncludePaths, sass } = entryPoint.data.entryPoint;
3047
const { moduleResolutionCache } = entryPoint.cache;
3148

@@ -59,6 +76,10 @@ export const compileNgcTransformFactory = (
5976
} catch (error) {
6077
spinner.fail();
6178
throw error;
79+
} finally {
80+
if (!options.watch) {
81+
entryPoint.cache.stylesheetProcessor?.destroy();
82+
}
6283
}
6384

6485
spinner.succeed();

‎src/lib/ng-package/entry-point/entry-point.transform.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { pipe, tap } from 'rxjs';
22
import { STATE_DONE } from '../../graph/node';
33
import { Transform } from '../../graph/transform';
44
import log from '../../utils/log';
5-
import { getActiveEntryPoint } from '../nodes';
5+
import { findEntryPointInProgress } from '../nodes';
66

77
/**
88
* A re-write of the `transformSources()` script that transforms an entry point from sources to distributable format.
@@ -37,7 +37,7 @@ export const entryPointTransformFactory = (
3737
pipe(
3838
tap(graph => {
3939
// Peek the first entry point from the graph
40-
const entryPoint = getActiveEntryPoint(graph);
40+
const entryPoint = findEntryPointInProgress(graph);
4141
log.msg('\n------------------------------------------------------------------------------');
4242
log.msg(`Building entry point '${entryPoint.data.entryPoint.moduleId}'`);
4343
log.msg('------------------------------------------------------------------------------');
@@ -48,7 +48,7 @@ export const entryPointTransformFactory = (
4848
writeBundles,
4949
writePackage,
5050
tap(graph => {
51-
const entryPoint = getActiveEntryPoint(graph);
51+
const entryPoint = findEntryPointInProgress(graph);
5252
entryPoint.state = STATE_DONE;
5353
}),
5454
);

‎src/lib/ng-package/entry-point/write-bundles.transform.ts‎

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { join } from 'node:path';
2+
import ora from 'ora';
23
import type { OutputAsset, OutputChunk } from 'rolldown';
34
import { invalidateEntryPointsAndCacheOnFileChange } from '../../file-system/file-watcher';
45
import { rolldownBundleFile } from '../../flatten/rolldown';
56
import { transformFromPromise } from '../../graph/transform';
67
import { generateKey, readCacheEntry, saveCacheEntry } from '../../utils/cache';
78
import { exists, mkdir, writeFile } from '../../utils/fs';
89
import { ensureUnixPath } from '../../utils/path';
9-
import { openSpinner } from '../../utils/spinner';
10-
import { getActiveEntryPoint } from '../nodes';
10+
import { findEntryPointInProgress } from '../nodes';
1111
import { NgPackagrOptions } from '../options.di';
1212

1313
type CachedBundleFile =
@@ -30,11 +30,14 @@ interface BundlesCache {
3030

3131
export const writeBundlesTransform = (options: NgPackagrOptions) =>
3232
transformFromPromise(async graph => {
33-
const entryPoint = getActiveEntryPoint(graph);
33+
const entryPoint = findEntryPointInProgress(graph);
3434
const { destinationFiles, entryPoint: ngEntryPoint, tsConfig } = entryPoint.data;
3535
const cache = entryPoint.cache;
3636
const { fesm2022Dir, esm2022, declarations, declarationsDir } = destinationFiles;
37-
const spinner = openSpinner(undefined, graph);
37+
const spinner = ora({
38+
hideCursor: false,
39+
discardStdin: false,
40+
});
3841

3942
const cacheKey = await generateKey(
4043
ngEntryPoint.moduleId,
@@ -43,12 +46,8 @@ export const writeBundlesTransform = (options: NgPackagrOptions) =>
4346
tsConfig.options.compilationMode,
4447
(tsConfig.options.declarationMap ?? false).toString(),
4548
);
46-
const hash = await generateKey(
47-
[...cache.outputCache.entries()]
48-
.sort(([a], [b]) => a.localeCompare(b))
49-
.map(([p, { version }]) => `${p}:${version}`)
50-
.join(':'),
51-
);
49+
50+
const hash = await generateKey([...cache.outputCache.values()].map(({ version }) => version).join(':'));
5251
const cacheDirectory = options.cacheEnabled && options.cacheDirectory;
5352
if (cacheDirectory) {
5453
const cacheResult: BundlesCache = await readCacheEntry(options.cacheDirectory, cacheKey);

‎src/lib/ng-package/entry-point/write-package.transform.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { basename, dirname, join, relative } from 'node:path';
2-
import type { Ora } from 'ora';
2+
import ora, { type Ora } from 'ora';
33
import { glob } from 'tinyglobby';
44
import { AssetPattern } from '../../../ng-package.schema';
55
import { BuildGraph } from '../../graph/build-graph';
66
import { Node } from '../../graph/node';
7+
import { isInProgress } from '../../graph/select';
78
import { transformFromPromise } from '../../graph/transform';
89
import { colors } from '../../utils/color';
910
import { copyFile, mkdir, rmdir, stat, writeFile } from '../../utils/fs';
1011
import log from '../../utils/log';
1112
import { ConditionalExport, generatePackageExports, generateWatchVersion } from '../../utils/package-json';
1213
import { ensureUnixPath } from '../../utils/path';
13-
import { openSpinner } from '../../utils/spinner';
14-
import { EntryPointNode, PackageNode, fileUrl, findPackageNode, getActiveEntryPoint, isEntryPoint } from '../nodes';
14+
import { EntryPointNode, PackageNode, fileUrl, findPackageNode, isEntryPoint } from '../nodes';
1515
import { NgPackagrOptions } from '../options.di';
1616
import { NgPackage } from '../package';
1717
import { NgEntryPoint } from './entry-point';
@@ -20,9 +20,9 @@ type CompilationMode = 'partial' | 'full' | undefined;
2020

2121
export const writePackageTransform = (options: NgPackagrOptions) =>
2222
transformFromPromise(async graph => {
23-
const spinner = openSpinner(undefined, graph);
23+
const spinner = ora({ hideCursor: false, discardStdin: false });
2424
const entryPoints = graph.filter(isEntryPoint);
25-
const entryPoint = getActiveEntryPoint(graph);
25+
const entryPoint = entryPoints.find(isInProgress);
2626
const ngEntryPoint: NgEntryPoint = entryPoint.data.entryPoint;
2727
const ngPackageNode = findPackageNode(graph);
2828
const ngPackage = ngPackageNode.data;

‎src/lib/ng-package/nodes.ts‎

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { NgtscProgram, ParsedConfiguration } from '@angular/compiler-cli';
22
import ts from 'typescript';
33
import { FileCache } from '../file-system/file-cache';
4-
import { BuildGraph, ComplexPredicate, isScopedBuildGraph } from '../graph/build-graph';
4+
import { BuildGraph, ComplexPredicate } from '../graph/build-graph';
55
import { Node } from '../graph/node';
66
import { by, isInProgress, isPending } from '../graph/select';
77
import { AngularDiagnosticsCache } from '../ngc/angular-diagnostics-cache';
@@ -38,29 +38,13 @@ export function isEntryPointPending(): ComplexPredicate<EntryPointNode> {
3838
return by(n => isEntryPoint(n) && isPending(n));
3939
}
4040

41-
export function getActiveEntryPoint(graph: BuildGraph): EntryPointNode {
42-
if (isScopedBuildGraph(graph) && isEntryPoint(graph.activeEntryPoint)) {
43-
return graph.activeEntryPoint;
44-
}
45-
46-
return findEntryPointInProgress(graph);
47-
}
48-
4941
export function findEntryPointInProgress(graph: BuildGraph): EntryPointNode {
50-
if (isScopedBuildGraph(graph) && isEntryPoint(graph.activeEntryPoint)) {
51-
return graph.activeEntryPoint;
52-
}
53-
54-
const inProgress = graph.filter(isEntryPointInProgress());
55-
if (inProgress.length === 1) {
56-
return inProgress[0];
57-
}
58-
if (inProgress.length > 1) {
59-
const moduleIds = inProgress.map(e => e.data.entryPoint.moduleId).join(', ');
60-
throw new Error(`Multiple entry points are in progress (${moduleIds}). A ScopedBuildGraph is required.`);
42+
const entryPoint = graph.find(isEntryPointInProgress());
43+
if (!entryPoint) {
44+
throw new Error('Could not find entry point in progress');
6145
}
6246

63-
throw new Error('Could not find entry point in progress');
47+
return entryPoint;
6448
}
6549

6650
export function findPackageNode(graph: BuildGraph): PackageNode {

0 commit comments

Comments
 (0)