From d6e4ea543b9909174d8e4e6adfafc646c488b6df Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 23 May 2023 13:02:35 -0400 Subject: [PATCH] fix(@angular-devkit/build-angular): watch all TypeScript referenced files in esbuild builder When using the esbuild-based browser application builder in watch mode, all files referenced by the TypeScript program are now watched in additional to files within the project root. This allows for more extensive monorepo setups to take advantage of watch mode as TypeScript files may exist in other library projects within the repository. --- .../angular/angular-compilation.ts | 6 +++++- .../browser-esbuild/angular/aot-compilation.ts | 13 +++++++++++-- .../browser-esbuild/angular/compiler-plugin.ts | 8 ++++++++ .../browser-esbuild/angular/jit-compilation.ts | 12 ++++++++++-- .../src/builders/browser-esbuild/index.ts | 16 ++++++++++++++++ .../src/builders/browser-esbuild/watcher.ts | 4 ++-- 6 files changed, 52 insertions(+), 7 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/angular-compilation.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/angular-compilation.ts index de1e775dbdd8..1889cdfebe6a 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/angular-compilation.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/angular-compilation.ts @@ -53,7 +53,11 @@ export abstract class AngularCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions, - ): Promise<{ affectedFiles: ReadonlySet; compilerOptions: ng.CompilerOptions }>; + ): Promise<{ + affectedFiles: ReadonlySet; + compilerOptions: ng.CompilerOptions; + referencedFiles: readonly string[]; + }>; abstract collectDiagnostics(): Iterable; diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/aot-compilation.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/aot-compilation.ts index 81c60707b746..84a59e40a7b9 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/aot-compilation.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/aot-compilation.ts @@ -43,7 +43,11 @@ export class AotCompilation extends AngularCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions, - ): Promise<{ affectedFiles: ReadonlySet; compilerOptions: ng.CompilerOptions }> { + ): Promise<{ + affectedFiles: ReadonlySet; + compilerOptions: ng.CompilerOptions; + referencedFiles: readonly string[]; + }> { // Dynamically load the Angular compiler CLI package const { NgtscProgram, OptimizeFor } = await AngularCompilation.loadCompilerCli(); @@ -96,7 +100,12 @@ export class AotCompilation extends AngularCompilation { this.#state?.diagnosticCache, ); - return { affectedFiles, compilerOptions }; + const referencedFiles = typeScriptProgram + .getSourceFiles() + .filter((sourceFile) => !angularCompiler.ignoreForEmit.has(sourceFile)) + .map((sourceFile) => sourceFile.fileName); + + return { affectedFiles, compilerOptions, referencedFiles }; } *collectDiagnostics(): Iterable { diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/compiler-plugin.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/compiler-plugin.ts index 0ee89d2c5cd8..4d1107398b81 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/compiler-plugin.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/compiler-plugin.ts @@ -45,6 +45,8 @@ export class SourceFileCache extends Map { readonly typeScriptFileCache = new Map(); readonly loadResultCache = new MemoryLoadResultCache(); + referencedFiles?: readonly string[]; + constructor(readonly persistentCachePath?: string) { super(); } @@ -185,6 +187,7 @@ export function createCompilerPlugin( // In watch mode, previous build state will be reused. const { compilerOptions: { allowJs }, + referencedFiles, } = await compilation.initialize(tsconfigPath, hostOptions, (compilerOptions) => { if ( compilerOptions.target === undefined || @@ -254,6 +257,11 @@ export function createCompilerPlugin( } }); + // Store referenced files for updated file watching if enabled + if (pluginOptions.sourceFileCache) { + pluginOptions.sourceFileCache.referencedFiles = referencedFiles; + } + // Reset the setup warnings so that they are only shown during the first build. setupWarnings = undefined; diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/jit-compilation.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/jit-compilation.ts index 2df13e01955f..d7c455645c15 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/jit-compilation.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/angular/jit-compilation.ts @@ -30,7 +30,11 @@ export class JitCompilation extends AngularCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions, - ): Promise<{ affectedFiles: ReadonlySet; compilerOptions: ng.CompilerOptions }> { + ): Promise<{ + affectedFiles: ReadonlySet; + compilerOptions: ng.CompilerOptions; + referencedFiles: readonly string[]; + }> { // Dynamically load the Angular compiler CLI package const { constructorParametersDownlevelTransform } = await AngularCompilation.loadCompilerCli(); @@ -68,7 +72,11 @@ export class JitCompilation extends AngularCompilation { createJitResourceTransformer(() => typeScriptProgram.getProgram().getTypeChecker()), ); - return { affectedFiles, compilerOptions }; + const referencedFiles = typeScriptProgram + .getSourceFiles() + .map((sourceFile) => sourceFile.fileName); + + return { affectedFiles, compilerOptions, referencedFiles }; } *collectDiagnostics(): Iterable { diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts index be61739b076a..950883ab0330 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts @@ -73,6 +73,10 @@ class ExecutionResult { }; } + get watchFiles() { + return this.codeBundleCache?.referencedFiles ?? []; + } + createRebuildState(fileChanges: ChangedFiles): RebuildState { this.codeBundleCache?.invalidate([...fileChanges.modified, ...fileChanges.removed]); @@ -676,6 +680,10 @@ export async function* buildEsbuildBrowserInternal( ]; watcher.add(packageWatchFiles.map((file) => path.join(normalizedOptions.workspaceRoot, file))); + // Watch locations provided by the initial build result + let previousWatchFiles = new Set(result.watchFiles); + watcher.add(result.watchFiles); + // Wait for changes and rebuild as needed try { for await (const changes of watcher) { @@ -687,6 +695,14 @@ export async function* buildEsbuildBrowserInternal( execute(normalizedOptions, context, result.createRebuildState(changes)), ); + // Update watched locations provided by the new build result. + // Add any new locations + watcher.add(result.watchFiles.filter((watchFile) => !previousWatchFiles.has(watchFile))); + const newWatchFiles = new Set(result.watchFiles); + // Remove any old locations + watcher.remove([...previousWatchFiles].filter((watchFile) => !newWatchFiles.has(watchFile))); + previousWatchFiles = newWatchFiles; + if (shouldWriteResult) { // Write output files await writeResultFiles(result.outputFiles, result.assetFiles, normalizedOptions.outputPath); diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/watcher.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/watcher.ts index 2fd26ee56f2e..053f585b48f7 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/watcher.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/watcher.ts @@ -25,8 +25,8 @@ export class ChangedFiles { } export interface BuildWatcher extends AsyncIterableIterator { - add(paths: string | string[]): void; - remove(paths: string | string[]): void; + add(paths: string | readonly string[]): void; + remove(paths: string | readonly string[]): void; close(): Promise; }