From 9bd8fab62bcdb2c1fedfdbe4e10bf6742dcaf4ff Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Thu, 2 Apr 2026 15:57:32 +0530 Subject: [PATCH] fix(@angular/build): invalidate cached SCSS errors when source files are corrected Normalize the file path in MemoryLoadResultCache.invalidate() to match how watch file paths are stored in put(). Without normalization, paths produced by fileURLToPath() or path.join() during Sass error reporting may use different separators than the normalized paths stored as keys in #fileDependencies. This mismatch caused the invalidation lookup to fail, leaving stale error results in the load cache after fixing an SCSS partial. The fix ensures the path is normalized before the lookup so the correct cache entries are cleared and the next rebuild picks up the fix. Fixes #32744 --- .../behavior/rebuild-global_styles_spec.ts | 63 +++++++++++++++++++ .../src/tools/esbuild/load-result-cache.ts | 10 ++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-global_styles_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-global_styles_spec.ts index 22c4c32202bd..b16c8ab2d40c 100644 --- a/packages/angular/build/src/builders/application/tests/behavior/rebuild-global_styles_spec.ts +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-global_styles_spec.ts @@ -87,6 +87,69 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { ); }); + it('recovers from error in SCSS partial after fix on rebuild using @use', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + styles: ['src/styles.scss'], + }); + + await harness.writeFile('src/styles.scss', "@use './variables' as v;\nh1 { color: v.$primary; }"); + await harness.writeFile('src/variables.scss', '$primary: aqua;'); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.toContain('color: aqua'); + + // Introduce a syntax error in the imported partial + await harness.writeFile('src/variables.scss', '$primary: aqua\n$broken;'); + }, + async ({ result }) => { + expect(result?.success).toBe(false); + + // Fix the partial — the cached error should be cleared + await harness.writeFile('src/variables.scss', '$primary: blue;'); + }, + ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.not.toContain('color: aqua'); + harness.expectFile('dist/browser/styles.css').content.toContain('color: blue'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + + it('recovers from error in SCSS partial after fix on initial build using @use', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + styles: ['src/styles.scss'], + }); + + await harness.writeFile('src/styles.scss', "@use './variables' as v;\nh1 { color: v.$primary; }"); + // Start with an error in the partial + await harness.writeFile('src/variables.scss', '$primary: aqua\n$broken;'); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBe(false); + + // Fix the partial + await harness.writeFile('src/variables.scss', '$primary: aqua;'); + }, + ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.toContain('color: aqua'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + it('rebuilds dependent Sass stylesheets after error on initial build from import', async () => { harness.useTarget('build', { ...BASE_OPTIONS, diff --git a/packages/angular/build/src/tools/esbuild/load-result-cache.ts b/packages/angular/build/src/tools/esbuild/load-result-cache.ts index 30067486a384..d130926ca598 100644 --- a/packages/angular/build/src/tools/esbuild/load-result-cache.ts +++ b/packages/angular/build/src/tools/esbuild/load-result-cache.ts @@ -70,7 +70,13 @@ export class MemoryLoadResultCache implements LoadResultCache { } invalidate(path: string): boolean { - const affectedPaths = this.#fileDependencies.get(path); + // Normalize the path to match how watch file paths are stored in `put()`. + // Without normalization, paths produced by `fileURLToPath()` or `path.join()` + // during error reporting may use different separators than the normalized paths + // stored as keys in `#fileDependencies`, causing the lookup to fail and leaving + // stale error results in the cache after the source file is corrected. + const normalizedPath = normalize(path); + const affectedPaths = this.#fileDependencies.get(normalizedPath); let found = false; if (affectedPaths) { @@ -79,7 +85,7 @@ export class MemoryLoadResultCache implements LoadResultCache { found = true; } } - this.#fileDependencies.delete(path); + this.#fileDependencies.delete(normalizedPath); } return found;