Skip to content

Commit ece5d5e

Browse files
committed
Add playground for vite-plugin-cloudflare
1 parent d3e913b commit ece5d5e

File tree

19 files changed

+322
-8
lines changed

19 files changed

+322
-8
lines changed
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
node_modules
1+
.DS_Store
2+
/node_modules/
3+
*.tsbuildinfo
24

3-
/.cache
4-
/build
5-
.env
6-
.react-router
5+
# React Router
6+
/.react-router/
7+
/build/
8+
9+
# Cloudflare
10+
.mf
11+
.wrangler

integration/helpers/vite-plugin-cloudflare-template/env.d.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

integration/helpers/vite-plugin-cloudflare-template/workers/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ declare module "react-router" {
1414
}
1515

1616
const requestHandler = createRequestHandler(
17-
// @ts-expect-error - virtual module provided by React Router at build time
17+
// @ts-expect-error
1818
() => import("virtual:react-router/server-build"),
1919
import.meta.env.MODE
2020
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.DS_Store
2+
/node_modules/
3+
*.tsbuildinfo
4+
5+
# React Router
6+
/.react-router/
7+
/build/
8+
9+
# Cloudflare
10+
.mf
11+
.wrangler
Lines changed: 43 additions & 0 deletions
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+
}
Lines changed: 19 additions & 0 deletions
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+
}
Lines changed: 4 additions & 0 deletions
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;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { MetaFunction } from "react-router";
2+
import type { Route } from "./+types/_index"
3+
4+
export const meta: MetaFunction = () => {
5+
return [
6+
{ title: "New React Router App" },
7+
{ name: "description", content: "Welcome to React Router!" },
8+
];
9+
};
10+
11+
export async function loader({ context }: Route.LoaderArgs) {
12+
return {
13+
message: context.cloudflare.env.VALUE_FROM_CLOUDFLARE,
14+
};
15+
}
16+
17+
export default function Index({ loaderData }: Route.ComponentProps) {
18+
return (
19+
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
20+
<h1>Welcome to React Router</h1>
21+
<p>{loaderData.message}</p>
22+
</div>
23+
);
24+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "@playground/vite-plugin-cloudflare",
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.
Lines changed: 7 additions & 0 deletions
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;
Lines changed: 28 additions & 0 deletions
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+
}
Lines changed: 14 additions & 0 deletions
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+
}
Lines changed: 13 additions & 0 deletions
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+
}
Lines changed: 12 additions & 0 deletions
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+
});
Lines changed: 5 additions & 0 deletions
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+
}
Lines changed: 28 additions & 0 deletions
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
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>;
Lines changed: 8 additions & 0 deletions
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"

pnpm-lock.yaml

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)