fix(@angular/build): ensure full rebuild after initial error build in watch mode

If an initial build of an application results in an error during watch mode
(including `ng serve`), the following non-error rebuild will now always be
a full build result. This ensures that all new files are available for later
incremental build result updates.
This commit is contained in:
Charles Lyding 2025-02-04 10:39:17 -05:00 committed by Alan Agius
parent 5c1360179c
commit b24089ef86
4 changed files with 82 additions and 1 deletions

View File

@ -152,6 +152,10 @@ export async function* runEsBuildBuildAction(
return;
}
// Used to force a full result on next rebuild if there were initial errors.
// This ensures at least one full result is emitted.
let hasInitialErrors = result.errors.length > 0;
// Wait for changes and rebuild as needed
const currentWatchFiles = new Set(result.watchFiles);
try {
@ -201,10 +205,13 @@ export async function* runEsBuildBuildAction(
result,
outputOptions,
changes,
incrementalResults ? rebuildState : undefined,
incrementalResults && !hasInitialErrors ? rebuildState : undefined,
)) {
yield outputResult;
}
// Clear initial build errors flag if no errors are now present
hasInitialErrors &&= result.errors.length > 0;
}
} finally {
// Stop the watcher and cleanup incremental rebuild state

View File

@ -0,0 +1,66 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { concatMap, count, take, timeout } from 'rxjs';
import { executeDevServer } from '../../index';
import { describeServeBuilder } from '../jasmine-helpers';
import { BASE_OPTIONS, BUILD_TIMEOUT, DEV_SERVER_BUILDER_INFO } from '../setup';
import { logging } from '@angular-devkit/core';
describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => {
describe('Behavior: "Rebuild Error Detection"', () => {
beforeEach(() => {
setupTarget(harness);
});
it('Emits full build result with incremental enabled and initial build has errors', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
watch: true,
});
// Missing ending `>` on the div will cause an error
await harness.appendToFile('src/app/app.component.html', '<div>Hello, world!</div');
const buildCount = await harness
.execute({ outputLogsOnFailure: false })
.pipe(
timeout(BUILD_TIMEOUT),
concatMap(async ({ result, logs }, index) => {
switch (index) {
case 0:
expect(result?.success).toBeFalse();
debugger;
expect(logs).toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('Unexpected character "EOF"'),
}),
);
await harness.appendToFile('src/app/app.component.html', '>');
break;
case 1:
expect(result?.success).toBeTrue();
expect(logs).not.toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('Unexpected character "EOF"'),
}),
);
break;
}
}),
take(2),
count(),
)
.toPromise();
expect(buildCount).toBe(2);
});
});
});

View File

@ -213,6 +213,8 @@ export async function* serveWithVite(
},
});
}
yield { baseUrl: '', success: false };
continue;
}
// Clear existing error overlay on successful result

View File

@ -32,6 +32,12 @@ export default async function () {
};
}
// Remove bundle budgets due to the increased size from JIT
build.configurations.production = {
...build.configurations.production,
budgets: undefined,
};
build.options.aot = false;
});
// Test it works