Skip to content

Commit 03ce964

Browse files
committed
refactor(@angular-devkit/build-angular): use native path normalization for angular compiler caching
The path comparisons for the TypeScript emit file lookups now use the native path normalization functions. This removes the special handling previously used and better ensures that cache checks are valid. The separate babel cache Map has also been combined with the already present memory load cache within the Angular plugin. This removes the need for extra handling of another Map and allows it to use the common logic of the load cache. To support this usage, the load cache will also now automatically include the request path in the file dependencies if the request is from the `file` namespace. This removes the need to manually specify the request path file for each load result return value.
1 parent 3b93df4 commit 03ce964

File tree

3 files changed

+40
-34
lines changed

3 files changed

+40
-34
lines changed

packages/angular_devkit/build_angular/src/tools/esbuild/angular/compiler-plugin.ts

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@ import type {
1818
import assert from 'node:assert';
1919
import { realpath } from 'node:fs/promises';
2020
import * as path from 'node:path';
21-
import { pathToFileURL } from 'node:url';
2221
import { maxWorkers } from '../../../utils/environment-options';
2322
import { JavaScriptTransformer } from '../javascript-transformer';
24-
import { LoadResultCache } from '../load-result-cache';
23+
import { LoadResultCache, createCachedLoad } from '../load-result-cache';
2524
import { logCumulativeDurations, profileAsync, resetCumulativeDurations } from '../profiling';
2625
import { BundleStylesheetOptions } from '../stylesheets/bundle-options';
2726
import { AngularHostOptions } from './angular-host';
@@ -280,7 +279,7 @@ export function createCompilerPlugin(
280279
try {
281280
await profileAsync('NG_EMIT_TS', async () => {
282281
for (const { filename, contents } of await compilation.emitAffectedFiles()) {
283-
typeScriptFileCache.set(pathToFileURL(filename).href, contents);
282+
typeScriptFileCache.set(path.normalize(filename), contents);
284283
}
285284
});
286285
} catch (error) {
@@ -323,7 +322,9 @@ export function createCompilerPlugin(
323322
});
324323

325324
build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => {
326-
const request = pluginOptions.fileReplacements?.[args.path] ?? args.path;
325+
const request = path.normalize(
326+
pluginOptions.fileReplacements?.[path.normalize(args.path)] ?? args.path,
327+
);
327328

328329
// Skip TS load attempt if JS TypeScript compilation not enabled and file is JS
329330
if (shouldTsIgnoreJs && /\.[cm]?js$/.test(request)) {
@@ -334,7 +335,7 @@ export function createCompilerPlugin(
334335
// the options cannot change and do not need to be represented in the key. If the
335336
// cache is later stored to disk, then the options that affect transform output
336337
// would need to be added to the key as well as a check for any change of content.
337-
let contents = typeScriptFileCache.get(pathToFileURL(request).href);
338+
let contents = typeScriptFileCache.get(request);
338339

339340
if (contents === undefined) {
340341
// If the Angular compilation had errors the file may not have been emitted.
@@ -364,7 +365,7 @@ export function createCompilerPlugin(
364365
);
365366

366367
// Store as the returned Uint8Array to allow caching the fully transformed code
367-
typeScriptFileCache.set(pathToFileURL(request).href, contents);
368+
typeScriptFileCache.set(request, contents);
368369
}
369370

370371
return {
@@ -373,27 +374,25 @@ export function createCompilerPlugin(
373374
};
374375
});
375376

376-
build.onLoad({ filter: /\.[cm]?js$/ }, (args) =>
377-
profileAsync(
378-
'NG_EMIT_JS*',
379-
async () => {
380-
// The filename is currently used as a cache key. Since the cache is memory only,
381-
// the options cannot change and do not need to be represented in the key. If the
382-
// cache is later stored to disk, then the options that affect transform output
383-
// would need to be added to the key as well as a check for any change of content.
384-
let contents = pluginOptions.sourceFileCache?.babelFileCache.get(args.path);
385-
if (contents === undefined) {
386-
contents = await javascriptTransformer.transformFile(args.path, pluginOptions.jit);
387-
pluginOptions.sourceFileCache?.babelFileCache.set(args.path, contents);
388-
}
377+
build.onLoad(
378+
{ filter: /\.[cm]?js$/ },
379+
createCachedLoad(pluginOptions.loadResultCache, async (args) => {
380+
return profileAsync(
381+
'NG_EMIT_JS*',
382+
async () => {
383+
const contents = await javascriptTransformer.transformFile(
384+
args.path,
385+
pluginOptions.jit,
386+
);
389387

390-
return {
391-
contents,
392-
loader: 'js',
393-
};
394-
},
395-
true,
396-
),
388+
return {
389+
contents,
390+
loader: 'js',
391+
};
392+
},
393+
true,
394+
);
395+
}),
397396
);
398397

399398
// Setup bundling of component templates and stylesheets when in JIT mode
@@ -531,18 +530,20 @@ function bundleWebWorker(
531530
}
532531

533532
function createMissingFileError(request: string, original: string, root: string): PartialMessage {
533+
const relativeRequest = path.relative(root, request);
534534
const error = {
535-
text: `File '${path.relative(root, request)}' is missing from the TypeScript compilation.`,
535+
text: `File '${relativeRequest}' is missing from the TypeScript compilation.`,
536536
notes: [
537537
{
538538
text: `Ensure the file is part of the TypeScript program via the 'files' or 'include' property.`,
539539
},
540540
],
541541
};
542542

543-
if (request !== original) {
543+
const relativeOriginal = path.relative(root, original);
544+
if (relativeRequest !== relativeOriginal) {
544545
error.notes.push({
545-
text: `File is requested from a file replacement of '${path.relative(root, original)}'.`,
546+
text: `File is requested from a file replacement of '${relativeOriginal}'.`,
546547
});
547548
}
548549

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import { platform } from 'node:os';
1010
import * as path from 'node:path';
11-
import { pathToFileURL } from 'node:url';
1211
import type ts from 'typescript';
1312
import { MemoryLoadResultCache } from '../load-result-cache';
1413

@@ -17,7 +16,6 @@ const WINDOWS_SEP_REGEXP = new RegExp(`\\${path.win32.sep}`, 'g');
1716

1817
export class SourceFileCache extends Map<string, ts.SourceFile> {
1918
readonly modifiedFiles = new Set<string>();
20-
readonly babelFileCache = new Map<string, Uint8Array>();
2119
readonly typeScriptFileCache = new Map<string, string | Uint8Array>();
2220
readonly loadResultCache = new MemoryLoadResultCache();
2321

@@ -32,8 +30,8 @@ export class SourceFileCache extends Map<string, ts.SourceFile> {
3230
this.modifiedFiles.clear();
3331
}
3432
for (let file of files) {
35-
this.babelFileCache.delete(file);
36-
this.typeScriptFileCache.delete(pathToFileURL(file).href);
33+
file = path.normalize(file);
34+
this.typeScriptFileCache.delete(file);
3735
this.loadResultCache.invalidate(file);
3836

3937
// Normalize separators to allow matching TypeScript Host paths

packages/angular_devkit/build_angular/src/tools/esbuild/load-result-cache.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ export function createCachedLoad(
3232

3333
// Do not cache null or undefined
3434
if (result) {
35+
// Ensure requested path is included if it was a resolved file
36+
if (args.namespace === 'file') {
37+
result.watchFiles ??= [];
38+
result.watchFiles.push(args.path);
39+
}
3540
await cache.put(loadCacheKey, result);
3641
}
3742
}
@@ -79,6 +84,8 @@ export class MemoryLoadResultCache implements LoadResultCache {
7984
}
8085

8186
get watchFiles(): string[] {
82-
return [...this.#loadResults.keys(), ...this.#fileDependencies.keys()];
87+
// this.#loadResults.keys() is not included here because the keys
88+
// are namespaced request paths and not disk-based file paths.
89+
return [...this.#fileDependencies.keys()];
8390
}
8491
}

0 commit comments

Comments
 (0)