Skip to content

Commit 39daefd

Browse files
committed
Support Vite middleware mode in RSC Framework Mode
1 parent 35069d5 commit 39daefd

File tree

7 files changed

+243
-98
lines changed

7 files changed

+243
-98
lines changed

integration/helpers/vite.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,57 @@ export const EXPRESS_SERVER = (args: {
171171
base?: string;
172172
loadContext?: Record<string, unknown>;
173173
customLogic?: string;
174-
}) =>
175-
String.raw`
174+
templateName?: TemplateName;
175+
}) => {
176+
if (args.templateName?.includes("rsc")) {
177+
return String.raw`
178+
import { createRequestListener } from "@mjackson/node-fetch-server";
179+
import express from "express";
180+
181+
const viteDevServer =
182+
process.env.NODE_ENV === "production"
183+
? undefined
184+
: await import("vite").then(({ createServer }) =>
185+
createServer({
186+
server: {
187+
middlewareMode: true,
188+
},
189+
})
190+
);
191+
192+
const requestListener =
193+
viteDevServer
194+
? async (req, res) => {
195+
// In dev mode, ensure we load a fresh request handler every request
196+
const rscEntry = await viteDevServer.environments.rsc.runner.import(
197+
"virtual:react-router/unstable_rsc/rsc-entry",
198+
);
199+
return createRequestListener(rscEntry.default)(req, res);
200+
}
201+
// In production, get the static request handler from the build output
202+
: createRequestListener((await import("./build/server/index.js")).default);
203+
204+
const app = express();
205+
206+
if (viteDevServer) {
207+
app.use(viteDevServer.middlewares);
208+
} else {
209+
app.use(
210+
"/assets",
211+
express.static("build/client/assets", { immutable: true, maxAge: "1y" })
212+
);
213+
}
214+
215+
${args?.customLogic || ""}
216+
217+
app.all("*", requestListener);
218+
219+
const port = ${args.port};
220+
app.listen(port, () => console.log('http://localhost:' + port));
221+
`;
222+
}
223+
224+
return String.raw`
176225
import { createRequestHandler } from "@react-router/express";
177226
import express from "express";
178227
@@ -212,6 +261,7 @@ export const EXPRESS_SERVER = (args: {
212261
const port = ${args.port};
213262
app.listen(port, () => console.log('http://localhost:' + port));
214263
`;
264+
};
215265

216266
type FrameworkModeViteMajorTemplateName =
217267
| "vite-5-template"

integration/vite-basename-test.ts

Lines changed: 115 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -91,47 +91,89 @@ const customServerFile = ({
9191
port,
9292
base,
9393
basename,
94+
templateName,
9495
}: {
9596
port: number;
9697
base?: string;
9798
basename?: string;
99+
templateName: TemplateName;
98100
}) => {
99101
base = base ?? "/mybase/";
100102
basename = basename ?? base;
101103

102-
return js`
103-
import { createRequestHandler } from "@react-router/express";
104-
import express from "express";
105-
106-
const viteDevServer =
107-
process.env.NODE_ENV === "production"
108-
? undefined
109-
: await import("vite").then(({ createServer }) =>
110-
createServer({
111-
server: {
112-
middlewareMode: true,
113-
},
114-
})
115-
);
104+
if (templateName.includes("rsc")) {
105+
return js`
106+
import { createRequestListener } from "@mjackson/node-fetch-server";
107+
import express from "express";
108+
109+
const viteDevServer =
110+
process.env.NODE_ENV === "production"
111+
? undefined
112+
: await import("vite").then(({ createServer }) =>
113+
createServer({
114+
server: {
115+
middlewareMode: true,
116+
},
117+
})
118+
);
119+
120+
const requestListener = viteDevServer
121+
? async (req, res) => {
122+
// In dev mode, ensure we load a fresh request handler every request
123+
const rscEntry = await viteDevServer.environments.rsc.runner.import(
124+
"virtual:react-router/unstable_rsc/rsc-entry",
125+
);
126+
return createRequestListener(rscEntry.default)(req, res);
127+
}
128+
// In production, get the static request handler from the build output
129+
: createRequestListener((await import("./build/server/index.js")).default);
130+
131+
const app = express();
132+
app.use("${base}", viteDevServer?.middlewares || express.static("build/client"));
133+
app.all("${basename}*", requestListener);
134+
app.get("*", (_req, res) => {
135+
res.setHeader("content-type", "text/html")
136+
res.end('React Router app is at <a href="${basename}">${basename}</a>');
137+
});
116138
117-
const app = express();
118-
app.use("${base}", viteDevServer?.middlewares || express.static("build/client"));
119-
app.all(
120-
"${basename}*",
121-
createRequestHandler({
122-
build: viteDevServer
123-
? () => viteDevServer.ssrLoadModule("virtual:react-router/server-build")
124-
: await import("./build/server/index.js"),
125-
})
126-
);
127-
app.get("*", (_req, res) => {
128-
res.setHeader("content-type", "text/html")
129-
res.end('React Router app is at <a href="${basename}">${basename}</a>');
130-
});
139+
const port = ${port};
140+
app.listen(port, () => console.log('http://localhost:' + port));
141+
`;
142+
} else {
143+
return js`
144+
import { createRequestHandler } from "@react-router/express";
145+
import express from "express";
146+
147+
const viteDevServer =
148+
process.env.NODE_ENV === "production"
149+
? undefined
150+
: await import("vite").then(({ createServer }) =>
151+
createServer({
152+
server: {
153+
middlewareMode: true,
154+
},
155+
})
156+
);
157+
158+
const app = express();
159+
app.use("${base}", viteDevServer?.middlewares || express.static("build/client"));
160+
app.all(
161+
"${basename}*",
162+
createRequestHandler({
163+
build: viteDevServer
164+
? () => viteDevServer.ssrLoadModule("virtual:react-router/server-build")
165+
: await import("./build/server/index.js"),
166+
})
167+
);
168+
app.get("*", (_req, res) => {
169+
res.setHeader("content-type", "text/html")
170+
res.end('React Router app is at <a href="${basename}">${basename}</a>');
171+
});
131172
132-
const port = ${port};
133-
app.listen(port, () => console.log('http://localhost:' + port));
134-
`;
173+
const port = ${port};
174+
app.listen(port, () => console.log('http://localhost:' + port));
175+
`;
176+
}
135177
};
136178

137179
test.describe("Vite base + React Router basename", () => {
@@ -280,11 +322,6 @@ test.describe("Vite base + React Router basename", () => {
280322
});
281323

282324
test.describe("express dev", async () => {
283-
test.skip(
284-
templateName.includes("rsc"),
285-
"RSC Framework Mode doesn't support Vite middleware mode yet",
286-
);
287-
288325
let port: number;
289326
let cwd: string;
290327
let stop: () => void;
@@ -302,7 +339,7 @@ test.describe("Vite base + React Router basename", () => {
302339
cwd = await createProject(
303340
{
304341
...(await configFiles({ port, base, basename, templateName })),
305-
"server.mjs": customServerFile({ port, basename }),
342+
"server.mjs": customServerFile({ port, basename, templateName }),
306343
...sharedFiles,
307344
},
308345
templateName,
@@ -409,11 +446,6 @@ test.describe("Vite base + React Router basename", () => {
409446
});
410447

411448
test.describe("express build", async () => {
412-
test.skip(
413-
templateName.includes("rsc"),
414-
"Vite build test is already using Express",
415-
);
416-
417449
let port: number;
418450
let cwd: string;
419451
let stop: () => void;
@@ -435,6 +467,7 @@ test.describe("Vite base + React Router basename", () => {
435467
port,
436468
base,
437469
basename,
470+
templateName,
438471
}),
439472
...sharedFiles,
440473
},
@@ -480,29 +513,46 @@ test.describe("Vite base + React Router basename", () => {
480513
page,
481514
}) => {
482515
port = await getPort();
483-
cwd = await createProject({
484-
...(await configFiles({
485-
templateName,
486-
port,
487-
base: "https://cdn.example.com/assets/",
488-
basename: "/app/",
489-
})),
490-
// Slim server that only serves basename (route) requests from the React Router handler
491-
"server.mjs": String.raw`
492-
import { createRequestHandler } from "@react-router/express";
493-
import express from "express";
494-
495-
const app = express();
496-
app.all(
497-
"/app/*",
498-
createRequestHandler({ build: await import("./build/server/index.js") })
499-
);
500-
501-
const port = ${port};
502-
app.listen(port, () => console.log('http://localhost:' + port));
503-
`,
504-
...sharedFiles,
505-
});
516+
cwd = await createProject(
517+
{
518+
...(await configFiles({
519+
templateName,
520+
port,
521+
base: "https://cdn.example.com/assets/",
522+
basename: "/app/",
523+
})),
524+
// Slim server that only serves basename (route) requests from the React Router handler
525+
"server.mjs": templateName.includes("rsc")
526+
? String.raw`
527+
import { createRequestListener } from "@mjackson/node-fetch-server";
528+
import express from "express";
529+
530+
const app = express();
531+
app.all(
532+
"/app/*",
533+
createRequestListener((await import("./build/server/index.js")).default)
534+
);
535+
536+
const port = ${port};
537+
app.listen(port, () => console.log('http://localhost:' + port));
538+
`
539+
: String.raw`
540+
import { createRequestHandler } from "@react-router/express";
541+
import express from "express";
542+
543+
const app = express();
544+
app.all(
545+
"/app/*",
546+
createRequestHandler({ build: await import("./build/server/index.js") })
547+
);
548+
549+
const port = ${port};
550+
app.listen(port, () => console.log('http://localhost:' + port));
551+
`,
552+
...sharedFiles,
553+
},
554+
templateName,
555+
);
506556

507557
build({ cwd });
508558
stop = await customDev({

integration/vite-css-test.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,6 @@ test.describe("Vite CSS", () => {
292292
});
293293

294294
test.describe("express", async () => {
295-
test.fixme(
296-
templateName.includes("rsc"),
297-
"RSC Framework mode doesn't support Vite middleware mode yet",
298-
);
299-
300295
let port: number;
301296
let cwd: string;
302297
let stop: () => void;
@@ -313,7 +308,7 @@ test.describe("Vite CSS", () => {
313308
templateName,
314309
vanillaExtract: true,
315310
}),
316-
"server.mjs": EXPRESS_SERVER({ port }),
311+
"server.mjs": EXPRESS_SERVER({ port, templateName }),
317312
...files({ templateName }),
318313
},
319314
templateName,

integration/vite-hmr-hdr-test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,12 @@ test.describe("Vite HMR & HDR", () => {
6767
});
6868

6969
test("express", async ({ page, browserName, customDev }) => {
70-
test.skip(templateName.includes("rsc"), "RSC is not supported");
7170
let files: Files = async ({ port }) => ({
7271
"vite.config.js": await viteConfig.basic({ port, templateName }),
7372
"react-router.config.ts": reactRouterConfig({
7473
viteEnvironmentApi: templateName.includes("rsc"),
7574
}),
76-
"server.mjs": EXPRESS_SERVER({ port }),
75+
"server.mjs": EXPRESS_SERVER({ port, templateName }),
7776
"app/routes/_index.tsx": indexRoute,
7877
});
7978
let { cwd, port } = await customDev(files, templateName);

0 commit comments

Comments
 (0)