Skip to content

Commit c2fdb81

Browse files
committed
Fix client-only module HMR in RSC Framework Mode
1 parent 4a42011 commit c2fdb81

File tree

3 files changed

+64
-2
lines changed

3 files changed

+64
-2
lines changed

integration/vite-css-test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -599,8 +599,7 @@ async function hmrWorkflow({
599599
// changing non-React modules imported by the route.
600600
if (
601601
templateName.includes("rsc") &&
602-
(file === "styles.module.css" ||
603-
file === "styles-postcss-linked.css" ||
602+
(file === "styles-postcss-linked.css" ||
604603
file === "styles-vanilla-global.css.ts")
605604
) {
606605
continue;

packages/react-router-dev/vite/rsc/plugin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
isVirtualClientRouteModuleId,
2121
CLIENT_NON_COMPONENT_EXPORTS,
2222
} from "./virtual-route-modules";
23+
import { hmrInvalidateClientOnlyModulesInRsc } from "./plugins/hmr-invalidate-client-only-modules-in-rsc";
2324
import validatePluginOrder from "../plugins/validate-plugin-order";
2425

2526
export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
@@ -321,6 +322,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
321322
return modules;
322323
},
323324
},
325+
hmrInvalidateClientOnlyModulesInRsc(),
324326
validatePluginOrder(),
325327
rsc({ entries: getRscEntries() }),
326328
];
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type * as Vite from "vite";
2+
3+
export function hmrInvalidateClientOnlyModulesInRsc(): Vite.Plugin {
4+
return {
5+
name: "react-router/rsc/hmr/invalidate-client-only-modules-in-rsc",
6+
async hotUpdate(ctx) {
7+
// We only want to invalidate ancestors of client-only modules in the RSC
8+
// graph, so bail out if we're not in the RSC environment
9+
if (this.environment.name !== "rsc") {
10+
return;
11+
}
12+
13+
const updatedServerModule = this.environment.moduleGraph.getModuleById(
14+
ctx.file,
15+
);
16+
17+
// If this file is in the RSC graph, it's not a client-only module and
18+
// changes will already be picked up, so bail out
19+
if (updatedServerModule) {
20+
return;
21+
}
22+
23+
// Find the corresponding client module for this file so we can walk the
24+
// module graph
25+
const updatedClientModule =
26+
ctx.server.environments.client.moduleGraph.getModuleById(ctx.file);
27+
28+
// If this file is not in the client graph, it's not a client-only module
29+
// and we don't need to invalidate anything, so bail out
30+
if (!updatedClientModule) {
31+
return;
32+
}
33+
34+
const visited = new Set<Vite.EnvironmentModuleNode>();
35+
const walk = (module: Vite.EnvironmentModuleNode) => {
36+
if (!module || visited.has(module) || !module.id) {
37+
return;
38+
}
39+
40+
visited.add(module);
41+
42+
// If this module is in the RSC graph, invalidate it and stop walking
43+
const serverModule = this.environment.moduleGraph.getModuleById(
44+
module.id,
45+
);
46+
if (serverModule) {
47+
this.environment.moduleGraph.invalidateModule(serverModule);
48+
return;
49+
}
50+
51+
if (module.importers) {
52+
for (const importer of module.importers) {
53+
walk(importer);
54+
}
55+
}
56+
};
57+
58+
walk(updatedClientModule);
59+
},
60+
};
61+
}

0 commit comments

Comments
 (0)