Skip to content

fix(@angular/build): invalidate cached SCSS errors when source files are corrected#32924

Open
maruthang wants to merge 1 commit intoangular:mainfrom
maruthang:fix-32744-scss-error-cache-v2
Open

fix(@angular/build): invalidate cached SCSS errors when source files are corrected#32924
maruthang wants to merge 1 commit intoangular:mainfrom
maruthang:fix-32744-scss-error-cache-v2

Conversation

@maruthang
Copy link
Copy Markdown
Contributor

Problem

When a SCSS partial (or any Sass dependency) contains an error during ng serve, fixing the file does not clear the error in watch mode. The stale error remains cached even after the source is corrected. Fixes #32744.

Root Cause

MemoryLoadResultCache.put() stores file dependencies using normalize(watchFile) to ensure OS-consistent path separators. However, invalidate() looked up dependencies using the raw, un-normalized path. During Sass error reporting, watchFiles are populated via extractFilesFromStack() (which uses path.join(cwd, relativePath)) and fileURLToPath() — both produce paths that may use different separators. The resulting path mismatch caused invalidate() to silently fail to find the cache entry, leaving the stale error result in #loadResults. On the next rebuild, createCachedLoad() returned the stale (error) result from the cache instead of re-compiling the now-fixed SCSS.

Fix

Normalize the path inside invalidate() (using the same normalize() call that put() uses) before looking up the key in #fileDependencies. This ensures the invalidation lookup always matches the stored entry regardless of how the path was formatted by the caller.

Changes

  • packages/angular/build/src/tools/esbuild/load-result-cache.ts — normalize path in invalidate() to match how keys are stored in put()
  • packages/angular/build/src/builders/application/tests/behavior/rebuild-global_styles_spec.ts — add two regression tests covering the @use partial error scenario (rebuild after error introduced mid-watch, and recovery from error present at initial build)

Test plan

  • bazel test //packages/angular/build:application_integration_tests — new tests recovers from error in SCSS partial after fix on rebuild using @use and recovers from error in SCSS partial after fix on initial build using @use pass
  • Existing rebuilds Sass stylesheet after error on rebuild from import and related tests continue to pass

…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 angular#32744
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes a caching issue in MemoryLoadResultCache where path separator inconsistencies prevented proper invalidation of dependencies, leading to stale error results. The fix involves normalizing paths before lookup and deletion. Additionally, new regression tests were added to verify that SCSS partials correctly recover from syntax errors during rebuilds and initial builds. I have no further feedback, though the suggestion to extract repeated test strings into constants is a good opportunity to improve test maintainability.

Comment on lines +90 to +151
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 },
);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's some duplication of test data strings (like SCSS content and expected CSS output) between the two new tests. To improve maintainability and readability, consider defining these repeated strings as constants at the top of the describe block or before the tests. For example:

const SCSS_AQUA = '$primary: aqua;';
const SCSS_BROKEN = '$primary: aqua\n$broken;';
const CSS_AQUA = 'color: aqua';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SCSS partial error cached and not invalidated on fix during ng serve (esbuild)

1 participant