Skip to content

Commit c838b33

Browse files
Add integration test for vite-plugin-cloudflare (#13099)
1 parent 61af7e5 commit c838b33

40 files changed

+1386
-339
lines changed

Diff for: integration/helpers/vite-cloudflare-template/.gitignore renamed to integration/helpers/cloudflare-dev-proxy-template/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ node_modules
22

33
/build
44
.env
5+
.react-router

Diff for: integration/helpers/vite-cloudflare-template/package.json renamed to integration/helpers/cloudflare-dev-proxy-template/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "integration-vite-cloudflare-template",
2+
"name": "integration-cloudflare-dev-proxy-template",
33
"version": "0.0.0",
44
"private": true,
55
"sideEffects": false,
@@ -13,21 +13,21 @@
1313
"dependencies": {
1414
"@react-router/cloudflare": "workspace:*",
1515
"isbot": "^4.1.0",
16-
"miniflare": "^3.20231030.4",
16+
"miniflare": "^3.20250214.0",
1717
"react": "^18.2.0",
1818
"react-dom": "^18.2.0",
1919
"react-router": "workspace:*"
2020
},
2121
"devDependencies": {
22-
"@cloudflare/workers-types": "^4.20230518.0",
22+
"@cloudflare/workers-types": "^4.20250214.0",
2323
"@react-router/dev": "workspace:*",
2424
"@react-router/fs-routes": "workspace:*",
2525
"@react-router/remix-routes-option-adapter": "workspace:*",
2626
"@types/react": "^18.2.20",
2727
"@types/react-dom": "^18.2.7",
2828
"typescript": "^5.1.6",
29-
"vite": "^6.0.0",
30-
"wrangler": "^3.28.2"
29+
"vite": "^6.1.0",
30+
"wrangler": "^3.109.2"
3131
},
3232
"engines": {
3333
"node": ">=20.0.0"

Diff for: integration/helpers/vite-6-template/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"@types/react-dom": "^18.2.7",
3232
"eslint": "^8.38.0",
3333
"typescript": "^5.1.6",
34-
"vite": "^6.0.0",
34+
"vite": "^6.1.0",
3535
"vite-env-only": "^3.0.1",
3636
"vite-tsconfig-paths": "^4.2.1"
3737
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
.env
6+
.react-router
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { AppLoadContext, EntryContext } from "react-router";
2+
import { ServerRouter } from "react-router";
3+
import { isbot } from "isbot";
4+
import { renderToReadableStream } from "react-dom/server";
5+
6+
export default async function handleRequest(
7+
request: Request,
8+
responseStatusCode: number,
9+
responseHeaders: Headers,
10+
routerContext: EntryContext,
11+
_loadContext: AppLoadContext
12+
) {
13+
let shellRendered = false;
14+
const userAgent = request.headers.get("user-agent");
15+
16+
const body = await renderToReadableStream(
17+
<ServerRouter context={routerContext} url={request.url} />,
18+
{
19+
onError(error: unknown) {
20+
responseStatusCode = 500;
21+
// Log streaming rendering errors from inside the shell. Don't log
22+
// errors encountered during initial shell rendering since they'll
23+
// reject and get logged in handleDocumentRequest.
24+
if (shellRendered) {
25+
console.error(error);
26+
}
27+
},
28+
}
29+
);
30+
shellRendered = true;
31+
32+
// Ensure requests from bots and SPA Mode renders wait for all content to load before responding
33+
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
34+
if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
35+
await body.allReady;
36+
}
37+
38+
responseHeaders.set("Content-Type", "text/html");
39+
return new Response(body, {
40+
headers: responseHeaders,
41+
status: responseStatusCode,
42+
});
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
2+
3+
export default function App() {
4+
return (
5+
<html lang="en">
6+
<head>
7+
<meta charSet="utf-8" />
8+
<meta name="viewport" content="width=device-width, initial-scale=1" />
9+
<Meta />
10+
<Links />
11+
</head>
12+
<body>
13+
<Outlet />
14+
<ScrollRestoration />
15+
<Scripts />
16+
</body>
17+
</html>
18+
);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { type RouteConfig } from "@react-router/dev/routes";
2+
import { flatRoutes } from "@react-router/fs-routes";
3+
4+
export default flatRoutes() satisfies RouteConfig;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { MetaFunction } from "react-router";
2+
3+
export const meta: MetaFunction = () => {
4+
return [
5+
{ title: "New React Router App" },
6+
{ name: "description", content: "Welcome to React Router!" },
7+
];
8+
};
9+
10+
export default function Index() {
11+
return (
12+
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
13+
<h1>Welcome to React Router</h1>
14+
</div>
15+
);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/// <reference types="@react-router/node" />
2+
/// <reference types="vite/client" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "integration-vite-plugin-cloudflare-template",
3+
"version": "0.0.0",
4+
"private": true,
5+
"sideEffects": false,
6+
"type": "module",
7+
"scripts": {
8+
"dev": "react-router dev",
9+
"build": "react-router build",
10+
"typecheck": "react-router typegen && tsc"
11+
},
12+
"dependencies": {
13+
"express": "^4.19.2",
14+
"isbot": "^5.1.11",
15+
"react": "^18.2.0",
16+
"react-dom": "^18.2.0",
17+
"react-router": "workspace:*",
18+
"serialize-javascript": "^6.0.1"
19+
},
20+
"devDependencies": {
21+
"@cloudflare/vite-plugin": "^0.1.1",
22+
"@cloudflare/workers-types": "^4.20250214.0",
23+
"@react-router/dev": "workspace:*",
24+
"@react-router/fs-routes": "workspace:*",
25+
"@types/node": "^20.0.0",
26+
"@types/react": "^18.2.20",
27+
"@types/react-dom": "^18.2.7",
28+
"eslint": "^8.38.0",
29+
"typescript": "^5.1.6",
30+
"vite": "^6.1.0",
31+
"vite-tsconfig-paths": "^4.2.1",
32+
"wrangler": "^3.109.2"
33+
},
34+
"engines": {
35+
"node": ">=20.0.0"
36+
}
37+
}
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Config } from "@react-router/dev/config";
2+
3+
export default {
4+
future: {
5+
unstable_viteEnvironmentApi: true,
6+
},
7+
} satisfies Config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"include": [
4+
".react-router/types/**/*",
5+
"app/**/*",
6+
"app/**/.server/**/*",
7+
"app/**/.client/**/*",
8+
"workers/**/*",
9+
"worker-configuration.d.ts"
10+
],
11+
"compilerOptions": {
12+
"composite": true,
13+
"strict": true,
14+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
15+
"types": ["@cloudflare/workers-types", "vite/client"],
16+
"target": "ES2022",
17+
"module": "ES2022",
18+
"moduleResolution": "bundler",
19+
"jsx": "react-jsx",
20+
"baseUrl": ".",
21+
"rootDirs": [".", "./.react-router/types"],
22+
"paths": {
23+
"~/*": ["./app/*"]
24+
},
25+
"esModuleInterop": true,
26+
"resolveJsonModule": true
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"files": [],
3+
"references": [
4+
{ "path": "./tsconfig.node.json" },
5+
{ "path": "./tsconfig.cloudflare.json" }
6+
],
7+
"compilerOptions": {
8+
"checkJs": true,
9+
"verbatimModuleSyntax": true,
10+
"skipLibCheck": true,
11+
"strict": true,
12+
"noEmit": true
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"include": ["react-router.config.ts", "vite.config.ts"],
4+
"compilerOptions": {
5+
"composite": true,
6+
"strict": true,
7+
"types": ["node"],
8+
"lib": ["ES2022"],
9+
"target": "ES2022",
10+
"module": "ES2022",
11+
"moduleResolution": "bundler"
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { reactRouter } from "@react-router/dev/vite";
2+
import { defineConfig } from "vite";
3+
import tsconfigPaths from "vite-tsconfig-paths";
4+
import { cloudflare } from "@cloudflare/vite-plugin";
5+
6+
export default defineConfig({
7+
plugins: [
8+
cloudflare({ viteEnvironment: { name: "ssr" } }),
9+
reactRouter(),
10+
tsconfigPaths(),
11+
],
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Generated by Wrangler by running `wrangler types`
2+
3+
interface Env {
4+
VALUE_FROM_CLOUDFLARE: "Hello from Cloudflare";
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { createRequestHandler } from "react-router";
2+
3+
declare global {
4+
interface CloudflareEnvironment extends Env {}
5+
}
6+
7+
declare module "react-router" {
8+
export interface AppLoadContext {
9+
cloudflare: {
10+
env: CloudflareEnvironment;
11+
ctx: ExecutionContext;
12+
};
13+
}
14+
}
15+
16+
const requestHandler = createRequestHandler(
17+
// @ts-expect-error - virtual module provided by React Router at build time
18+
() => import("virtual:react-router/server-build"),
19+
import.meta.env.MODE
20+
);
21+
22+
export default {
23+
async fetch(request, env, ctx) {
24+
return requestHandler(request, {
25+
cloudflare: { env, ctx },
26+
});
27+
},
28+
} satisfies ExportedHandler<CloudflareEnvironment>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name = "react-router-app"
2+
compatibility_date = "2024-11-18"
3+
main = "./workers/app.ts"
4+
5+
assets = {}
6+
7+
[vars]
8+
VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare"

Diff for: integration/helpers/vite.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ export const EXPRESS_SERVER = (args: {
143143
export type TemplateName =
144144
| "vite-5-template"
145145
| "vite-6-template"
146-
| "vite-cloudflare-template";
146+
| "cloudflare-dev-proxy-template"
147+
| "vite-plugin-cloudflare-template";
147148

148149
export const viteMajorTemplates = [
149150
{ templateName: "vite-5-template", templateDisplayName: "Vite 5" },
@@ -366,7 +367,7 @@ export const test = base.extend<Fixtures>({
366367
let port = await getPort();
367368
let cwd = await createProject(
368369
await files({ port }),
369-
"vite-cloudflare-template"
370+
"cloudflare-dev-proxy-template"
370371
);
371372
let { status } = build({ cwd });
372373
expect(status).toBe(0);

Diff for: integration/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"tailwindcss": "^3.3.0",
4242
"type-fest": "^4.0.0",
4343
"typescript": "^5.1.0",
44-
"vite": "^6.0.0",
44+
"vite": "^6.1.0",
4545
"vite-env-only": "^3.0.1",
4646
"vite-tsconfig-paths": "^4.2.2",
4747
"wait-on": "^7.0.1"

Diff for: integration/vite-cloudflare-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,13 @@ function getFiles({
136136
});
137137
}
138138

139-
test.describe("Cloudflare", () => {
139+
test.describe("Cloudflare Dev Proxy", () => {
140140
[false, true].forEach((viteEnvironmentApi) => {
141141
test.describe(`viteEnvironmentApi enabled: ${viteEnvironmentApi}`, () => {
142142
const files = getFiles({ viteEnvironmentApi });
143143

144144
test("vite dev", async ({ page, dev }) => {
145-
let { port } = await dev(files, "vite-cloudflare-template");
145+
let { port } = await dev(files, "cloudflare-dev-proxy-template");
146146
await workflow({ page, port });
147147
});
148148

0 commit comments

Comments
 (0)