Skip to content

Commit

Permalink
fix(@angular/build): prevent full page reload on HMR updates with SSR…
Browse files Browse the repository at this point in the history
… 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 03ac417)
  • Loading branch information
alan-agius4 committed Jan 17, 2025
1 parent 939d161 commit 3040272
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
69 changes: 36 additions & 33 deletions packages/angular/build/src/builders/dev-server/vite-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand All @@ -322,6 +307,7 @@ export async function* serveWithVite(
});
}
}

context.logger.info('Component update sent to client(s).');
continue;
default:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -483,15 +468,18 @@ export async function* serveWithVite(
await new Promise<void>((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<string, OutputFileRecord>,
assetFiles: Map<string, OutputAssetRecord>,
server: ViteDevServer,
serverOptions: NormalizedDevServerOptions,
logger: BuilderContext['logger'],
componentStyles: Map<string, ComponentStyleRecord>,
): Promise<void> {
): Promise<string[]> {
const updatedFiles: string[] = [];

// Invalidate any updated asset
Expand Down Expand Up @@ -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<string, ComponentStyleRecord>,
updatedFiles: string[],
): void {
if (!updatedFiles.length) {
return;
}

if (serverOptions.hmr) {
if (updatedFiles.every((f) => f.endsWith('.css'))) {
let requiresReload = false;
Expand Down

0 comments on commit 3040272

Please sign in to comment.