From ece5d5e2a78170f004c693b5657b555e11bfde37 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Tue, 4 Mar 2025 10:52:01 +1100 Subject: [PATCH] Add playground for `vite-plugin-cloudflare` --- .../.gitignore | 15 +++-- .../vite-plugin-cloudflare-template/env.d.ts | 2 - .../workers/app.ts | 2 +- playground/vite-plugin-cloudflare/.gitignore | 11 ++++ .../app/entry.server.tsx | 43 +++++++++++++ .../vite-plugin-cloudflare/app/root.tsx | 19 ++++++ .../vite-plugin-cloudflare/app/routes.ts | 4 ++ .../app/routes/_index.tsx | 24 ++++++++ .../vite-plugin-cloudflare/package.json | 37 +++++++++++ .../vite-plugin-cloudflare/public/favicon.ico | Bin 0 -> 15086 bytes .../react-router.config.ts | 7 +++ .../tsconfig.cloudflare.json | 28 +++++++++ .../vite-plugin-cloudflare/tsconfig.json | 14 +++++ .../vite-plugin-cloudflare/tsconfig.node.json | 13 ++++ .../vite-plugin-cloudflare/vite.config.ts | 12 ++++ .../worker-configuration.d.ts | 5 ++ .../vite-plugin-cloudflare/workers/app.ts | 28 +++++++++ .../vite-plugin-cloudflare/wrangler.toml | 8 +++ pnpm-lock.yaml | 58 ++++++++++++++++++ 19 files changed, 322 insertions(+), 8 deletions(-) delete mode 100644 integration/helpers/vite-plugin-cloudflare-template/env.d.ts create mode 100644 playground/vite-plugin-cloudflare/.gitignore create mode 100644 playground/vite-plugin-cloudflare/app/entry.server.tsx create mode 100644 playground/vite-plugin-cloudflare/app/root.tsx create mode 100644 playground/vite-plugin-cloudflare/app/routes.ts create mode 100644 playground/vite-plugin-cloudflare/app/routes/_index.tsx create mode 100644 playground/vite-plugin-cloudflare/package.json create mode 100644 playground/vite-plugin-cloudflare/public/favicon.ico create mode 100644 playground/vite-plugin-cloudflare/react-router.config.ts create mode 100644 playground/vite-plugin-cloudflare/tsconfig.cloudflare.json create mode 100644 playground/vite-plugin-cloudflare/tsconfig.json create mode 100644 playground/vite-plugin-cloudflare/tsconfig.node.json create mode 100644 playground/vite-plugin-cloudflare/vite.config.ts create mode 100644 playground/vite-plugin-cloudflare/worker-configuration.d.ts create mode 100644 playground/vite-plugin-cloudflare/workers/app.ts create mode 100644 playground/vite-plugin-cloudflare/wrangler.toml diff --git a/integration/helpers/vite-plugin-cloudflare-template/.gitignore b/integration/helpers/vite-plugin-cloudflare-template/.gitignore index c08251ce0e..0c402dead2 100644 --- a/integration/helpers/vite-plugin-cloudflare-template/.gitignore +++ b/integration/helpers/vite-plugin-cloudflare-template/.gitignore @@ -1,6 +1,11 @@ -node_modules +.DS_Store +/node_modules/ +*.tsbuildinfo -/.cache -/build -.env -.react-router +# React Router +/.react-router/ +/build/ + +# Cloudflare +.mf +.wrangler diff --git a/integration/helpers/vite-plugin-cloudflare-template/env.d.ts b/integration/helpers/vite-plugin-cloudflare-template/env.d.ts deleted file mode 100644 index 5e7dfe5dd9..0000000000 --- a/integration/helpers/vite-plugin-cloudflare-template/env.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// diff --git a/integration/helpers/vite-plugin-cloudflare-template/workers/app.ts b/integration/helpers/vite-plugin-cloudflare-template/workers/app.ts index 879ce69b04..bf41f02ee2 100644 --- a/integration/helpers/vite-plugin-cloudflare-template/workers/app.ts +++ b/integration/helpers/vite-plugin-cloudflare-template/workers/app.ts @@ -14,7 +14,7 @@ declare module "react-router" { } const requestHandler = createRequestHandler( - // @ts-expect-error - virtual module provided by React Router at build time + // @ts-expect-error () => import("virtual:react-router/server-build"), import.meta.env.MODE ); diff --git a/playground/vite-plugin-cloudflare/.gitignore b/playground/vite-plugin-cloudflare/.gitignore new file mode 100644 index 0000000000..0c402dead2 --- /dev/null +++ b/playground/vite-plugin-cloudflare/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +/node_modules/ +*.tsbuildinfo + +# React Router +/.react-router/ +/build/ + +# Cloudflare +.mf +.wrangler diff --git a/playground/vite-plugin-cloudflare/app/entry.server.tsx b/playground/vite-plugin-cloudflare/app/entry.server.tsx new file mode 100644 index 0000000000..0d843dbb1f --- /dev/null +++ b/playground/vite-plugin-cloudflare/app/entry.server.tsx @@ -0,0 +1,43 @@ +import type { AppLoadContext, EntryContext } from "react-router"; +import { ServerRouter } from "react-router"; +import { isbot } from "isbot"; +import { renderToReadableStream } from "react-dom/server"; + +export default async function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + routerContext: EntryContext, + _loadContext: AppLoadContext +) { + let shellRendered = false; + const userAgent = request.headers.get("user-agent"); + + const body = await renderToReadableStream( + , + { + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + shellRendered = true; + + // Ensure requests from bots and SPA Mode renders wait for all content to load before responding + // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation + if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) { + await body.allReady; + } + + responseHeaders.set("Content-Type", "text/html"); + return new Response(body, { + headers: responseHeaders, + status: responseStatusCode, + }); +} diff --git a/playground/vite-plugin-cloudflare/app/root.tsx b/playground/vite-plugin-cloudflare/app/root.tsx new file mode 100644 index 0000000000..b36392b4dd --- /dev/null +++ b/playground/vite-plugin-cloudflare/app/root.tsx @@ -0,0 +1,19 @@ +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; + +export default function App() { + return ( + + + + + + + + + + + + + + ); +} diff --git a/playground/vite-plugin-cloudflare/app/routes.ts b/playground/vite-plugin-cloudflare/app/routes.ts new file mode 100644 index 0000000000..4c05936cb6 --- /dev/null +++ b/playground/vite-plugin-cloudflare/app/routes.ts @@ -0,0 +1,4 @@ +import { type RouteConfig } from "@react-router/dev/routes"; +import { flatRoutes } from "@react-router/fs-routes"; + +export default flatRoutes() satisfies RouteConfig; diff --git a/playground/vite-plugin-cloudflare/app/routes/_index.tsx b/playground/vite-plugin-cloudflare/app/routes/_index.tsx new file mode 100644 index 0000000000..f6f63dcb51 --- /dev/null +++ b/playground/vite-plugin-cloudflare/app/routes/_index.tsx @@ -0,0 +1,24 @@ +import type { MetaFunction } from "react-router"; +import type { Route } from "./+types/_index" + +export const meta: MetaFunction = () => { + return [ + { title: "New React Router App" }, + { name: "description", content: "Welcome to React Router!" }, + ]; +}; + +export async function loader({ context }: Route.LoaderArgs) { + return { + message: context.cloudflare.env.VALUE_FROM_CLOUDFLARE, + }; +} + +export default function Index({ loaderData }: Route.ComponentProps) { + return ( +
+

