Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setu

describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
describe('Behavior: "Rebuilds when global stylesheets change"', () => {
const CSS_AQUA = 'color: aqua';
const CSS_BLUE = 'color: blue';
const SCSS_AQUA = '$primary: aqua;';
const SCSS_BROKEN = '$primary: aqua\n$broken;';

beforeEach(async () => {
// Application code is not needed for styles tests
await harness.writeFile('src/main.ts', 'console.log("TEST");');
Expand All @@ -30,8 +35,8 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
[
async ({ result }) => {
expect(result?.success).toBe(true);
harness.expectFile('dist/browser/styles.css').content.toContain('color: aqua');
harness.expectFile('dist/browser/styles.css').content.not.toContain('color: blue');
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_AQUA);
harness.expectFile('dist/browser/styles.css').content.not.toContain(CSS_BLUE);

await harness.writeFile(
'src/a.scss',
Expand All @@ -45,8 +50,8 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
},
({ 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');
harness.expectFile('dist/browser/styles.css').content.not.toContain(CSS_AQUA);
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_BLUE);
},
],
{ outputLogsOnFailure: false },
Expand All @@ -72,15 +77,78 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
},
async ({ result }) => {
expect(result?.success).toBe(true);
harness.expectFile('dist/browser/styles.css').content.toContain('color: aqua');
harness.expectFile('dist/browser/styles.css').content.not.toContain('color: blue');
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_AQUA);
harness.expectFile('dist/browser/styles.css').content.not.toContain(CSS_BLUE);

await harness.writeFile('src/a.scss', '$primary: blue;\\nh1 { color: $primary; }');
},
({ 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');
harness.expectFile('dist/browser/styles.css').content.not.toContain(CSS_AQUA);
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_BLUE);
},
],
{ outputLogsOnFailure: false },
);
});

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', SCSS_AQUA);

await harness.executeWithCases(
[
async ({ result }) => {
expect(result?.success).toBe(true);
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_AQUA);

// Introduce a syntax error in the imported partial
await harness.writeFile('src/variables.scss', SCSS_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(CSS_AQUA);
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_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', SCSS_BROKEN);

await harness.executeWithCases(
[
async ({ result }) => {
expect(result?.success).toBe(false);

// Fix the partial
await harness.writeFile('src/variables.scss', SCSS_AQUA);
},
({ result }) => {
expect(result?.success).toBe(true);
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_AQUA);
},
],
{ outputLogsOnFailure: false },
Expand Down Expand Up @@ -110,23 +178,23 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
},
async ({ result }) => {
expect(result?.success).toBe(true);
harness.expectFile('dist/browser/styles.css').content.toContain('color: aqua');
harness.expectFile('dist/browser/styles.css').content.not.toContain('color: blue');
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_AQUA);
harness.expectFile('dist/browser/styles.css').content.not.toContain(CSS_BLUE);

harness.expectFile('dist/browser/other.css').content.toContain('color: green');
harness.expectFile('dist/browser/other.css').content.toContain('color: aqua');
harness.expectFile('dist/browser/other.css').content.not.toContain('color: blue');
harness.expectFile('dist/browser/other.css').content.toContain(CSS_AQUA);
harness.expectFile('dist/browser/other.css').content.not.toContain(CSS_BLUE);

await harness.writeFile('src/a.scss', '$primary: blue;\\nh1 { color: $primary; }');
},
({ 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');
harness.expectFile('dist/browser/styles.css').content.not.toContain(CSS_AQUA);
harness.expectFile('dist/browser/styles.css').content.toContain(CSS_BLUE);

harness.expectFile('dist/browser/other.css').content.toContain('color: green');
harness.expectFile('dist/browser/other.css').content.not.toContain('color: aqua');
harness.expectFile('dist/browser/other.css').content.toContain('color: blue');
harness.expectFile('dist/browser/other.css').content.not.toContain(CSS_AQUA);
harness.expectFile('dist/browser/other.css').content.toContain(CSS_BLUE);
},
],
{ outputLogsOnFailure: false },
Expand Down
10 changes: 8 additions & 2 deletions packages/angular/build/src/tools/esbuild/load-result-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -79,7 +85,7 @@ export class MemoryLoadResultCache implements LoadResultCache {
found = true;
}
}
this.#fileDependencies.delete(path);
this.#fileDependencies.delete(normalizedPath);
}

return found;
Expand Down
Loading