Skip to content

Commit b085b59

Browse files
committed
fixup! perf(@angular/build): replace watchpack with @parcel/watcher and chokidar
1 parent 6feb774 commit b085b59

6 files changed

Lines changed: 54 additions & 15 deletions

File tree

packages/angular/build/src/builders/application/execute-build.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ export async function executeBuild(
104104
bundlingResult = BundlerContext.mergeResults([bundlingResult, ...typescriptResults]);
105105
} else {
106106
const target = transformSupportedBrowsersToTargets(browsers);
107-
codeBundleCache = new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined);
107+
codeBundleCache = new SourceFileCache(
108+
cacheOptions.enabled ? cacheOptions.path : undefined,
109+
options.workspaceRoot,
110+
);
108111
componentStyleBundler = createComponentStyleBundler(options, target);
109112
if (options.templateUpdates) {
110113
templateUpdates = new Map<string, string>();

packages/angular/build/src/builders/application/options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export async function normalizeOptions(
160160
options.preserveSymlinks ?? process.execArgv.includes('--preserve-symlinks');
161161

162162
// Setup base paths based on workspace root and project information
163-
const workspaceRoot = canonicalizePath(context.workspaceRoot, preserveSymlinks);
163+
const workspaceRoot = canonicalizePath(context.workspaceRoot);
164164
const projectMetadata = await context.getProjectMetadata(projectName);
165165
const { projectRoot, projectSourceRoot } = getProjectRootPaths(workspaceRoot, projectMetadata);
166166

packages/angular/build/src/builders/unit-test/options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export async function normalizeOptions(
5858
: process.execArgv.includes('--preserve-symlinks');
5959

6060
// Setup base paths based on workspace root and project information
61-
const workspaceRoot = canonicalizePath(context.workspaceRoot, preserveSymlinks);
61+
const workspaceRoot = canonicalizePath(context.workspaceRoot);
6262

6363
const projectMetadata = await context.getProjectMetadata(projectName);
6464
const { projectRoot, projectSourceRoot } = getProjectRootPaths(workspaceRoot, projectMetadata);

packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import { realpathSync } from 'node:fs';
910
import { platform } from 'node:os';
1011
import * as path from 'node:path';
1112
import type ts from 'typescript';
@@ -18,11 +19,25 @@ export class SourceFileCache extends Map<string, ts.SourceFile> {
1819
readonly modifiedFiles = new Set<string>();
1920
readonly typeScriptFileCache = new Map<string, string | Uint8Array>();
2021
readonly loadResultCache = new MemoryLoadResultCache();
22+
private readonly rootDir?: string;
23+
private readonly realRootDir?: string;
2124

2225
referencedFiles?: readonly string[];
2326

24-
constructor(readonly persistentCachePath?: string) {
27+
constructor(
28+
readonly persistentCachePath?: string,
29+
workspaceRoot?: string,
30+
) {
2531
super();
32+
const root = workspaceRoot ?? process.cwd();
33+
try {
34+
const normRoot = path.normalize(root);
35+
const real = path.normalize(realpathSync(normRoot));
36+
if (real !== normRoot) {
37+
this.rootDir = normRoot;
38+
this.realRootDir = real;
39+
}
40+
} catch {}
2641
}
2742

2843
invalidate(files: Iterable<string>): boolean {
@@ -45,6 +60,16 @@ export class SourceFileCache extends Map<string, ts.SourceFile> {
4560

4661
invalid = this.delete(file) || invalid;
4762
this.modifiedFiles.add(file);
63+
64+
// Invalidate the realpath variant if TypeScript stored the SourceFile under its realpath
65+
// (e.g. dynamically imported modules where TS module resolution resolved symlinked root directories).
66+
if (this.rootDir && this.realRootDir) {
67+
if (file.startsWith(this.rootDir)) {
68+
const realFile = this.realRootDir + file.slice(this.rootDir.length);
69+
invalid = this.delete(realFile) || invalid;
70+
this.modifiedFiles.add(realFile);
71+
}
72+
}
4873
}
4974

5075
return invalid;

packages/angular/build/src/tools/esbuild/watcher.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,10 @@ async function createParcelWatcher(
236236
const queue = new WatcherQueue();
237237

238238
const isCaseSensitive = isFileSystemCaseSensitive(options?.cwd);
239-
const rootDirPosix = toPosixPathNormalized(options?.cwd ?? process.cwd());
239+
const rootDir = options?.cwd ? path.resolve(options.cwd) : process.cwd();
240+
const rootDirPosix = toPosixPathNormalized(rootDir);
241+
const realRootDir = fs.existsSync(rootDir) ? fs.realpathSync(rootDir) : rootDir;
242+
const realRootDirPosix = toPosixPathNormalized(realRootDir);
240243
const rootDirLookupKey = toLookupKey(rootDirPosix, isCaseSensitive);
241244
const extraSubscriptions = new Map<string, ParcelWatcher.AsyncSubscription>();
242245
const pendingSubscriptions = new Map<string, Promise<ParcelWatcher.AsyncSubscription>>();
@@ -245,7 +248,11 @@ async function createParcelWatcher(
245248
const handleEvents = (events: ParcelWatcher.Event[]) => {
246249
const changes: { type: 'added' | 'modified' | 'removed'; file: string }[] = [];
247250
for (const event of events) {
248-
const posixPath = toPosixPathNormalized(event.path);
251+
let rawPath = toPosixPath(event.path);
252+
if (realRootDirPosix !== rootDirPosix && rawPath.startsWith(realRootDirPosix)) {
253+
rawPath = rootDirPosix + rawPath.slice(realRootDirPosix.length);
254+
}
255+
const posixPath = toPosixPathNormalized(rawPath);
249256
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
250257
if (!isPathWatched(lookupKey, watchedFiles)) {
251258
continue;
@@ -419,7 +426,10 @@ async function createChokidarWatcher(
419426
const watchedFiles = new Set<string>();
420427
const queue = new WatcherQueue();
421428

422-
const rootDir = options?.cwd ?? process.cwd();
429+
const rootDir = options?.cwd ? path.resolve(options.cwd) : process.cwd();
430+
const rootDirPosix = toPosixPathNormalized(rootDir);
431+
const realRootDir = fs.existsSync(rootDir) ? fs.realpathSync(rootDir) : rootDir;
432+
const realRootDirPosix = toPosixPathNormalized(realRootDir);
423433
const isCaseSensitive = isFileSystemCaseSensitive(rootDir);
424434

425435
const watcher = chokidar.watch([], {
@@ -431,7 +441,11 @@ async function createChokidarWatcher(
431441
});
432442

433443
const handleEvent = (type: 'added' | 'modified' | 'removed', rawPath: string) => {
434-
const posixPath = toPosixPathNormalized(rawPath);
444+
let normalizedPath = toPosixPath(rawPath);
445+
if (realRootDirPosix !== rootDirPosix && normalizedPath.startsWith(realRootDirPosix)) {
446+
normalizedPath = rootDirPosix + normalizedPath.slice(realRootDirPosix.length);
447+
}
448+
const posixPath = toPosixPathNormalized(normalizedPath);
435449
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
436450
if (!isPathWatched(lookupKey, watchedFiles)) {
437451
return;

packages/angular/build/src/utils/path.ts

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

9-
import { realpathSync } from 'node:fs';
109
import { isAbsolute, posix, relative, resolve } from 'node:path';
1110
import { platform } from 'node:process';
1211

@@ -53,17 +52,15 @@ export function isSubDirectory(parent: string, child: string): boolean {
5352
}
5453

5554
/**
56-
* Canonicalizes a file path by normalising Windows drive-letter casing to uppercase
57-
* and optionally resolving symbolic links.
55+
* Canonicalizes a file path by normalising Windows drive-letter casing to uppercase.
5856
*
5957
* @param pathString - The file path to canonicalize.
60-
* @param preserveSymlinks - If true, symbolic links will not be resolved.
6158
* @returns The canonicalized file path.
6259
*/
63-
export function canonicalizePath(pathString: string, preserveSymlinks = false): string {
64-
const resolved = preserveSymlinks ? pathString : realpathSync(pathString);
60+
export function canonicalizePath(pathString: string): string {
61+
let resolved = toPosixPath(pathString);
6562
if (platform === 'win32' && /^[a-z]:/.test(resolved)) {
66-
return resolved[0].toUpperCase() + resolved.slice(1);
63+
resolved = resolved[0].toUpperCase() + resolved.slice(1);
6764
}
6865

6966
return resolved;

0 commit comments

Comments
 (0)