diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index bf50faa7dc..a285b483bd 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -171,8 +171,43 @@ export const EXPRESS_SERVER = (args: { base?: string; loadContext?: Record; customLogic?: string; -}) => - String.raw` + templateName?: TemplateName; +}) => { + if (args.templateName?.includes("rsc")) { + return String.raw` + import { createRequestListener } from "@mjackson/node-fetch-server"; + import express from "express"; + + const viteDevServer = + process.env.NODE_ENV === "production" + ? undefined + : await import("vite").then(({ createServer }) => + createServer({ + server: { + middlewareMode: true, + }, + }) + ); + const app = express(); + + ${args?.customLogic || ""} + + if (viteDevServer) { + app.use(viteDevServer.middlewares); + } else { + app.use( + "/assets", + express.static("build/client/assets", { immutable: true, maxAge: "1y" }) + ); + app.all("*", createRequestListener((await import("./build/server/index.js")).default)); + } + + const port = ${args.port}; + app.listen(port, () => console.log('http://localhost:' + port)); + `; + } + + return String.raw` import { createRequestHandler } from "@react-router/express"; import express from "express"; @@ -212,6 +247,7 @@ export const EXPRESS_SERVER = (args: { const port = ${args.port}; app.listen(port, () => console.log('http://localhost:' + port)); `; +}; type FrameworkModeViteMajorTemplateName = | "vite-5-template" diff --git a/integration/vite-basename-test.ts b/integration/vite-basename-test.ts index 96c2892296..18ad10124a 100644 --- a/integration/vite-basename-test.ts +++ b/integration/vite-basename-test.ts @@ -90,47 +90,85 @@ const customServerFile = ({ port, base, basename, + templateName, }: { port: number; base?: string; basename?: string; + templateName: TemplateName; }) => { base = base ?? "/mybase/"; basename = basename ?? base; - return js` - import { createRequestHandler } from "@react-router/express"; - import express from "express"; - - const viteDevServer = - process.env.NODE_ENV === "production" - ? undefined - : await import("vite").then(({ createServer }) => - createServer({ - server: { - middlewareMode: true, - }, - }) - ); + if (templateName.includes("rsc")) { + return js` + import { createRequestListener } from "@mjackson/node-fetch-server"; + import express from "express"; + + const viteDevServer = + process.env.NODE_ENV === "production" + ? undefined + : await import("vite").then(({ createServer }) => + createServer({ + server: { + middlewareMode: true, + }, + }) + ); + + const app = express(); + if (viteDevServer) { + app.use(viteDevServer.middlewares); + } else { + app.use("${base}", express.static("build/client")); + app.all( + "${basename}*", + createRequestListener((await import("./build/server/index.js")).default), + ); + } + app.get("*", (_req, res) => { + res.setHeader("content-type", "text/html") + res.end('React Router app is at ${basename}'); + }); - const app = express(); - app.use("${base}", viteDevServer?.middlewares || express.static("build/client")); - app.all( - "${basename}*", - createRequestHandler({ - build: viteDevServer - ? () => viteDevServer.ssrLoadModule("virtual:react-router/server-build") - : await import("./build/server/index.js"), - }) - ); - app.get("*", (_req, res) => { - res.setHeader("content-type", "text/html") - res.end('React Router app is at ${basename}'); - }); + const port = ${port}; + app.listen(port, () => console.log('http://localhost:' + port)); + `; + } else { + return js` + import { createRequestHandler } from "@react-router/express"; + import express from "express"; + + const viteDevServer = + process.env.NODE_ENV === "production" + ? undefined + : await import("vite").then(({ createServer }) => + createServer({ + server: { + middlewareMode: true, + }, + }) + ); + + const app = express(); + app.use("${base}", viteDevServer?.middlewares || express.static("build/client")); + app.all( + "${basename}*", + createRequestHandler({ + build: viteDevServer + ? () => viteDevServer.ssrLoadModule("virtual:react-router/server-build") + : await import("./build/server/index.js"), + }) + ); + app.get("*", (_req, res) => { + res.setHeader("content-type", "text/html") + res.end('React Router app is at ${basename}'); + }); - const port = ${port}; - app.listen(port, () => console.log('http://localhost:' + port)); - `; + const port = ${port}; + app.listen(port, () => console.log('http://localhost:' + port)); + `; + } }; test.describe("Vite base + React Router basename", () => { @@ -279,11 +317,6 @@ test.describe("Vite base + React Router basename", () => { }); test.describe("express dev", async () => { - test.skip( - templateName.includes("rsc"), - "RSC Framework Mode doesn't support Vite middleware mode yet", - ); - let port: number; let cwd: string; let stop: () => void; @@ -301,7 +334,7 @@ test.describe("Vite base + React Router basename", () => { cwd = await createProject( { ...(await configFiles({ port, base, basename, templateName })), - "server.mjs": customServerFile({ port, basename }), + "server.mjs": customServerFile({ port, basename, templateName }), ...sharedFiles, }, templateName, @@ -408,11 +441,6 @@ test.describe("Vite base + React Router basename", () => { }); test.describe("express build", async () => { - test.skip( - templateName.includes("rsc"), - "Vite build test is already using Express", - ); - let port: number; let cwd: string; let stop: () => void; @@ -434,6 +462,7 @@ test.describe("Vite base + React Router basename", () => { port, base, basename, + templateName, }), ...sharedFiles, }, @@ -479,29 +508,46 @@ test.describe("Vite base + React Router basename", () => { page, }) => { port = await getPort(); - cwd = await createProject({ - ...(await configFiles({ - templateName, - port, - base: "https://cdn.example.com/assets/", - basename: "/app/", - })), - // Slim server that only serves basename (route) requests from the React Router handler - "server.mjs": String.raw` - import { createRequestHandler } from "@react-router/express"; - import express from "express"; - - const app = express(); - app.all( - "/app/*", - createRequestHandler({ build: await import("./build/server/index.js") }) - ); - - const port = ${port}; - app.listen(port, () => console.log('http://localhost:' + port)); - `, - ...sharedFiles, - }); + cwd = await createProject( + { + ...(await configFiles({ + templateName, + port, + base: "https://cdn.example.com/assets/", + basename: "/app/", + })), + // Slim server that only serves basename (route) requests from the React Router handler + "server.mjs": templateName.includes("rsc") + ? String.raw` + import { createRequestListener } from "@mjackson/node-fetch-server"; + import express from "express"; + + const app = express(); + app.all( + "/app/*", + createRequestListener((await import("./build/server/index.js")).default) + ); + + const port = ${port}; + app.listen(port, () => console.log('http://localhost:' + port)); + ` + : String.raw` + import { createRequestHandler } from "@react-router/express"; + import express from "express"; + + const app = express(); + app.all( + "/app/*", + createRequestHandler({ build: await import("./build/server/index.js") }) + ); + + const port = ${port}; + app.listen(port, () => console.log('http://localhost:' + port)); + `, + ...sharedFiles, + }, + templateName, + ); build({ cwd }); stop = await customDev({ diff --git a/integration/vite-css-test.ts b/integration/vite-css-test.ts index daa64da676..d6e9667a6e 100644 --- a/integration/vite-css-test.ts +++ b/integration/vite-css-test.ts @@ -292,11 +292,6 @@ test.describe("Vite CSS", () => { }); test.describe("express", async () => { - test.fixme( - templateName.includes("rsc"), - "RSC Framework mode doesn't support Vite middleware mode yet", - ); - let port: number; let cwd: string; let stop: () => void; @@ -313,7 +308,7 @@ test.describe("Vite CSS", () => { templateName, vanillaExtract: true, }), - "server.mjs": EXPRESS_SERVER({ port }), + "server.mjs": EXPRESS_SERVER({ port, templateName }), ...files({ templateName }), }, templateName, diff --git a/integration/vite-hmr-hdr-test.ts b/integration/vite-hmr-hdr-test.ts index 4c5e795e8f..59bab4ff2a 100644 --- a/integration/vite-hmr-hdr-test.ts +++ b/integration/vite-hmr-hdr-test.ts @@ -63,10 +63,9 @@ test.describe("Vite HMR & HDR", () => { }); test("express", async ({ page, browserName, customDev }) => { - test.skip(templateName.includes("rsc"), "RSC is not supported"); let files: Files = async ({ port }) => ({ "vite.config.js": await viteConfig.basic({ port, templateName }), - "server.mjs": EXPRESS_SERVER({ port }), + "server.mjs": EXPRESS_SERVER({ port, templateName }), "app/routes/_index.tsx": indexRoute, }); let { cwd, port } = await customDev(files, templateName); diff --git a/packages/react-router-dev/vite/rsc/plugin.ts b/packages/react-router-dev/vite/rsc/plugin.ts index 2b7a6f0aa0..9da90a9415 100644 --- a/packages/react-router-dev/vite/rsc/plugin.ts +++ b/packages/react-router-dev/vite/rsc/plugin.ts @@ -31,6 +31,8 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { let routeIdByFile: Map | undefined; let logger: Vite.Logger; + const defaultEntries = getDefaultEntries(); + return [ { name: "react-router/rsc", @@ -66,8 +68,6 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { prefix: "[react-router]", }); - const rscEntries = getRscEntries(); - return { resolve: { dedupe: [ @@ -106,19 +106,19 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { environments: { client: { build: { - rollupOptions: { input: { index: rscEntries.client } }, + rollupOptions: { input: { index: virtual.clientEntry.id } }, outDir: join(config.buildDirectory, "client"), }, }, rsc: { build: { - rollupOptions: { input: { index: rscEntries.rsc } }, + rollupOptions: { input: { index: virtual.rscEntry.id } }, outDir: join(config.buildDirectory, "server"), }, }, ssr: { build: { - rollupOptions: { input: { index: rscEntries.ssr } }, + rollupOptions: { input: { index: virtual.ssrEntry.id } }, outDir: join(config.buildDirectory, "server/__ssr_build"), }, }, @@ -221,6 +221,15 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { (await typegenWatcherPromise)?.close(); }, }, + + { + name: "react-router/rsc/virtual-entries", + resolveId(id) { + if (id === virtual.rscEntry.id) return defaultEntries.rsc; + if (id === virtual.ssrEntry.id) return defaultEntries.ssr; + if (id === virtual.clientEntry.id) return defaultEntries.client; + }, + }, { name: "react-router/rsc/virtual-route-config", resolveId(id) { @@ -423,6 +432,9 @@ const virtual = { injectHmrRuntime: create("unstable_rsc/inject-hmr-runtime"), hmrRuntime: create("unstable_rsc/runtime"), basename: create("unstable_rsc/basename"), + rscEntry: create("unstable_rsc/rsc-entry"), + ssrEntry: create("unstable_rsc/ssr-entry"), + clientEntry: create("unstable_rsc/client-entry"), }; function invalidateVirtualModules(viteDevServer: Vite.ViteDevServer) { @@ -440,24 +452,6 @@ function getRootDirectory(viteUserConfig: Vite.UserConfig) { return viteUserConfig.root ?? process.env.REACT_ROUTER_ROOT ?? process.cwd(); } -function getRscEntries(): { - client: string; - rsc: string; - ssr: string; -} { - const entriesDir = join( - getDevPackageRoot(), - "dist", - "config", - "default-rsc-entries", - ); - return { - client: join(entriesDir, "entry.client.tsx"), - rsc: join(entriesDir, "entry.rsc.tsx"), - ssr: join(entriesDir, "entry.ssr.tsx"), - }; -} - function getDevPackageRoot(): string { const currentDir = dirname(__dirname); let dir = currentDir; @@ -473,6 +467,20 @@ function getDevPackageRoot(): string { throw new Error("Could not find package.json"); } +function getDefaultEntries() { + const defaultEntriesDir = join( + getDevPackageRoot(), + "dist", + "config", + "default-rsc-entries", + ); + return { + rsc: join(defaultEntriesDir, "entry.rsc.tsx"), + ssr: join(defaultEntriesDir, "entry.ssr.tsx"), + client: join(defaultEntriesDir, "entry.client.tsx"), + }; +} + function getModulesWithImporters( modules: Vite.EnvironmentModuleNode[], ): Set { diff --git a/playground/rsc-vite-framework/package.json b/playground/rsc-vite-framework/package.json index 4e02845b4d..35916b2b04 100644 --- a/playground/rsc-vite-framework/package.json +++ b/playground/rsc-vite-framework/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "react-router dev", + "dev:vite-middleware": "node start-vite-middleware.js", "build": "react-router build", "start": "cross-env NODE_ENV=production node start.js", "typecheck": "react-router typegen && tsc" diff --git a/playground/rsc-vite-framework/start-vite-middleware.js b/playground/rsc-vite-framework/start-vite-middleware.js new file mode 100644 index 0000000000..33f48ef2c9 --- /dev/null +++ b/playground/rsc-vite-framework/start-vite-middleware.js @@ -0,0 +1,33 @@ +import { createRequestListener } from "@mjackson/node-fetch-server"; +import express from "express"; + +const viteDevServer = + process.env.NODE_ENV === "production" + ? undefined + : await import("vite").then(({ createServer }) => + createServer({ + server: { + middlewareMode: true, + }, + }), + ); + +const app = express(); + +if (viteDevServer) { + app.use(viteDevServer.middlewares); +} else { + app.use( + "/assets", + express.static("build/client/assets", { immutable: true, maxAge: "1y" }), + ); + app.use(express.static("build/client")); + app.all( + "*", + createRequestListener((await import("./build/server/index.js")).default), + ); +} + +const port = process.env.PORT || 3000; +app.listen(port); +console.log(`Server listening on port ${port} (http://localhost:${port})`);