From 3254591011b771aa39f73aa255eed8c103c7428d Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:05:30 +0000 Subject: [PATCH 1/3] fix(@angular/build): include inline component stylesheets in referenced watch files Collect referenced files from inline stylesheet contexts in addition to file contexts when gathering watch files, and catch unhandled rejections when disposing an inline bundler context. --- .../src/tools/esbuild/angular/component-stylesheets.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts b/packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts index dd1b3b704150..75ce354341ad 100644 --- a/packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts +++ b/packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts @@ -219,7 +219,7 @@ export class ComponentStylesheetBundler { const filename = secondSemi !== -1 ? entry.slice(secondSemi + 1) : ''; if (filename && normalizedFiles.has(path.normalize(filename))) { this.#inlineContexts.delete(entry); - void bundler.dispose(); + void bundler.dispose().catch(() => {}); } else { bundler.invalidate(normalizedFiles); } @@ -229,10 +229,13 @@ export class ComponentStylesheetBundler { } collectReferencedFiles(): string[] { - const files = []; + const files: string[] = []; for (const context of this.#fileContexts.values()) { files.push(...context.watchFiles); } + for (const context of this.#inlineContexts.values()) { + files.push(...context.watchFiles); + } return files; } From c55f68d051de57add619a597dbf6332195e16597 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:05:44 +0000 Subject: [PATCH 2/3] fix(@angular/build): pass load cache to compiler plugin and escape extension regex in polyfills Ensure sourceFileCache.loadResultCache is forwarded when creating compiler plugin options for browser polyfills, escape the file extension regex in entryFileToWorkspaceRelative, and add a missing semicolon in SSR bundle options. --- .../build/src/tools/esbuild/application-code-bundle.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts index afb03d436d29..af53e04f58af 100644 --- a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts +++ b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts @@ -141,8 +141,8 @@ export function createBrowserPolyfillBundleOptions( buildOptions.plugins ??= []; const pluginOptions = createCompilerPluginOptions( options, - sourceFileCache, + sourceFileCache.loadResultCache, ); buildOptions.plugins.push( createCompilerPlugin( @@ -501,7 +501,7 @@ export function createSsrEntryCodeBundleOptions( // The below is needed to avoid // `Import "default" will always be undefined because there is no matching export` warning when no default is present. `const defaultExportName = 'default';`, - `export default server[defaultExportName]`, + `export default server[defaultExportName];`, // Add @angular/ssr exports `export { AngularAppEngine } from '@angular/ssr';`, @@ -764,7 +764,7 @@ function getEsBuildCommonPolyfillsOptions( } function entryFileToWorkspaceRelative(workspaceRoot: string, entryFile: string): string { - return './' + toPosixPath(relative(workspaceRoot, entryFile).replace(/.[mc]?ts$/, '')); + return './' + toPosixPath(relative(workspaceRoot, entryFile).replace(/\.[mc]?ts$/, '')); } /** From 6a0b76e47c26d7a187481cd73c7d9c5c7a9bb733 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:05:58 +0000 Subject: [PATCH 3/3] fix(@angular/build): prevent stale bundler caching and correctly resolve load cache Introduce an invalidation epoch guard to prevent caching stale esbuild results when an invalidation occurs while bundling is in-flight, and correctly await load cache checks before falling back to alternate keys. --- .../build/src/tools/esbuild/bundler-context.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/angular/build/src/tools/esbuild/bundler-context.ts b/packages/angular/build/src/tools/esbuild/bundler-context.ts index b335a1160ba6..a4228caa5782 100644 --- a/packages/angular/build/src/tools/esbuild/bundler-context.ts +++ b/packages/angular/build/src/tools/esbuild/bundler-context.ts @@ -80,6 +80,7 @@ export class BundlerContext { #optionsFactory: BundlerOptionsFactory; #shouldCacheResult: boolean; #loadCache?: LoadResultCache; + #invalidationEpoch = 0; readonly watchFiles = new Set(); constructor( @@ -128,8 +129,8 @@ export class BundlerContext { const externalImportsBrowser = new Set(); const externalImportsServer = new Set(); - const outputFiles = []; - let externalConfiguration; + const outputFiles: BuildOutputFile[] = []; + let externalConfiguration: Set | undefined; for (const result of results) { warnings.push(...result.warnings); if (result.errors) { @@ -202,6 +203,7 @@ export class BundlerContext { return this.#activeBundlePromise; } + const bundleEpoch = this.#invalidationEpoch; const bundlePromise = this.#performBundle().finally(() => { if (this.#activeBundlePromise === bundlePromise) { this.#activeBundlePromise = undefined; @@ -210,7 +212,7 @@ export class BundlerContext { this.#activeBundlePromise = bundlePromise; const result = await bundlePromise; - if (this.#shouldCacheResult) { + if (this.#shouldCacheResult && bundleEpoch === this.#invalidationEpoch) { this.#esbuildResult = result; } @@ -286,9 +288,10 @@ export class BundlerContext { } if (this.#loadCache) { - const cachedLoad = await (this.#loadCache.get(input) ?? - this.#loadCache.get(input.replace(';', ':')) ?? - this.#loadCache.get('file:' + normalizedAbsoluteInput)); + const cachedLoad = + (await this.#loadCache.get(input)) ?? + (await this.#loadCache.get(input.replace(';', ':'))) ?? + (await this.#loadCache.get('file:' + normalizedAbsoluteInput)); if (cachedLoad?.watchFiles) { for (const file of cachedLoad.watchFiles) { if (!isInternalAngularFile(file)) { @@ -551,6 +554,7 @@ export class BundlerContext { } if (invalid) { + this.#invalidationEpoch++; this.#esbuildResult = undefined; }