Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/red-trains-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@react-router/dev": patch
"react-router": patch
---

support prerender / ssr options for RSC and gate unstable_previewServerPrerendering behind Vite v7
2 changes: 1 addition & 1 deletion .github/workflows/shared-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,5 @@ jobs:
run: npx playwright install --with-deps ${{ matrix.browser }}

- name: 👀 Run Integration Tests ${{ matrix.browser }}
run: "pnpm test:integration --project=${{ matrix.browser }}"
run: "pnpm test:integration --project=${{ matrix.browser }} prerender"
timeout-minutes: ${{inputs.timeout}}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"typescript": "catalog:",
"vite": "^6.3.0",
"vite": "catalog:vite-7",
"wrangler": "^4.23.0"
},
"engines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
import { defineConfig } from "vite";

export default defineConfig({
// @ts-expect-error - vite versions
plugins: [cloudflareDevProxy(), reactRouter()],
});
111 changes: 101 additions & 10 deletions integration/helpers/create-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { JsonObject } from "type-fest";

import {
type ServerBuild,
type unstable_RSCPayload as RSCPayload,
createRequestHandler,
UNSAFE_ServerMode as ServerMode,
UNSAFE_decodeViaTurboStream as decodeViaTurboStream,
Expand All @@ -25,6 +26,61 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
const root = path.join(__dirname, "../..");
const TMP_DIR = path.join(root, ".tmp", "integration");

(global as any).__webpack_require__ ??= () => {
return {};
};

(global as any).__non_webpack_require__ ??= (p: string) => {
return require(p);
};

const decodeOptions = {
serverConsumerManifest: {
moduleMap: new Proxy(
{},
{
get() {
return new Proxy(
{},
{
get(_, p) {
return {
name: "",
id: "",
chunks: [],
};
},
},
);
},
},
),
serverModuleMap: new Proxy({}, {}),
moduleLoading: new Proxy({}, {}),
},
};

function normalizeRSCData(payload: RSCPayload) {
if (payload.type !== "render") return payload;
let data: Record<string, any> = {};
for (const [key, value] of Object.entries(payload.loaderData)) {
data[key] ??= {
data: value,
};
}
for (const [key, value] of Object.entries(payload.actionData ?? {})) {
data[key] ??= {
data: value,
};
}
for (const [key, value] of Object.entries(payload.errors ?? {})) {
data[key] ??= {
error: value,
};
}
return data;
}

export async function spawnTestServer({
command,
regex,
Expand Down Expand Up @@ -112,6 +168,7 @@ export async function createFixture(init: FixtureInit, mode?: ServerMode) {
let buildPath = url.pathToFileURL(
path.join(projectDir, "build/server/index.js"),
).href;
let isRsc = templateName.includes("rsc");

let getBrowserAsset = async (asset: string) => {
return readFile(
Expand Down Expand Up @@ -178,13 +235,28 @@ export async function createFixture(init: FixtureInit, mode?: ServerMode) {
return new Response(data);
},
async requestSingleFetchData(href: string) {
if (isRsc) {
if (href === "/_root.data") {
href = "/_.data";
}
href = href.replace(/\.data$/, ".rsc");
}

let data = readFileSync(path.join(projectDir, "build/client", href));
let stream = createReadableStreamFromReadable(Readable.from(data));
let createFromReadableStream = await import(
// @ts-expect-error - no types
"react-server-dom-webpack/client.edge"
).then((m) => m.createFromReadableStream);
return {
status: 200,
statusText: "OK",
headers: new Headers(),
data: (await decodeViaTurboStream(stream, global)).value,
data: isRsc
? normalizeRSCData(
await createFromReadableStream(stream, decodeOptions),
)
: (await decodeViaTurboStream(stream, global)).value,
};
},
postDocument: () => {
Expand Down Expand Up @@ -228,17 +300,33 @@ export async function createFixture(init: FixtureInit, mode?: ServerMode) {
};

let requestSingleFetchData = async (href: string, init?: RequestInit) => {
if (isRsc) {
if (href === "/_root.data") {
href = "/_.data";
}
href = href.replace(/\.data$/, ".rsc");
}

init = init || {};
init.signal = init.signal || new AbortController().signal;
let url = new URL(href, "test://test");
let request = new Request(url.toString(), init);
let response = await handler(request);
let createFromReadableStream = await import(
// @ts-expect-error - no types
"react-server-dom-webpack/client.edge"
).then((m) => m.createFromReadableStream);

return {
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.body
? (await decodeViaTurboStream(response.body!, global)).value
? isRsc
? normalizeRSCData(
await createFromReadableStream(response.body, decodeOptions),
)
: (await decodeViaTurboStream(response.body!, global)).value
: null,
};
};
Expand Down Expand Up @@ -481,7 +569,7 @@ export async function createFixtureProject(
projectDir,
init.buildStdio,
mode,
templateName.includes("rsc"),
templateName.includes("rsc") && !templateName.includes("framework"),
);

return projectDir;
Expand All @@ -491,7 +579,7 @@ function reactRouterBuild(
projectDir: string,
buildStdio?: Writable,
mode?: ServerMode,
isRsc?: boolean,
isViteBuildCommand?: boolean,
) {
// We have a "require" instead of a dynamic import in readConfig gated
// behind mode === ServerMode.Test to make jest happy, but that doesn't
Expand All @@ -503,7 +591,10 @@ function reactRouterBuild(
let reactRouterBin = "node_modules/@react-router/dev/dist/cli/index.js";
let viteBin = "node_modules/vite/dist/node/cli.js";

let buildArgs: string[] = [isRsc ? viteBin : reactRouterBin, "build"];
let buildArgs: string[] = [
isViteBuildCommand ? viteBin : reactRouterBin,
"build",
];

let buildSpawn = spawnSync("node", buildArgs, {
cwd: projectDir,
Expand All @@ -517,11 +608,11 @@ function reactRouterBuild(
});

// These logs are helpful for debugging. Remove comments if needed.
// console.log("spawning node " + buildArgs.join(" ") + ":\n");
// console.log(" STDOUT:");
// console.log(" " + buildSpawn.stdout.toString("utf-8"));
// console.log(" STDERR:");
// console.log(" " + buildSpawn.stderr.toString("utf-8"));
console.log("spawning node " + buildArgs.join(" ") + ":\n");
console.log(" STDOUT:");
console.log(" " + buildSpawn.stdout.toString("utf-8"));
console.log(" STDERR:");
console.log(" " + buildSpawn.stderr.toString("utf-8"));

if (buildStdio) {
buildStdio.write(buildSpawn.stdout.toString("utf-8"));
Expand Down
6 changes: 4 additions & 2 deletions integration/helpers/rsc-vite-framework/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --app",
"build": "vite build",
"start": "cross-env NODE_ENV=production node start.js",
"typecheck": "react-router typegen && tsc"
},
"devDependencies": {
"@mdx-js/rollup": "^3.1.0",
"@react-router/dev": "workspace:*",
"@react-router/fs-routes": "workspace:*",
"@react-router/node": "workspace:*",
"@types/express": "^5.0.0",
"@types/node": "^22.13.1",
"@types/react": "catalog:react-canary",
Expand All @@ -24,12 +25,13 @@
"@vitejs/plugin-rsc": "catalog:",
"cross-env": "^7.0.3",
"typescript": "catalog:",
"vite": "^6.3.0",
"vite": "catalog:vite-7",
"vite-env-only": "^3.0.1",
"vite-tsconfig-paths": "^4.2.1"
},
"dependencies": {
"@mjackson/node-fetch-server": "0.6.1",
"@react-router/node": "workspace:*",
"@react-router/serve": "workspace:*",
"compression": "^1.8.1",
"express": "^4.21.2",
Expand Down
2 changes: 1 addition & 1 deletion integration/helpers/vite-7-beta-template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@types/react-dom": "catalog:",
"eslint": "^8.38.0",
"typescript": "catalog:",
"vite": "7.0.0-beta.0",
"vite": "catalog:vite-7",
"vite-env-only": "^3.0.1",
"vite-tsconfig-paths": "^4.2.1"
},
Expand Down
1 change: 1 addition & 0 deletions integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"react": "catalog:",
"react-dom": "catalog:",
"react-router": "workspace:*",
"react-server-dom-webpack": "catalog:",
"semver": "^7.7.2",
"serialize-javascript": "^6.0.1",
"shelljs": "^0.8.5",
Expand Down
2 changes: 1 addition & 1 deletion integration/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const config: PlaywrightTestConfig = {
},
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 3 : 0,
reporter: process.env.CI ? "dot" : [["html", { open: "never" }]],
reporter: "line",
use: { actionTimeout: 0 },

projects: [
Expand Down
Loading
Loading