Skip to content

Commit 0b78922

Browse files
committed
Merge branch 'dev' into markdalgleish/env-api-critical-css
2 parents 1b45426 + 21aff33 commit 0b78922

File tree

28 files changed

+299
-75
lines changed

28 files changed

+299
-75
lines changed

.changeset/hip-jars-hunt.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/express": patch
3+
---
4+
5+
Update `express` `peerDependency` to include v5 (https://github.com/remix-run/react-router/pull/13064)

.changeset/modern-forks-ring.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Stub all routes except root in "SPA Mode" server builds to avoid issues when route modules or their dependencies import non-SSR-friendly modules

.changeset/new-houses-hug.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"@react-router/dev": patch
33
---
44

5-
Fix `future.unstable_viteEnvironmentApi` when the `ssr` environment has been configured by another plugin to be a custom `Vite.DevEnvironment` rather than a `Vite.RunnableDevEnvironment`
5+
Fix errors with `future.unstable_viteEnvironmentApi` when the `ssr` environment has been configured by another plugin to be a custom `Vite.DevEnvironment` rather than the default `Vite.RunnableDevEnvironment`

.changeset/sharp-sloths-swim.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Fix dev server when using HTTPS by stripping HTTP/2 pseudo headers from dev server requests

.changeset/short-comics-fly.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Fix single fetch `_root.data` requests when a `basename` is used

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ node_modules/
3030
.eslintcache
3131
.tmp
3232
tsup.config.bundled_*.mjs
33+
build.utils.d.ts
3334
/.env
3435
/NOTES.md
3536

contributors.yml

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- akamfoad
1717
- alany411
1818
- alberto
19+
- Aleuck
1920
- alexandernanberg
2021
- alexanderson1993
2122
- alexlbr
@@ -264,6 +265,7 @@
264265
- robbtraister
265266
- RobHannay
266267
- robinvdvleuten
268+
- rossipedia
267269
- rtmann
268270
- rtzll
269271
- rubeonline

integration/single-fetch-test.ts

+43-4
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ const files = {
4242
<Links />
4343
</head>
4444
<body>
45-
<Link to="/">Home</Link><br/>
46-
<Link to="/data">Data</Link><br/>
47-
<Link to="/a/b/c">/a/b/c</Link><br/>
45+
<Link to="/">Go to Home</Link><br/>
46+
<Link to="/data">Go to Data</Link><br/>
47+
<Link to="/a/b/c">Go to /a/b/c</Link><br/>
4848
<Form method="post" action="/data">
4949
<button type="submit" name="key" value="value">
5050
Submit
@@ -101,7 +101,7 @@ const files = {
101101
let actionData = useActionData();
102102
return (
103103
<>
104-
<h1 id="heading">Data</h1>
104+
<h1 id="heading">Data Route</h1>
105105
<p id="message">{data.message}</p>
106106
<p id="date">{data.date.toISOString()}</p>
107107
{actionData ? <p id="action-data">{actionData.key}</p> : null}
@@ -1375,6 +1375,45 @@ test.describe("single-fetch", () => {
13751375
expect(await app.getHtml("#target")).toContain("Target");
13761376
});
13771377

1378+
test("supports a basename", async ({ page }) => {
1379+
let fixture = await createFixture({
1380+
files: {
1381+
"vite.config.ts": js`
1382+
import { reactRouter } from "@react-router/dev/vite";
1383+
1384+
export default {
1385+
base: "/base/",
1386+
plugins: [reactRouter()]
1387+
}
1388+
`,
1389+
"react-router.config.ts": reactRouterConfig({
1390+
basename: "/base/",
1391+
}),
1392+
...files,
1393+
},
1394+
useReactRouterServe: true,
1395+
});
1396+
1397+
let appFixture = await createAppFixture(fixture);
1398+
1399+
let requests: string[] = [];
1400+
page.on("request", (req) => {
1401+
let url = new URL(req.url());
1402+
if (url.pathname.endsWith(".data")) {
1403+
requests.push(url.pathname + url.search);
1404+
}
1405+
});
1406+
1407+
let app = new PlaywrightFixture(appFixture, page);
1408+
await app.goto("/base/");
1409+
await app.clickLink("/base/data");
1410+
await expect(page.getByText("Data Route")).toBeVisible();
1411+
await app.clickLink("/base/");
1412+
await expect(page.getByText("Index")).toBeVisible();
1413+
1414+
expect(requests).toEqual(["/base/data.data", "/base/_root.data"]);
1415+
});
1416+
13781417
test("processes redirects when a basename is present", async ({ page }) => {
13791418
let fixture = await createFixture({
13801419
files: {

integration/vite-spa-mode-test.ts

+112-1
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ test.describe("SPA Mode", () => {
905905
appFixture.close();
906906
});
907907

908-
test("renders the root HydrateFallback initially with access to the root loader data", async ({}) => {
908+
test("renders the root HydrateFallback initially with access to the root loader data", async () => {
909909
let res = await fixture.requestDocument("/");
910910
let html = await res.text();
911911
expect(html).toMatch('<h1 data-loading="true">Loading SPA...</h1>');
@@ -1067,4 +1067,115 @@ test.describe("SPA Mode", () => {
10671067
});
10681068
});
10691069
});
1070+
1071+
test("only imports the root route in the server build when SSRing index.html", async ({
1072+
page,
1073+
}) => {
1074+
let fixture = await createFixture({
1075+
spaMode: true,
1076+
files: {
1077+
"react-router.config.ts": reactRouterConfig({
1078+
ssr: false,
1079+
}),
1080+
"vite.config.ts": js`
1081+
import { defineConfig } from "vite";
1082+
import { reactRouter } from "@react-router/dev/vite";
1083+
1084+
export default defineConfig({
1085+
build: { manifest: true },
1086+
plugins: [reactRouter()],
1087+
});
1088+
`,
1089+
"app/routeImportTracker.ts": js`
1090+
// this is kinda silly, but this way we can track imports
1091+
// that happen during SSR and during CSR
1092+
export async function logImport(url: string) {
1093+
try {
1094+
const fs = await import("node:fs");
1095+
const path = await import("node:path");
1096+
fs.appendFileSync(path.join(process.cwd(), "ssr-route-imports.txt"), url + "\n");
1097+
}
1098+
catch (e) {
1099+
(window.csrRouteImports ??= []).push(url);
1100+
}
1101+
}
1102+
`,
1103+
"app/root.tsx": js`
1104+
import { Links, Meta, Outlet, Scripts } from "react-router";
1105+
import { logImport } from "./routeImportTracker";
1106+
logImport("app/root.tsx");
1107+
1108+
export default function Root() {
1109+
return (
1110+
<html lang="en">
1111+
<head>
1112+
<Meta />
1113+
<Links />
1114+
</head>
1115+
<body>
1116+
hello world
1117+
<Outlet />
1118+
<Scripts />
1119+
</body>
1120+
</html>
1121+
);
1122+
}
1123+
`,
1124+
"app/routes/_index.tsx": js`
1125+
import { logImport } from "../routeImportTracker";
1126+
logImport("app/routes/_index.tsx");
1127+
1128+
// This should not cause an error on SSR because the module is not loaded
1129+
console.log(window);
1130+
1131+
export default function Component() {
1132+
return "index";
1133+
}
1134+
`,
1135+
"app/routes/about.tsx": js`
1136+
import * as React from "react";
1137+
import { logImport } from "../routeImportTracker";
1138+
logImport("app/routes/about.tsx");
1139+
1140+
// This should not cause an error on SSR because the module is not loaded
1141+
console.log(window);
1142+
1143+
export default function Component() {
1144+
const [mounted, setMounted] = React.useState(false);
1145+
React.useEffect(() => setMounted(true), []);
1146+
1147+
return (
1148+
<>
1149+
{!mounted ? <span>Unmounted</span> : <span data-mounted>Mounted</span>}
1150+
</>
1151+
);
1152+
}
1153+
`,
1154+
},
1155+
});
1156+
1157+
let importedRoutes = (
1158+
await fs.promises.readFile(
1159+
path.join(fixture.projectDir, "ssr-route-imports.txt"),
1160+
"utf-8"
1161+
)
1162+
)
1163+
.trim()
1164+
.split("\n");
1165+
expect(importedRoutes).toStrictEqual([
1166+
"app/root.tsx",
1167+
// we should not have imported app/routes/_index.tsx
1168+
// we should not have imported app/routes/about.tsx
1169+
]);
1170+
1171+
appFixture = await createAppFixture(fixture);
1172+
let app = new PlaywrightFixture(appFixture, page);
1173+
await app.goto("/about");
1174+
await page.waitForSelector("[data-mounted]");
1175+
// @ts-expect-error
1176+
expect(await page.evaluate(() => window.csrRouteImports)).toStrictEqual([
1177+
"app/root.tsx",
1178+
"app/routes/about.tsx",
1179+
]);
1180+
});
10701181
});

packages/create-react-router/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"./package.json": "./package.json"
2020
},
2121
"scripts": {
22-
"build": "wireit"
22+
"build": "wireit",
23+
"typecheck": "tsc"
2324
},
2425
"wireit": {
2526
"build": {

packages/react-router-architect/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"./package.json": "./package.json"
3333
},
3434
"scripts": {
35-
"build": "wireit"
35+
"build": "wireit",
36+
"typecheck": "tsc"
3637
},
3738
"wireit": {
3839
"build": {

packages/react-router-cloudflare/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"./package.json": "./package.json"
2828
},
2929
"scripts": {
30-
"build": "wireit"
30+
"build": "wireit",
31+
"typecheck": "tsc"
3132
},
3233
"wireit": {
3334
"build": {

packages/react-router-dev/vite/node-adapter.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { IncomingHttpHeaders, ServerResponse } from "node:http";
1+
import type { IncomingMessage, ServerResponse } from "node:http";
22
import { once } from "node:events";
33
import { Readable } from "node:stream";
44
import { splitCookiesString } from "set-cookie-parser";
@@ -12,7 +12,20 @@ export type NodeRequestHandler = (
1212
res: ServerResponse
1313
) => Promise<void>;
1414

15-
function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers {
15+
function fromNodeHeaders(nodeReq: IncomingMessage): Headers {
16+
let nodeHeaders = nodeReq.headers;
17+
18+
if (nodeReq.httpVersionMajor >= 2) {
19+
nodeHeaders = { ...nodeHeaders };
20+
if (nodeHeaders[":authority"]) {
21+
nodeHeaders.host = nodeHeaders[":authority"] as string;
22+
}
23+
delete nodeHeaders[":authority"];
24+
delete nodeHeaders[":method"];
25+
delete nodeHeaders[":path"];
26+
delete nodeHeaders[":scheme"];
27+
}
28+
1629
let headers = new Headers();
1730

1831
for (let [key, values] of Object.entries(nodeHeaders)) {
@@ -50,7 +63,7 @@ export function fromNodeRequest(
5063
let controller: AbortController | null = new AbortController();
5164
let init: RequestInit = {
5265
method: nodeReq.method,
53-
headers: fromNodeHeaders(nodeReq.headers),
66+
headers: fromNodeHeaders(nodeReq),
5467
signal: controller.signal,
5568
};
5669

0 commit comments

Comments
 (0)