diff --git a/packages/angular/build/src/tools/angular/angular-host.ts b/packages/angular/build/src/tools/angular/angular-host.ts index 22ac345d413e..9322b5683dc5 100644 --- a/packages/angular/build/src/tools/angular/angular-host.ts +++ b/packages/angular/build/src/tools/angular/angular-host.ts @@ -17,7 +17,6 @@ export type AngularCompilerHost = ng.CompilerHost; export interface AngularHostOptions { fileReplacements?: Record; - sourceFileCache?: Map; modifiedFiles?: Set; externalStylesheets?: Map; transformStylesheet( @@ -165,6 +164,7 @@ export function createAngularCompilerHost( compilerOptions: AngularCompilerOptions, hostOptions: AngularHostOptions, packageJsonCache: ts.PackageJsonInfoCache | undefined, + sourceFileCache?: Map, ): AngularCompilerHost { // Create TypeScript compiler host const host: AngularCompilerHost = typescript.createIncrementalCompilerHost(compilerOptions); @@ -254,8 +254,8 @@ export function createAngularCompilerHost( } // Augment TypeScript Host with source file caching if provided - if (hostOptions.sourceFileCache) { - augmentHostWithCaching(host, hostOptions.sourceFileCache); + if (sourceFileCache) { + augmentHostWithCaching(host, sourceFileCache); } return host; diff --git a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts index 61a9e4949fd4..42df6a40e778 100644 --- a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts @@ -11,6 +11,7 @@ import assert from 'node:assert'; import { relative } from 'node:path'; import ts from 'typescript'; import { useTypeChecking } from '../../../utils/environment-options'; +import { toPosixPath } from '../../../utils/path'; import { profileAsync, profileSync } from '../../esbuild/profiling'; import { AngularHostOptions, @@ -55,6 +56,7 @@ class AngularCompilationState { export class AotCompilation extends AngularCompilation { #state?: AngularCompilationState; + readonly #sourceFiles = new Map(); constructor(private readonly browserOnlyBuild: boolean) { super(); @@ -97,27 +99,37 @@ export class AotCompilation extends AngularCompilation { let staleSourceFiles; let clearPackageJsonCache = false; - if (hostOptions.modifiedFiles && this.#state) { + if (hostOptions.modifiedFiles) { for (const modifiedFile of hostOptions.modifiedFiles) { - // Clear package.json cache if a node modules file was modified - if (!clearPackageJsonCache && modifiedFile.includes('node_modules')) { - clearPackageJsonCache = true; - packageJsonCache?.clear(); - } + this.#sourceFiles.delete(toPosixPath(modifiedFile)); - // Collect stale source files for HMR analysis of inline component resources - if (useHmr) { - const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile); - if (sourceFile) { - staleSourceFiles ??= new Map(); - staleSourceFiles.set(modifiedFile, sourceFile); + if (this.#state) { + // Clear package.json cache if a node modules file was modified + if (!clearPackageJsonCache && modifiedFile.includes('node_modules')) { + clearPackageJsonCache = true; + packageJsonCache?.clear(); + } + + // Collect stale source files for HMR analysis of inline component resources + if (useHmr) { + const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile); + if (sourceFile) { + staleSourceFiles ??= new Map(); + staleSourceFiles.set(modifiedFile, sourceFile); + } } } } } // Create Angular compiler host - const host = createAngularCompilerHost(ts, compilerOptions, hostOptions, packageJsonCache); + const host = createAngularCompilerHost( + ts, + compilerOptions, + hostOptions, + packageJsonCache, + this.#sourceFiles, + ); // Create the Angular specific program that contains the Angular compiler const angularProgram = profileSync( @@ -451,6 +463,12 @@ export class AotCompilation extends AngularCompilation { return emittedFiles.values(); } + + override async update(files: Set): Promise { + for (const file of files) { + this.#sourceFiles.delete(toPosixPath(file)); + } + } } function findAffectedFiles( diff --git a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts index ffbfb9dfd7e6..955c90502cb0 100644 --- a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts @@ -9,6 +9,7 @@ import type * as ng from '@angular/compiler-cli'; import assert from 'node:assert'; import ts from 'typescript'; +import { toPosixPath } from '../../../utils/path'; import { profileSync } from '../../esbuild/profiling'; import { AngularHostOptions, createAngularCompilerHost } from '../angular-host'; import { createJitResourceTransformer } from '../transformers/jit-resource-transformer'; @@ -33,6 +34,7 @@ class JitCompilationState { export class JitCompilation extends AngularCompilation { #state?: JitCompilationState; + readonly #sourceFiles = new Map(); constructor(private readonly browserOnlyBuild: boolean) { super(); @@ -56,8 +58,20 @@ export class JitCompilation extends AngularCompilation { const compilerOptions = compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions; + if (hostOptions.modifiedFiles) { + for (const modifiedFile of hostOptions.modifiedFiles) { + this.#sourceFiles.delete(toPosixPath(modifiedFile)); + } + } + // Create Angular compiler host - const host = createAngularCompilerHost(ts, compilerOptions, hostOptions, undefined); + const host = createAngularCompilerHost( + ts, + compilerOptions, + hostOptions, + undefined, + this.#sourceFiles, + ); // Create the TypeScript Program const typeScriptProgram = profileSync('TS_CREATE_PROGRAM', () => @@ -70,10 +84,6 @@ export class JitCompilation extends AngularCompilation { ), ); - const affectedFiles = profileSync('TS_FIND_AFFECTED', () => - findAffectedFiles(typeScriptProgram), - ); - this.#state = new JitCompilationState( host, typeScriptProgram, @@ -157,17 +167,10 @@ export class JitCompilation extends AngularCompilation { return emittedFiles; } -} - -function findAffectedFiles( - builder: ts.EmitAndSemanticDiagnosticsBuilderProgram, -): Set { - const affectedFiles = new Set(); - let result; - while ((result = builder.getSemanticDiagnosticsOfNextAffectedFile())) { - affectedFiles.add(result.affected as ts.SourceFile); + override async update(files: Set): Promise { + for (const file of files) { + this.#sourceFiles.delete(toPosixPath(file)); + } } - - return affectedFiles; } diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts index ee3345d83388..d592b5fb4777 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts @@ -11,7 +11,6 @@ import assert from 'node:assert'; import { randomUUID } from 'node:crypto'; import { type MessagePort, receiveMessageOnPort } from 'node:worker_threads'; import { initializeHash } from '../../../utils/hash'; -import { SourceFileCache } from '../../esbuild/angular/source-file-cache'; import { getAndClearCumulativeDurations } from '../../esbuild/profiling'; import type { AngularCompilation, @@ -35,9 +34,12 @@ export interface InitRequest { let compilation: AngularCompilation | undefined; -const sourceFileCache = new SourceFileCache(); +const modifiedFiles = new Set(); export async function initialize(request: InitRequest): Promise { + const currentModifiedFiles = new Set(modifiedFiles); + modifiedFiles.clear(); + await initializeHash(); compilation ??= request.jit ? new JitCompilation(request.browserOnlyBuild) @@ -62,8 +64,7 @@ export async function initialize(request: InitRequest): Promise((resolve, reject) => @@ -151,6 +152,9 @@ export async function emit() { return [...files]; } -export function update(files: Set): void { - sourceFileCache.invalidate(files); +export async function update(files: Set): Promise { + for (const file of files) { + modifiedFiles.add(file); + } + await compilation?.update?.(files); } diff --git a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts index 0406d4628889..b0ff0593cecc 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -187,7 +187,6 @@ export function createCompilerPlugin( const hostOptions: AngularHostOptions = { fileReplacements: pluginOptions.fileReplacements, modifiedFiles, - sourceFileCache: pluginOptions.sourceFileCache, async transformStylesheet(data, containingFile, stylesheetFile, order, className) { let stylesheetResult; let resultSource = stylesheetFile ?? containingFile; diff --git a/packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts b/packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts index a408650a4f4f..136fbb651ba2 100644 --- a/packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts +++ b/packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts @@ -6,32 +6,24 @@ * found in the LICENSE file at https://angular.dev/license */ -import { platform } from 'node:os'; import * as path from 'node:path'; -import type ts from 'typescript'; import { MemoryLoadResultCache } from '../load-result-cache'; -const USING_WINDOWS = platform() === 'win32'; -const WINDOWS_SEP_REGEXP = new RegExp(`\\${path.win32.sep}`, 'g'); - -export class SourceFileCache extends Map { +export class SourceFileCache { readonly modifiedFiles = new Set(); readonly typeScriptFileCache = new Map(); readonly loadResultCache = new MemoryLoadResultCache(); referencedFiles?: readonly string[]; - constructor(readonly persistentCachePath?: string) { - super(); - } + constructor(readonly persistentCachePath?: string) {} /** * Releases all cached content. The cached data is only needed for incremental * rebuilds and can include the emitted contents of every TypeScript file in the * program. The cache is repopulated if a build is performed after this is called. */ - override clear(): void { - super.clear(); + clear(): void { this.modifiedFiles.clear(); this.typeScriptFileCache.clear(); this.loadResultCache.clear(); @@ -50,13 +42,6 @@ export class SourceFileCache extends Map { file = path.normalize(file); invalid = this.loadResultCache.invalidate(file) || invalid; invalid = extraWatchFiles.has(file) || invalid; - - // Normalize separators to allow matching TypeScript Host paths - if (USING_WINDOWS) { - file = file.replace(WINDOWS_SEP_REGEXP, path.posix.sep); - } - - invalid = this.delete(file) || invalid; this.modifiedFiles.add(file); }