Welcome to React Router

+

{loaderData.message}

+
+ ); +} diff --git a/playground/vite-plugin-cloudflare/package.json b/playground/vite-plugin-cloudflare/package.json new file mode 100644 index 0000000000..2f3993a62e --- /dev/null +++ b/playground/vite-plugin-cloudflare/package.json @@ -0,0 +1,37 @@ +{ + "name": "@playground/vite-plugin-cloudflare", + "version": "0.0.0", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "react-router dev", + "build": "react-router build", + "typecheck": "react-router typegen && tsc" + }, + "dependencies": { + "express": "^4.19.2", + "isbot": "^5.1.11", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router": "workspace:*", + "serialize-javascript": "^6.0.1" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "^0.1.1", + "@cloudflare/workers-types": "^4.20250214.0", + "@react-router/dev": "workspace:*", + "@react-router/fs-routes": "workspace:*", + "@types/node": "^20.0.0", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "eslint": "^8.38.0", + "typescript": "^5.1.6", + "vite": "^6.1.0", + "vite-tsconfig-paths": "^4.2.1", + "wrangler": "^3.109.2" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/playground/vite-plugin-cloudflare/public/favicon.ico b/playground/vite-plugin-cloudflare/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5dbdfcddcb14182535f6d32d1c900681321b1aa3 GIT binary patch literal 15086 zcmeI33v3ic7{|AFEmuJ-;v>ep_G*NPi6KM`qNryCe1PIJ8siIN1WZ(7qVa)RVtmC% z)Ch?tN+afMKm;5@rvorJk zcXnoOc4q51HBQnQH_jn!cAg&XI1?PlX>Kl^k8qq0;zkha`kY$Fxt#=KNJAE9CMdpW zqr4#g8`nTw191(+H4xW8Tmyru2I^3=J1G3emPxkPXA=3{vvuvse_WWSshqaqls^-m zgB7q8&Vk*aYRe?sn$n53dGH#%3y%^vxv{pL*-h0Z4bmb_(k6{FL7HWIz(V*HT#IcS z-wE{)+0x1U!RUPt3gB97%p}@oHxF4|6S*+Yw=_tLtxZ~`S=z6J?O^AfU>7qOX`JNBbV&8+bO0%@fhQitKIJ^O^ zpgIa__qD_y07t@DFlBJ)8SP_#^j{6jpaXt{U%=dx!qu=4u7^21lWEYHPPY5U3TcoQ zX_7W+lvZi>TapNk_X>k-KO%MC9iZp>1E`N34gHKd9tK&){jq2~7OsJ>!G0FzxQFw6G zm&Vb(2#-T|rM|n3>uAsG_hnbvUKFf3#ay@u4uTzia~NY%XgCHfx4^To4BDU@)HlV? z@EN=g^ymETa1sQK{kRwyE4Ax8?wT&GvaG@ASO}{&a17&^v`y z!oPdiSiia^oov(Z)QhG2&|FgE{M9_4hJROGbnj>#$~ZF$-G^|zPj*QApltKe?;u;uKHJ~-V!=VLkg7Kgct)l7u39f@%VG8e3f$N-B zAu3a4%ZGf)r+jPAYCSLt73m_J3}p>}6Tx0j(wg4vvKhP!DzgiWANiE;Ppvp}P2W@m z-VbYn+NXFF?6ngef5CfY6ZwKnWvNV4z6s^~yMXw2i5mv}jC$6$46g?G|CPAu{W5qF zDobS=zb2ILX9D827g*NtGe5w;>frjanY{f)hrBP_2ehBt1?`~ypvg_Ot4x1V+43P@Ve8>qd)9NX_jWdLo`Zfy zoeam9)@Dpym{4m@+LNxXBPjPKA7{3a&H+~xQvr>C_A;7=JrfK~$M2pCh>|xLz>W6SCs4qC|#V`)# z)0C|?$o>jzh<|-cpf

K7osU{Xp5PG4-K+L2G=)c3f&}H&M3wo7TlO_UJjQ-Oq&_ zjAc9=nNIYz{c3zxOiS5UfcE1}8#iI4@uy;$Q7>}u`j+OU0N<*Ezx$k{x_27+{s2Eg z`^=rhtIzCm!_UcJ?Db~Lh-=_))PT3{Q0{Mwdq;0>ZL%l3+;B&4!&xm#%HYAK|;b456Iv&&f$VQHf` z>$*K9w8T+paVwc7fLfMlhQ4)*zL_SG{~v4QR;IuX-(oRtYAhWOlh`NLoX0k$RUYMi z2Y!bqpdN}wz8q`-%>&Le@q|jFw92ErW-hma-le?S z-@OZt2EEUm4wLsuEMkt4zlyy29_3S50JAcQHTtgTC{P~%-mvCTzrjXOc|{}N`Cz`W zSj7CrXfa7lcsU0J(0uSX6G`54t^7}+OLM0n(|g4waOQ}bd3%!XLh?NX9|8G_|06Ie zD5F1)w5I~!et7lA{G^;uf7aqT`KE&2qx9|~O;s6t!gb`+zVLJyT2T)l*8l(j literal 0 HcmV?d00001 diff --git a/playground/vite-plugin-cloudflare/react-router.config.ts b/playground/vite-plugin-cloudflare/react-router.config.ts new file mode 100644 index 0000000000..ef164520d8 --- /dev/null +++ b/playground/vite-plugin-cloudflare/react-router.config.ts @@ -0,0 +1,7 @@ +import type { Config } from "@react-router/dev/config"; + +export default { + future: { + unstable_viteEnvironmentApi: true, + }, +} satisfies Config; diff --git a/playground/vite-plugin-cloudflare/tsconfig.cloudflare.json b/playground/vite-plugin-cloudflare/tsconfig.cloudflare.json new file mode 100644 index 0000000000..587e895746 --- /dev/null +++ b/playground/vite-plugin-cloudflare/tsconfig.cloudflare.json @@ -0,0 +1,28 @@ +{ + "extends": "./tsconfig.json", + "include": [ + ".react-router/types/**/*", + "app/**/*", + "app/**/.server/**/*", + "app/**/.client/**/*", + "workers/**/*", + "worker-configuration.d.ts" + ], + "compilerOptions": { + "composite": true, + "strict": true, + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["@cloudflare/workers-types", "vite/client"], + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "baseUrl": ".", + "rootDirs": [".", "./.react-router/types"], + "paths": { + "~/*": ["./app/*"] + }, + "esModuleInterop": true, + "resolveJsonModule": true + } +} diff --git a/playground/vite-plugin-cloudflare/tsconfig.json b/playground/vite-plugin-cloudflare/tsconfig.json new file mode 100644 index 0000000000..d7ce9e49b0 --- /dev/null +++ b/playground/vite-plugin-cloudflare/tsconfig.json @@ -0,0 +1,14 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.cloudflare.json" } + ], + "compilerOptions": { + "checkJs": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true + } +} diff --git a/playground/vite-plugin-cloudflare/tsconfig.node.json b/playground/vite-plugin-cloudflare/tsconfig.node.json new file mode 100644 index 0000000000..4fce596ace --- /dev/null +++ b/playground/vite-plugin-cloudflare/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "include": ["react-router.config.ts", "vite.config.ts"], + "compilerOptions": { + "composite": true, + "strict": true, + "types": ["node"], + "lib": ["ES2022"], + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler" + } +} diff --git a/playground/vite-plugin-cloudflare/vite.config.ts b/playground/vite-plugin-cloudflare/vite.config.ts new file mode 100644 index 0000000000..629cf373dc --- /dev/null +++ b/playground/vite-plugin-cloudflare/vite.config.ts @@ -0,0 +1,12 @@ +import { reactRouter } from "@react-router/dev/vite"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; +import { cloudflare } from "@cloudflare/vite-plugin"; + +export default defineConfig({ + plugins: [ + cloudflare({ viteEnvironment: { name: "ssr" } }), + reactRouter(), + tsconfigPaths(), + ], +}); diff --git a/playground/vite-plugin-cloudflare/worker-configuration.d.ts b/playground/vite-plugin-cloudflare/worker-configuration.d.ts new file mode 100644 index 0000000000..421604f908 --- /dev/null +++ b/playground/vite-plugin-cloudflare/worker-configuration.d.ts @@ -0,0 +1,5 @@ +// Generated by Wrangler by running `wrangler types` + +interface Env { + VALUE_FROM_CLOUDFLARE: "Hello from Cloudflare"; +} diff --git a/playground/vite-plugin-cloudflare/workers/app.ts b/playground/vite-plugin-cloudflare/workers/app.ts new file mode 100644 index 0000000000..bf41f02ee2 --- /dev/null +++ b/playground/vite-plugin-cloudflare/workers/app.ts @@ -0,0 +1,28 @@ +import { createRequestHandler } from "react-router"; + +declare global { + interface CloudflareEnvironment extends Env {} +} + +declare module "react-router" { + export interface AppLoadContext { + cloudflare: { + env: CloudflareEnvironment; + ctx: ExecutionContext; + }; + } +} + +const requestHandler = createRequestHandler( + // @ts-expect-error + () => import("virtual:react-router/server-build"), + import.meta.env.MODE +); + +export default { + async fetch(request, env, ctx) { + return requestHandler(request, { + cloudflare: { env, ctx }, + }); + }, +} satisfies ExportedHandler; diff --git a/playground/vite-plugin-cloudflare/wrangler.toml b/playground/vite-plugin-cloudflare/wrangler.toml new file mode 100644 index 0000000000..86700690f8 --- /dev/null +++ b/playground/vite-plugin-cloudflare/wrangler.toml @@ -0,0 +1,8 @@ +name = "react-router-app" +compatibility_date = "2024-11-18" +main = "./workers/app.ts" + +assets = {} + +[vars] +VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 297b54e275..28325e133c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1341,6 +1341,64 @@ importers: specifier: ^4.2.1 version: 4.3.2(typescript@5.4.5)(vite@6.1.1(@types/node@20.11.30)(jiti@1.21.0)(yaml@2.6.0)) + playground/vite-plugin-cloudflare: + dependencies: + express: + specifier: ^4.19.2 + version: 4.19.2 + isbot: + specifier: ^5.1.11 + version: 5.1.11 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + react-router: + specifier: workspace:* + version: link:../../packages/react-router + serialize-javascript: + specifier: ^6.0.1 + version: 6.0.2 + devDependencies: + '@cloudflare/vite-plugin': + specifier: ^0.1.1 + version: 0.1.1(vite@6.1.1(@types/node@20.11.30)(jiti@1.21.0)(yaml@2.6.0))(workerd@1.20241230.0)(wrangler@3.109.2(@cloudflare/workers-types@4.20250214.0)) + '@cloudflare/workers-types': + specifier: ^4.20250214.0 + version: 4.20250214.0 + '@react-router/dev': + specifier: workspace:* + version: link:../../packages/react-router-dev + '@react-router/fs-routes': + specifier: workspace:* + version: link:../../packages/react-router-fs-routes + '@types/node': + specifier: ^20.0.0 + version: 20.11.30 + '@types/react': + specifier: ^18.2.18 + version: 18.2.18 + '@types/react-dom': + specifier: ^18.2.7 + version: 18.2.7 + eslint: + specifier: ^8.38.0 + version: 8.57.0 + typescript: + specifier: ^5.1.6 + version: 5.4.5 + vite: + specifier: ^6.1.0 + version: 6.1.1(@types/node@20.11.30)(jiti@1.21.0)(yaml@2.6.0) + vite-tsconfig-paths: + specifier: ^4.2.1 + version: 4.3.2(typescript@5.4.5)(vite@6.1.1(@types/node@20.11.30)(jiti@1.21.0)(yaml@2.6.0)) + wrangler: + specifier: ^3.109.2 + version: 3.109.2(@cloudflare/workers-types@4.20250214.0) + packages: '@aashutoshrathi/word-wrap@1.2.6':