Skip to content

Commit dfd4582

Browse files
committed
fix(@angular/build): prevent full page reload on HMR updates with SSR enabled
This commit resolves an issue where HMR would incorrectly trigger a full page reload when used with SSR. Closes angular#29372
1 parent 612da79 commit dfd4582

File tree

2 files changed

+33
-37
lines changed

2 files changed

+33
-37
lines changed

packages/angular/build/src/builders/application/build-action.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ function* emitOutputResults(
295295

296296
if (needFile) {
297297
// Updates to non-JS files must signal an update with the dev server
298-
if (!/(?:\.js|\.map)?$/.test(file.path)) {
298+
if (!/(?:\.m?js|\.map)?$/.test(file.path)) {
299299
incrementalResult.background = false;
300300
}
301301

packages/angular/build/src/builders/dev-server/vite-server.ts

+32-36
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,6 @@ export async function* serveWithVite(
250250
);
251251
}
252252

253-
// Invalidate SSR module graph to ensure that only new rebuild is used and not stale component updates
254-
if (server && browserOptions.ssr && templateUpdates.size > 0) {
255-
server.moduleGraph.invalidateAll();
256-
}
257-
258253
// Clear stale template updates on code rebuilds
259254
templateUpdates.clear();
260255

@@ -302,17 +297,6 @@ export async function* serveWithVite(
302297
server,
303298
'Builder must provide an initial full build before component update results.',
304299
);
305-
306-
// Invalidate SSR module graph to ensure that new component updates are used
307-
// TODO: Use fine-grained invalidation of only the component update modules
308-
if (browserOptions.ssr) {
309-
server.moduleGraph.invalidateAll();
310-
const { ɵresetCompiledComponents } = (await server.ssrLoadModule('/main.server.mjs')) as {
311-
ɵresetCompiledComponents: () => void;
312-
};
313-
ɵresetCompiledComponents();
314-
}
315-
316300
for (const componentUpdate of result.updates) {
317301
if (componentUpdate.type === 'template') {
318302
templateUpdates.set(componentUpdate.id, componentUpdate.content);
@@ -367,16 +351,15 @@ export async function* serveWithVite(
367351
]),
368352
];
369353

354+
const updatedFiles = await invalidateUpdatedFiles(
355+
normalizePath,
356+
generatedFiles,
357+
assetFiles,
358+
server,
359+
);
360+
370361
if (needClientUpdate) {
371-
await handleUpdate(
372-
normalizePath,
373-
generatedFiles,
374-
assetFiles,
375-
server,
376-
serverOptions,
377-
context.logger,
378-
componentStyles,
379-
);
362+
handleUpdate(server, serverOptions, context.logger, componentStyles, updatedFiles);
380363
}
381364
} else {
382365
const projectName = context.target?.project;
@@ -483,16 +466,20 @@ export async function* serveWithVite(
483466
await new Promise<void>((resolve) => (deferred = resolve));
484467
}
485468

486-
async function handleUpdate(
469+
/**
470+
* Invalidates any updated asset or generated files and resets their `updated` state.
471+
* This function also clears the server application cache when necessary.
472+
*
473+
* @returns A list of files that were updated and invalidated.
474+
*/
475+
async function invalidateUpdatedFiles(
487476
normalizePath: (id: string) => string,
488477
generatedFiles: Map<string, OutputFileRecord>,
489478
assetFiles: Map<string, OutputAssetRecord>,
490479
server: ViteDevServer,
491-
serverOptions: NormalizedDevServerOptions,
492-
logger: BuilderContext['logger'],
493-
componentStyles: Map<string, ComponentStyleRecord>,
494-
): Promise<void> {
480+
): Promise<string[]> {
495481
const updatedFiles: string[] = [];
482+
let destroyAngularServerAppCalled = false;
496483

497484
// Invalidate any updated asset
498485
for (const [file, record] of assetFiles) {
@@ -505,7 +492,6 @@ async function handleUpdate(
505492
}
506493

507494
// Invalidate any updated files
508-
let destroyAngularServerAppCalled = false;
509495
for (const [file, record] of generatedFiles) {
510496
if (!record.updated) {
511497
continue;
@@ -531,15 +517,25 @@ async function handleUpdate(
531517
updatedModules?.forEach((m) => server.moduleGraph.invalidateModule(m));
532518
}
533519

520+
return updatedFiles;
521+
}
522+
523+
/**
524+
* Handles updates for the client by sending HMR or full page reload commands
525+
* based on the updated files. It also ensures proper tracking of component styles and determines if
526+
* a full reload is needed.
527+
*/
528+
function handleUpdate(
529+
server: ViteDevServer,
530+
serverOptions: NormalizedDevServerOptions,
531+
logger: BuilderContext['logger'],
532+
componentStyles: Map<string, ComponentStyleRecord>,
533+
updatedFiles: string[],
534+
): void {
534535
if (!updatedFiles.length) {
535536
return;
536537
}
537538

538-
if (destroyAngularServerAppCalled) {
539-
// Trigger module evaluation before reload to initiate dependency optimization.
540-
await server.ssrLoadModule('/main.server.mjs');
541-
}
542-
543539
if (serverOptions.hmr) {
544540
if (updatedFiles.every((f) => f.endsWith('.css'))) {
545541
let requiresReload = false;

0 commit comments

Comments
 (0)