Skip to content

Commit e862d57

Browse files
Support RSC Framework Mode in react-router-serve (#14324)
1 parent 2d83c21 commit e862d57

File tree

12 files changed

+177
-113
lines changed

12 files changed

+177
-113
lines changed

integration/helpers/rsc-vite-framework/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
},
3131
"dependencies": {
3232
"@mjackson/node-fetch-server": "0.6.1",
33+
"@react-router/serve": "workspace:*",
3334
"compression": "^1.8.0",
3435
"express": "^4.21.2",
3536
"react": "^19.0.0",

integration/helpers/vite.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -373,31 +373,6 @@ export const reactRouterServe = async ({
373373
return () => serveProc.kill();
374374
};
375375

376-
export const runStartScript = async ({
377-
cwd,
378-
port,
379-
viteBase,
380-
basename,
381-
}: {
382-
cwd: string;
383-
port: number;
384-
viteBase?: string;
385-
basename?: string;
386-
}) => {
387-
let nodeBin = process.argv[0];
388-
let proc = spawn(nodeBin, ["start.js"], {
389-
cwd,
390-
stdio: "pipe",
391-
env: {
392-
NODE_ENV: "production",
393-
PORT: port.toFixed(0),
394-
VITE_BASE: viteBase,
395-
},
396-
});
397-
await waitForServer(proc, { port, basename });
398-
return () => proc.kill();
399-
};
400-
401376
export const wranglerPagesDev = async ({
402377
cwd,
403378
port,
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { test, expect } from "@playwright/test";
2+
3+
import { PlaywrightFixture } from "./helpers/playwright-fixture.js";
4+
import type { Fixture, AppFixture } from "./helpers/create-fixture.js";
5+
import {
6+
createAppFixture,
7+
createFixture,
8+
js,
9+
} from "./helpers/create-fixture.js";
10+
import { type TemplateName } from "./helpers/vite.js";
11+
12+
const templateNames = [
13+
"vite-5-template",
14+
"rsc-vite-framework",
15+
] as const satisfies TemplateName[];
16+
17+
test.describe("react-router-serve", () => {
18+
for (const templateName of templateNames) {
19+
test.describe(`template: ${templateName}`, () => {
20+
let fixture: Fixture;
21+
let appFixture: AppFixture;
22+
23+
test.beforeEach(async ({ context }) => {
24+
await context.route(/\.(data|rsc)$/, async (route) => {
25+
await new Promise((resolve) => setTimeout(resolve, 50));
26+
route.continue();
27+
});
28+
});
29+
30+
test.beforeAll(async () => {
31+
fixture = await createFixture({
32+
templateName,
33+
useReactRouterServe: true,
34+
files: {
35+
"app/routes/_index.tsx": js`
36+
import { useLoaderData, Link } from "react-router";
37+
38+
export function loader() {
39+
return "pizza";
40+
}
41+
42+
export default function Index() {
43+
let data = useLoaderData();
44+
return (
45+
<div>
46+
{data}
47+
<Link to="/burgers">Other Route</Link>
48+
</div>
49+
)
50+
}
51+
`,
52+
53+
"app/routes/burgers.tsx": js`
54+
export default function Index() {
55+
return <div>cheeseburger</div>;
56+
}
57+
`,
58+
},
59+
});
60+
61+
// This creates an interactive app using playwright.
62+
appFixture = await createAppFixture(fixture);
63+
});
64+
65+
test.afterAll(() => {
66+
appFixture.close();
67+
});
68+
69+
test("should start and perform client side navigation", async ({
70+
page,
71+
}) => {
72+
let app = new PlaywrightFixture(appFixture, page);
73+
// You can test any request your app might get using `fixture`.
74+
let response = await fixture.requestDocument("/");
75+
expect(await response.text()).toMatch("pizza");
76+
77+
// If you need to test interactivity use the `app`
78+
await app.goto("/");
79+
await app.clickLink("/burgers");
80+
await page.waitForSelector("text=cheeseburger");
81+
});
82+
});
83+
}
84+
});

integration/remix-serve-test.ts

Lines changed: 0 additions & 69 deletions
This file was deleted.

integration/vite-basename-test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
viteConfig,
1111
dev,
1212
viteDevCmd,
13-
runStartScript,
1413
reactRouterServe,
1514
reactRouterConfig,
1615
type TemplateName,
@@ -399,9 +398,7 @@ test.describe("Vite base + React Router basename", () => {
399398
);
400399
build({ cwd });
401400
if (startServer !== false) {
402-
stop = templateName.includes("rsc")
403-
? await runStartScript({ cwd, port, viteBase: base, basename })
404-
: await reactRouterServe({ cwd, port, basename });
401+
stop = await reactRouterServe({ cwd, port, basename });
405402
}
406403
}
407404

integration/vite-css-test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
createEditor,
88
dev,
99
build,
10-
runStartScript,
1110
reactRouterServe,
1211
customDev,
1312
EXPRESS_SERVER,
@@ -380,9 +379,7 @@ test.describe("Vite CSS", () => {
380379
}
381380
expect(stderrString).toBeFalsy();
382381
expect(status).toBe(0);
383-
stop = templateName.includes("rsc")
384-
? await runStartScript({ cwd, port })
385-
: await reactRouterServe({ cwd, port });
382+
stop = await reactRouterServe({ cwd, port });
386383
});
387384
test.afterAll(() => stop());
388385

@@ -446,9 +443,7 @@ test.describe("Vite CSS", () => {
446443
});
447444
expect(stderr.toString()).toBeFalsy();
448445
expect(status).toBe(0);
449-
stop = templateName.includes("rsc")
450-
? await runStartScript({ cwd, port })
451-
: await reactRouterServe({ cwd, port });
446+
stop = await reactRouterServe({ cwd, port });
452447
});
453448
test.afterAll(() => stop());
454449

packages/react-router-dev/config/default-rsc-entries/entry.rsc.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { unstable_matchRSCServerRequest as matchRSCServerRequest } from "react-r
1010

1111
import routes from "virtual:react-router/unstable_rsc/routes";
1212
import basename from "virtual:react-router/unstable_rsc/basename";
13+
import unstable_reactRouterServeConfig from "virtual:react-router/unstable_rsc/react-router-serve-config";
1314

1415
export async function fetchServer(request: Request) {
1516
return await matchRSCServerRequest({
@@ -30,6 +31,8 @@ export async function fetchServer(request: Request) {
3031
});
3132
}
3233

34+
export { unstable_reactRouterServeConfig };
35+
3336
export default async function handler(request: Request) {
3437
const ssr = await import.meta.viteRsc.loadModule<
3538
typeof import("./entry.ssr")

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as Typegen from "../../typegen";
99
import { readFileSync } from "fs";
1010
import { readFile } from "fs/promises";
1111
import path, { join, dirname } from "pathe";
12+
import invariant from "../../invariant";
1213
import {
1314
type ConfigLoader,
1415
type ResolvedReactRouterConfig,
@@ -32,6 +33,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
3233
let configLoader: ConfigLoader;
3334
let typegenWatcherPromise: Promise<Typegen.Watcher> | undefined;
3435
let viteCommand: Vite.ConfigEnv["command"];
36+
let resolvedViteConfig: Vite.ResolvedConfig;
3537
let routeIdByFile: Map<string, string> | undefined;
3638
let logger: Vite.Logger;
3739

@@ -231,6 +233,9 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
231233
},
232234
};
233235
},
236+
configResolved(viteConfig) {
237+
resolvedViteConfig = viteConfig;
238+
},
234239
async configureServer(viteDevServer) {
235240
configLoader.onChange(
236241
async ({
@@ -499,6 +504,30 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
499504
return modules;
500505
},
501506
},
507+
{
508+
name: "react-router/rsc/virtual-react-router-serve-config",
509+
resolveId(id) {
510+
if (id === virtual.reactRouterServeConfig.id) {
511+
return virtual.reactRouterServeConfig.resolvedId;
512+
}
513+
},
514+
load(id) {
515+
if (id === virtual.reactRouterServeConfig.resolvedId) {
516+
const rscOutDir = resolvedViteConfig.environments.rsc?.build?.outDir;
517+
invariant(rscOutDir, "RSC build directory config not found");
518+
const clientOutDir =
519+
resolvedViteConfig.environments.client?.build?.outDir;
520+
invariant(clientOutDir, "Client build directory config not found");
521+
const assetsBuildDirectory = Path.relative(rscOutDir, clientOutDir);
522+
const publicPath = resolvedViteConfig.base;
523+
524+
return `export default ${JSON.stringify({
525+
assetsBuildDirectory,
526+
publicPath,
527+
})};`;
528+
}
529+
},
530+
},
502531
validatePluginOrder(),
503532
warnOnClientSourceMaps(),
504533
];
@@ -510,6 +539,7 @@ const virtual = {
510539
hmrRuntime: create("unstable_rsc/runtime"),
511540
basename: create("unstable_rsc/basename"),
512541
rscEntry: create("unstable_rsc/rsc-entry"),
542+
reactRouterServeConfig: create("unstable_rsc/react-router-serve-config"),
513543
};
514544

515545
function invalidateVirtualModules(viteDevServer: Vite.ViteDevServer) {

0 commit comments

Comments
 (0)