From 30402720707b7a8b9042a6046692d62a768cdc64 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Fri, 17 Jan 2025 10:45:03 +0000 Subject: [PATCH] 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 #29372 (cherry picked from commit 03ac417425174b5ca75a2f7b1996c7d32f2a217e) --- .../src/builders/application/build-action.ts | 2 +- .../src/builders/dev-server/vite-server.ts | 69 ++++++++++--------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/packages/angular/build/src/builders/application/build-action.ts b/packages/angular/build/src/builders/application/build-action.ts index 8fdfa1a9d5bc..f819ef4e097d 100644 --- a/packages/angular/build/src/builders/application/build-action.ts +++ b/packages/angular/build/src/builders/application/build-action.ts @@ -295,7 +295,7 @@ function* emitOutputResults( if (needFile) { // Updates to non-JS files must signal an update with the dev server - if (!/(?:\.js|\.map)?$/.test(file.path)) { + if (!/(?:\.m?js|\.map)?$/.test(file.path)) { incrementalResult.background = false; } diff --git a/packages/angular/build/src/builders/dev-server/vite-server.ts b/packages/angular/build/src/builders/dev-server/vite-server.ts index 78bb603fd933..13da2788fe57 100644 --- a/packages/angular/build/src/builders/dev-server/vite-server.ts +++ b/packages/angular/build/src/builders/dev-server/vite-server.ts @@ -250,11 +250,6 @@ export async function* serveWithVite( ); } - // Invalidate SSR module graph to ensure that only new rebuild is used and not stale component updates - if (server && browserOptions.ssr && templateUpdates.size > 0) { - server.moduleGraph.invalidateAll(); - } - // Clear stale template updates on code rebuilds templateUpdates.clear(); @@ -303,16 +298,6 @@ export async function* serveWithVite( 'Builder must provide an initial full build before component update results.', ); - // Invalidate SSR module graph to ensure that new component updates are used - // TODO: Use fine-grained invalidation of only the component update modules - if (browserOptions.ssr) { - server.moduleGraph.invalidateAll(); - const { ɵresetCompiledComponents } = (await server.ssrLoadModule('/main.server.mjs')) as { - ɵresetCompiledComponents: () => void; - }; - ɵresetCompiledComponents(); - } - for (const componentUpdate of result.updates) { if (componentUpdate.type === 'template') { templateUpdates.set(componentUpdate.id, componentUpdate.content); @@ -322,6 +307,7 @@ export async function* serveWithVite( }); } } + context.logger.info('Component update sent to client(s).'); continue; default: @@ -367,16 +353,15 @@ export async function* serveWithVite( ]), ]; + const updatedFiles = await invalidateUpdatedFiles( + normalizePath, + generatedFiles, + assetFiles, + server, + ); + if (needClientUpdate) { - await handleUpdate( - normalizePath, - generatedFiles, - assetFiles, - server, - serverOptions, - context.logger, - componentStyles, - ); + handleUpdate(server, serverOptions, context.logger, componentStyles, updatedFiles); } } else { const projectName = context.target?.project; @@ -483,15 +468,18 @@ export async function* serveWithVite( await new Promise((resolve) => (deferred = resolve)); } -async function handleUpdate( +/** + * Invalidates any updated asset or generated files and resets their `updated` state. + * This function also clears the server application cache when necessary. + * + * @returns A list of files that were updated and invalidated. + */ +async function invalidateUpdatedFiles( normalizePath: (id: string) => string, generatedFiles: Map, assetFiles: Map, server: ViteDevServer, - serverOptions: NormalizedDevServerOptions, - logger: BuilderContext['logger'], - componentStyles: Map, -): Promise { +): Promise { const updatedFiles: string[] = []; // Invalidate any updated asset @@ -531,15 +519,30 @@ async function handleUpdate( updatedModules?.forEach((m) => server.moduleGraph.invalidateModule(m)); } - if (!updatedFiles.length) { - return; - } - if (destroyAngularServerAppCalled) { // Trigger module evaluation before reload to initiate dependency optimization. await server.ssrLoadModule('/main.server.mjs'); } + return updatedFiles; +} + +/** + * Handles updates for the client by sending HMR or full page reload commands + * based on the updated files. It also ensures proper tracking of component styles and determines if + * a full reload is needed. + */ +function handleUpdate( + server: ViteDevServer, + serverOptions: NormalizedDevServerOptions, + logger: BuilderContext['logger'], + componentStyles: Map, + updatedFiles: string[], +): void { + if (!updatedFiles.length) { + return; + } + if (serverOptions.hmr) { if (updatedFiles.every((f) => f.endsWith('.css'))) { let requiresReload = false;