Skip to content

Commit 655cb45

Browse files
committed
Support RSC Framework Mode in react-router-serve
1 parent e72fc6e commit 655cb45

File tree

12 files changed

+181
-113
lines changed

12 files changed

+181
-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: 34 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,
@@ -31,6 +32,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
3132
let configLoader: ConfigLoader;
3233
let typegenWatcherPromise: Promise<Typegen.Watcher> | undefined;
3334
let viteCommand: Vite.ConfigEnv["command"];
35+
let resolvedViteConfig: Vite.ResolvedConfig;
3436
let routeIdByFile: Map<string, string> | undefined;
3537
let logger: Vite.Logger;
3638

@@ -224,6 +226,9 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
224226
},
225227
};
226228
},
229+
configResolved(viteConfig) {
230+
resolvedViteConfig = viteConfig;
231+
},
227232
async configureServer(viteDevServer) {
228233
configLoader.onChange(
229234
async ({
@@ -492,6 +497,34 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
492497
return modules;
493498
},
494499
},
500+
{
501+
name: "react-router/rsc/virtual-react-router-serve-config",
502+
resolveId(id) {
503+
if (id === virtual.reactRouterServeConfig.id) {
504+
return virtual.reactRouterServeConfig.resolvedId;
505+
}
506+
},
507+
load(id) {
508+
if (id === virtual.reactRouterServeConfig.resolvedId) {
509+
const rscOutDir = resolvedViteConfig.environments.rsc?.build?.outDir;
510+
invariant(rscOutDir, "RSC build directory config not found");
511+
const clientOutDir =
512+
resolvedViteConfig.environments.client?.build?.outDir;
513+
invariant(clientOutDir, "Client build directory config not found");
514+
const relativeAssetsBuildDirectory = Path.relative(
515+
rscOutDir,
516+
clientOutDir,
517+
);
518+
519+
let code = "";
520+
code += `export default {`;
521+
code += ` publicPath: ${JSON.stringify(resolvedViteConfig.base ?? "/")},`;
522+
code += ` assetsBuildDirectory: ${JSON.stringify(relativeAssetsBuildDirectory)},`;
523+
code += `};`;
524+
return code;
525+
}
526+
},
527+
},
495528
validatePluginOrder(),
496529
warnOnClientSourceMaps(),
497530
];
@@ -503,6 +536,7 @@ const virtual = {
503536
hmrRuntime: create("unstable_rsc/runtime"),
504537
basename: create("unstable_rsc/basename"),
505538
rscEntry: create("unstable_rsc/rsc-entry"),
539+
reactRouterServeConfig: create("unstable_rsc/react-router-serve-config"),
506540
};
507541

508542
function invalidateVirtualModules(viteDevServer: Vite.ViteDevServer) {

0 commit comments

Comments
 (0)