diff --git a/packages/angular/build/src/builders/library/pipeline/compilation.ts b/packages/angular/build/src/builders/library/pipeline/compilation.ts
index 1c026c5e9fec..163c6194d6ad 100644
--- a/packages/angular/build/src/builders/library/pipeline/compilation.ts
+++ b/packages/angular/build/src/builders/library/pipeline/compilation.ts
@@ -89,10 +89,7 @@ export async function compileLibrary(
: await stylesheetBundler.bundleInline(data, containingFile);
if (result.referencedFiles) {
- buildState.addFileReferences(containingFile, result.referencedFiles);
- if (stylesheetFile) {
- buildState.addFileReferences(stylesheetFile, result.referencedFiles);
- }
+ buildState.addFileReferences(stylesheetFile ?? containingFile, result.referencedFiles);
}
if (result.warnings.length > 0) {
diff --git a/packages/angular/build/src/builders/library/tests/behavior/watch_spec.ts b/packages/angular/build/src/builders/library/tests/behavior/watch_spec.ts
index 9f85102fe20c..b648c690951a 100644
--- a/packages/angular/build/src/builders/library/tests/behavior/watch_spec.ts
+++ b/packages/angular/build/src/builders/library/tests/behavior/watch_spec.ts
@@ -984,5 +984,79 @@ describeLibraryBuilder(executeLibraryBuilder, LIBRARY_BUILDER_INFO, (harness) =>
{ outputLogsOnFailure: false },
);
});
+
+ it('should rebuild when an external Sass partial or template is modified and recover from template errors', async () => {
+ await harness.writeFiles({
+ 'projects/lib/src/lib/_vars.scss': '$primary-color: rgb(10, 20, 30);',
+ 'projects/lib/src/lib/lib.component.scss':
+ '@use "./vars"; :host { color: vars.$primary-color; }',
+ 'projects/lib/src/lib/lib.component.html': '
{{ title }}
',
+ 'projects/lib/src/lib/lib.component.ts': `
+ import { Component } from '@angular/core';
+
+ @Component({
+ selector: 'lib-external-resources',
+ templateUrl: './lib.component.html',
+ styleUrl: './lib.component.scss',
+ })
+ export class LibComponent {
+ title = 'Hello';
+ }
+ `,
+ });
+
+ harness.useTarget('build', {
+ ...BASE_OPTIONS,
+ watch: true,
+ });
+
+ await harness.executeWithCases([
+ async ({ result }) => {
+ expect(result?.success).toBeTrue();
+ const content = harness.readFile('dist/lib/fesm2022/lib.mjs');
+ expect(content).toContain('h1');
+ expect(content).toMatch(/color:\s*(?:#0a141e|rgb\(10,\s*20,\s*30\))/);
+ expect(harness.readFile('dist/lib/types/lib.d.ts')).toContain('LibComponent');
+
+ // Modify the imported Sass partial
+ await harness.writeFile(
+ 'projects/lib/src/lib/_vars.scss',
+ '$primary-color: rgb(200, 100, 50);',
+ );
+ },
+ async ({ result }) => {
+ expect(result?.success).toBeTrue();
+ expect(harness.readFile('dist/lib/fesm2022/lib.mjs')).toMatch(
+ /color:\s*(?:#c86432|rgb\(200,\s*100,\s*50\))/,
+ );
+ expect(harness.readFile('dist/lib/types/lib.d.ts')).toContain('LibComponent');
+
+ // Introduce a template type-checking error in the external HTML template
+ await harness.writeFile(
+ 'projects/lib/src/lib/lib.component.html',
+ '',
+ );
+ },
+ async ({ result, logs }) => {
+ expect(result?.success).toBeFalse();
+ expect(logs).toContain(
+ jasmine.objectContaining({
+ message: jasmine.stringContaining('nonExistentProp'),
+ }),
+ );
+
+ // Fix the external HTML template error
+ await harness.writeFile(
+ 'projects/lib/src/lib/lib.component.html',
+ '',
+ );
+ },
+ ({ result }) => {
+ expect(result?.success).toBeTrue();
+ expect(harness.readFile('dist/lib/fesm2022/lib.mjs')).toContain('footer');
+ expect(harness.readFile('dist/lib/types/lib.d.ts')).toContain('LibComponent');
+ },
+ ]);
+ });
});
});
diff --git a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts
index 92684539c2ca..8a3e559c9c69 100644
--- a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts
+++ b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts
@@ -83,6 +83,7 @@ class AngularCompilationState {
public readonly declarationDiagnostics = new WeakMap(),
public readonly filesWithoutDtsSignature = new Set(),
public readonly useFallbackRootNames = false,
+ public readonly emittedDtsSourceFiles = new WeakSet(),
) {}
get angularCompiler() {
@@ -362,6 +363,7 @@ export class AotCompilation extends TypeScriptCompilation {
this.#state?.declarationDiagnostics,
filesWithoutDtsSignature,
useFallbackRootNames,
+ this.#state?.emittedDtsSourceFiles,
);
return {
@@ -498,10 +500,10 @@ export class AotCompilation extends TypeScriptCompilation {
override emitAffectedFiles(): Iterable {
assert(this.#state, 'Angular compilation must be initialized prior to emitting files.');
const {
- affectedFiles,
angularCompiler,
compilerHost,
declarationDiagnostics,
+ emittedDtsSourceFiles,
typeScriptProgram,
webWorkerTransform,
useTypeScriptTranspilation,
@@ -512,7 +514,6 @@ export class AotCompilation extends TypeScriptCompilation {
const emittedFiles = new Map();
const emittedSourceFiles = new Set();
- const emittedDtsSourceFiles = new Set();
const writeFileCallback: ts.WriteFileCallback = (filename, contents, _a, _b, sourceFiles) => {
if (!sourceFiles?.length && filename.endsWith(buildInfoFilename)) {
// Save builder info contents to specified location