From 71f1d59347c4c70bac965a80fe4b068237d1eaf4 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Tue, 13 Feb 2024 16:54:32 -0500 Subject: [PATCH 01/23] wip --- integration/helpers/vite.ts | 1 + integration/package.json | 2 +- integration/vite-cloudflare-test.ts | 43 ++++---- packages/remix-cloudflare-pages/worker.ts | 24 +++-- packages/remix-dev/index.ts | 6 +- packages/remix-dev/package.json | 9 +- .../remix-dev/vite/cloudflare-proxy-plugin.ts | 65 ++++++++++++ packages/remix-dev/vite/index.ts | 1 + yarn.lock | 98 +++++++++---------- 9 files changed, 170 insertions(+), 79 deletions(-) create mode 100644 packages/remix-dev/vite/cloudflare-proxy-plugin.ts diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index 2ec4670abbb..1a7551cb62c 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -211,6 +211,7 @@ export const test = base.extend({ await use(async (files) => { let port = await getPort(); let cwd = await createProject(await files({ port })); + console.log({ cwd }); stop = await viteDev({ cwd, port }); return { port, cwd }; }); diff --git a/integration/package.json b/integration/package.json index 50d6b69d704..4ee01d7b1ad 100644 --- a/integration/package.json +++ b/integration/package.json @@ -37,6 +37,6 @@ "typescript": "^5.1.0", "vite-env-only": "^2.0.0", "vite-tsconfig-paths": "^4.2.2", - "wrangler": "^3.24.0" + "wrangler": "^3.28.2" } } diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index 7695a93739a..93c94f2bc4a 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -4,6 +4,8 @@ import dedent from "dedent"; import type { Files } from "./helpers/vite.js"; import { test, viteConfig } from "./helpers/vite.js"; +// TODO: test wrangler too + const files: Files = async ({ port }) => ({ "package.json": JSON.stringify( { @@ -47,9 +49,8 @@ const files: Files = async ({ port }) => ({ "vite.config.ts": dedent` import { vitePlugin as remix, - cloudflarePreset as cloudflare, + cloudflareProxyVitePlugin as remixCloudflareProxy, } from "@remix-run/dev"; - import { getBindingsProxy } from "wrangler"; import { getLoadContext } from "./get-load-context"; export default { @@ -60,34 +61,37 @@ const files: Files = async ({ port }) => ({ }, }, plugins: [ - remix({ - presets: [ - cloudflare(getBindingsProxy, { - getRemixDevLoadContext: getLoadContext, - }) - ] - }) + remixCloudflareProxy({ getLoadContext }), + remix(), ], } `, "get-load-context.ts": ` import { type KVNamespace } from "@cloudflare/workers-types"; import { type AppLoadContext } from "@remix-run/cloudflare"; + import { type PlatformProxy } from "wrangler"; + + type Cloudflare = PlatformProxy<{ + MY_KV: KVNamespace; + }>; declare module "@remix-run/cloudflare" { export interface AppLoadContext { - env: { - MY_KV: KVNamespace; - }; + cloudflare: Cloudflare; + env2: Cloudflare["env"]; extra: string; } } - type Context = { request: Request; env: AppLoadContext["env"] }; - export const getLoadContext = (context: Context): AppLoadContext => { + type GetLoadContext = (args: { + request: Request; + context: { cloudflare: Cloudflare }; + }) => AppLoadContext; + + export const getLoadContext: GetLoadContext = ({ context }) => { return { ...context, - env2: context.env, + env2: context.cloudflare.env, extra: "stuff", }; }; @@ -120,23 +124,23 @@ const files: Files = async ({ port }) => ({ const key = "__my-key__"; export async function loader({ context }: LoaderFunctionArgs) { - const { MY_KV } = context.env; + const { MY_KV } = context.cloudflare.env; const value = await MY_KV.get(key); return json({ value, extra: context.extra }); } export async function action({ request, context }: ActionFunctionArgs) { - const { MY_KV: myKv } = context.env2; + const { MY_KV } = context.env2; if (request.method === "POST") { const formData = await request.formData(); const value = formData.get("value") as string; - await myKv.put(key, value); + await MY_KV.put(key, value); return null; } if (request.method === "DELETE") { - await myKv.delete(key); + await MY_KV.delete(key); return null; } @@ -186,4 +190,5 @@ test("vite dev", async ({ page, viteDev }) => { await expect(page.locator("[data-text]")).toHaveText("Value: my-value"); expect(page.errors).toEqual([]); + expect(1).toBe("chewbacca"); }); diff --git a/packages/remix-cloudflare-pages/worker.ts b/packages/remix-cloudflare-pages/worker.ts index 0fc0bfbf9e8..450f29ae6dd 100644 --- a/packages/remix-cloudflare-pages/worker.ts +++ b/packages/remix-cloudflare-pages/worker.ts @@ -12,9 +12,10 @@ export type GetLoadContextFunction< Env = unknown, Params extends string = any, Data extends Record = Record -> = ( - context: EventContext -) => Promise | AppLoadContext; +> = (args: { + request: Request; + context: { cloudflare: EventContext }; +}) => AppLoadContext | Promise; export type RequestHandler = PagesFunction; @@ -27,14 +28,23 @@ export interface createPagesFunctionHandlerParams { export function createRequestHandler({ build, mode, - getLoadContext = (context) => ({ env: context.env }), + getLoadContext = ({ context }) => ({ + ...context, + cloudflare: { + ...context.cloudflare, + cf: context.cloudflare.request.cf, + }, + }), }: createPagesFunctionHandlerParams): RequestHandler { let handleRequest = createRemixRequestHandler(build, mode); - return async (context) => { - let loadContext = await getLoadContext(context); + return async (cloudflare) => { + let loadContext = await getLoadContext({ + request: cloudflare.request, + context: { cloudflare }, + }); - return handleRequest(context.request, loadContext); + return handleRequest(cloudflare.request, loadContext); }; } diff --git a/packages/remix-dev/index.ts b/packages/remix-dev/index.ts index 18e02eea342..c6cc9f89932 100644 --- a/packages/remix-dev/index.ts +++ b/packages/remix-dev/index.ts @@ -12,4 +12,8 @@ export type { ServerBundlesFunction, VitePluginConfig, } from "./vite"; -export { vitePlugin, cloudflarePreset } from "./vite"; +export { + vitePlugin, + cloudflarePreset, + cloudflareProxyVitePlugin, +} from "./vite"; diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json index d94f3424c6e..76c59f8fa9f 100644 --- a/packages/remix-dev/package.json +++ b/packages/remix-dev/package.json @@ -91,12 +91,14 @@ "msw": "^1.2.3", "strip-ansi": "^6.0.1", "tiny-invariant": "^1.2.0", - "vite": "5.1.0" + "vite": "5.1.0", + "wrangler": "^3.28.2" }, "peerDependencies": { "@remix-run/serve": "^2.6.0", "typescript": "^5.1.0", - "vite": "^5.1.0" + "vite": "^5.1.0", + "wrangler": "^3.28.2" }, "peerDependenciesMeta": { "@remix-run/serve": { @@ -107,6 +109,9 @@ }, "vite": { "optional": true + }, + "wrangler": { + "optional": true } }, "engines": { diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts new file mode 100644 index 00000000000..4aeaba472ce --- /dev/null +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -0,0 +1,65 @@ +import { + createRequestHandler, + type ServerBuild, +} from "@remix-run/server-runtime"; +import { type Plugin } from "vite"; +// TODO: make wrangler import lazy?? +import { type PlatformProxy, getPlatformProxy } from "wrangler"; + +// TODO: auto-set ssr resolve conditions for workerd + +import { + fromNodeRequest, + toNodeRequest, + type NodeRequestHandler, +} from "./node-adapter"; + +let serverBuildId = "virtual:remix/server-build"; + +type LoadContext = { + cloudflare: PlatformProxy; +}; + +type GetLoadContext = (args: { + request: Request; + context: LoadContext; +}) => Promise>; + +export const cloudflareProxyVitePlugin = ( + options: { getLoadContext?: GetLoadContext } = {} +): Plugin => { + return { + name: "vite-plugin-remix-cloudflare-proxy", + async configureServer(viteDevServer) { + let cloudflare = await getPlatformProxy(); + let context = { cloudflare }; + return () => { + if (!viteDevServer.config.server.middlewareMode) { + viteDevServer.middlewares.use(async (req, res, next) => { + try { + let build = (await viteDevServer.ssrLoadModule( + serverBuildId + )) as ServerBuild; + + let handler = createRequestHandler(build, "development"); + let nodeHandler: NodeRequestHandler = async ( + nodeReq, + nodeRes + ) => { + let request = fromNodeRequest(nodeReq); + let loadContext = options.getLoadContext + ? await options.getLoadContext({ request, context }) + : context; + let res = await handler(request, loadContext); + await toNodeRequest(res, nodeRes); + }; + await nodeHandler(req, res); + } catch (error) { + next(error); + } + }); + } + }; + }, + }; +}; diff --git a/packages/remix-dev/vite/index.ts b/packages/remix-dev/vite/index.ts index f966bc2c94a..57a4e8e68d3 100644 --- a/packages/remix-dev/vite/index.ts +++ b/packages/remix-dev/vite/index.ts @@ -16,3 +16,4 @@ export const vitePlugin: RemixVitePlugin = (...args) => { }; export { cloudflarePreset } from "./presets/cloudflare"; +export { cloudflareProxyVitePlugin } from "./cloudflare-proxy-plugin"; diff --git a/yarn.lock b/yarn.lock index 4d1e55dd8e2..f915e347b43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1251,6 +1251,13 @@ human-id "^1.0.2" prettier "^2.7.1" +"@cloudflare/kv-asset-handler@0.3.1": + version "0.3.1" + resolved "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.1.tgz#9b86167e58dbc419943c8d3ddcd8e2823f5db300" + integrity sha512-lKN2XCfKCmpKb86a1tl4GIwsJYDy9TGuwjhDELLmpKygQhw8X2xR4dusgpC5Tg7q1pB96Eb0rBo81kxSILQMwA== + dependencies: + mime "^3.0.0" + "@cloudflare/kv-asset-handler@^0.1.3": version "0.1.3" resolved "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.1.3.tgz" @@ -1258,13 +1265,6 @@ dependencies: mime "^2.5.2" -"@cloudflare/kv-asset-handler@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.2.0.tgz#c9959bbd7a1c40bd7c674adae98aa8c8d0e5ca68" - integrity sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A== - dependencies: - mime "^3.0.0" - "@cloudflare/kv-asset-handler@^0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.0.tgz#11f0af0749a400ddadcca16dcd6f4696d7036991" @@ -1272,30 +1272,30 @@ dependencies: mime "^3.0.0" -"@cloudflare/workerd-darwin-64@1.20231218.0": - version "1.20231218.0" - resolved "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20231218.0.tgz#e887296a6bfa707b2e02dbf5168582cd3afb800c" - integrity sha512-547gOmTIVmRdDy7HNAGJUPELa+fSDm2Y0OCxqAtQOz0GLTDu1vX61xYmsb2rn91+v3xW6eMttEIpbYokKjtfJA== +"@cloudflare/workerd-darwin-64@1.20240129.0": + version "1.20240129.0" + resolved "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240129.0.tgz#b6db9c944fcb1a49b15be646383c937ffa175978" + integrity sha512-DfVVB5IsQLVcWPJwV019vY3nEtU88c2Qu2ST5SQxqcGivZ52imagLRK0RHCIP8PK4piSiq90qUC6ybppUsw8eg== -"@cloudflare/workerd-darwin-arm64@1.20231218.0": - version "1.20231218.0" - resolved "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20231218.0.tgz#9346de61b74324b09e3ef83e1666ffc84f1c4559" - integrity sha512-b39qrU1bKolCfmKFDAnX4vXcqzISkEUVE/V8sMBsFzxrIpNAbcUHBZAQPYmS/OHIGB94KjOVokvDi7J6UNurPw== +"@cloudflare/workerd-darwin-arm64@1.20240129.0": + version "1.20240129.0" + resolved "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240129.0.tgz#1e217bae20c8407ed0225b3eb60b6b2c4ab1a5ed" + integrity sha512-t0q8ABkmumG1zRM/MZ/vIv/Ysx0vTAXnQAPy/JW5aeQi/tqrypXkO9/NhPc0jbF/g/hIPrWEqpDgEp3CB7Da7Q== -"@cloudflare/workerd-linux-64@1.20231218.0": - version "1.20231218.0" - resolved "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20231218.0.tgz#7d21aaa0b4a97f9d7769fa6af2e484538f7e3713" - integrity sha512-dMUF1wA+0mybm6hHNOCgY/WMNMwomPPs4I7vvYCgwHSkch0Q2Wb7TnxQZSt8d1PK/myibaBwadrlIxpjxmpz3w== +"@cloudflare/workerd-linux-64@1.20240129.0": + version "1.20240129.0" + resolved "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240129.0.tgz#d0e46297c79982b47495cbfb73623d621aa49335" + integrity sha512-sFV1uobHgDI+6CKBS/ZshQvOvajgwl6BtiYaH4PSFSpvXTmRx+A9bcug+6BnD+V4WgwxTiEO2iR97E1XuwDAVw== -"@cloudflare/workerd-linux-arm64@1.20231218.0": - version "1.20231218.0" - resolved "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20231218.0.tgz#e8280275379aca868886db7d2491517be3f473f4" - integrity sha512-2s5uc8IHt0QmWyKxAr1Fy+4b8Xy0b/oUtlPnm5MrKi2gDRlZzR7JvxENPJCpCnYENydS8lzvkMiAFECPBccmyQ== +"@cloudflare/workerd-linux-arm64@1.20240129.0": + version "1.20240129.0" + resolved "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240129.0.tgz#e5d02fafcad1536e0515ee5feb2e713e487b1f2a" + integrity sha512-O7q7htHaFRp8PgTqNJx1/fYc3+LnvAo6kWWB9a14C5OWak6AAZk42PNpKPx+DXTmGvI+8S1+futBGUeJ8NPDXg== -"@cloudflare/workerd-windows-64@1.20231218.0": - version "1.20231218.0" - resolved "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20231218.0.tgz#85fc18f18f7c6593b427c58bf58224850f706d20" - integrity sha512-oN5hz6TXUDB5YKUN5N3QWAv6cYz9JjTZ9g16HVyoegVFEL6/zXU3tV19MBX2IvlE11ab/mRogEv9KXVIrHfKmA== +"@cloudflare/workerd-windows-64@1.20240129.0": + version "1.20240129.0" + resolved "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240129.0.tgz#99f456b636413e66d860deb2b803d04cc5b47d75" + integrity sha512-YqGno0XSqqqkDmNoGEX6M8kJlI2lEfWntbTPVtHaZlaXVR9sWfoD7TEno0NKC95cXFz+ioyFLbgbOdnfWwmVAA== "@cloudflare/workers-types@^4.20230518.0": version "4.20230628.0" @@ -9924,10 +9924,10 @@ min-indent@^1.0.0: resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -miniflare@3.20231218.3: - version "3.20231218.3" - resolved "https://registry.npmjs.org/miniflare/-/miniflare-3.20231218.3.tgz#31aa7f9165970ae53d3048eeb24ffb13ee84cf1e" - integrity sha512-OrPBYWO0WnFv6DrxZ7hF8f5agZ4+xo/2qSLE0wwCJSqlFhr91dfSJautxfCOBD896nAA7Jqr5LBPEnqq3/k/JQ== +miniflare@3.20240129.2: + version "3.20240129.2" + resolved "https://registry.npmjs.org/miniflare/-/miniflare-3.20240129.2.tgz#9fdfe5f4f2ade629996f2f7788a1500f765bcf67" + integrity sha512-BPUg8HsPmWQlRFUeiQk274i8M9L0gOvzbkjryuTvCX+M53EwBpP0gM2wyrRr/HokQoJcxWGh3InBu6L8+0bbPw== dependencies: "@cspotcode/source-map-support" "0.8.1" acorn "^8.8.0" @@ -9937,7 +9937,7 @@ miniflare@3.20231218.3: glob-to-regexp "^0.4.1" stoppable "^1.1.0" undici "^5.28.2" - workerd "1.20231218.0" + workerd "1.20240129.0" ws "^8.11.0" youch "^3.2.2" zod "^3.20.6" @@ -13670,29 +13670,29 @@ word-wrap@^1.2.3: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -workerd@1.20231218.0: - version "1.20231218.0" - resolved "https://registry.npmjs.org/workerd/-/workerd-1.20231218.0.tgz#a00403af346f654c1d73f4805c07b9ef3a6d2142" - integrity sha512-AGIsDvqCrcwhoA9kb1hxOhVAe53/xJeaGZxL4FbYI9FvO17DZwrnqGq+6eqItJ6Cfw1ZLmf3BM+QdMWaL2bFWQ== +workerd@1.20240129.0: + version "1.20240129.0" + resolved "https://registry.npmjs.org/workerd/-/workerd-1.20240129.0.tgz#123a84331ec18a1af7172fcd7b764070cfb951a9" + integrity sha512-t4pnsmjjk/u+GdVDgH2M1AFmJaBUABshYK/vT/HNrAXsHSwN6VR8Yqw0JQ845OokO34VLkuUtYQYyxHHKpdtsw== optionalDependencies: - "@cloudflare/workerd-darwin-64" "1.20231218.0" - "@cloudflare/workerd-darwin-arm64" "1.20231218.0" - "@cloudflare/workerd-linux-64" "1.20231218.0" - "@cloudflare/workerd-linux-arm64" "1.20231218.0" - "@cloudflare/workerd-windows-64" "1.20231218.0" - -wrangler@^3.24.0: - version "3.24.0" - resolved "https://registry.npmjs.org/wrangler/-/wrangler-3.24.0.tgz#a8ca60eaec280fecee7293189604a8b75150fa9a" - integrity sha512-jEnqpY+9/J4VPjtuEnS2lhCPXkvbDClnMalSWaRxSx+1tiTWMJhMjtK9oyXLdO+ZUf9Q4LvFTYSPm8O1uwmnxQ== - dependencies: - "@cloudflare/kv-asset-handler" "^0.2.0" + "@cloudflare/workerd-darwin-64" "1.20240129.0" + "@cloudflare/workerd-darwin-arm64" "1.20240129.0" + "@cloudflare/workerd-linux-64" "1.20240129.0" + "@cloudflare/workerd-linux-arm64" "1.20240129.0" + "@cloudflare/workerd-windows-64" "1.20240129.0" + +wrangler@^3.28.2: + version "3.28.2" + resolved "https://registry.npmjs.org/wrangler/-/wrangler-3.28.2.tgz#32a16dc150e31e9eb3f9f8076a8a71db9c8202e4" + integrity sha512-hlD4f2avBZuR1+qo9Um6D1prdWrSRtGTo9h6o/AKce+bHQEJWoJgJKHeLmrpZlLtHg/gGR1Xa1xzrexhuIzeJw== + dependencies: + "@cloudflare/kv-asset-handler" "0.3.1" "@esbuild-plugins/node-globals-polyfill" "^0.2.3" "@esbuild-plugins/node-modules-polyfill" "^0.2.2" blake3-wasm "^2.1.5" chokidar "^3.5.3" esbuild "0.17.19" - miniflare "3.20231218.3" + miniflare "3.20240129.2" nanoid "^3.3.3" path-to-regexp "^6.2.0" resolve "^1.22.8" From ee6967e991395d162cd0e1a18e57566c918b4e7e Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Tue, 13 Feb 2024 17:55:35 -0500 Subject: [PATCH 02/23] wip --- integration/vite-cloudflare-test.ts | 3 +- packages/remix-cloudflare-pages/worker.ts | 25 +++++++++- packages/remix-dev/index.ts | 6 +-- .../remix-dev/vite/cloudflare-proxy-plugin.ts | 27 ++++++---- packages/remix-dev/vite/index.ts | 1 - packages/remix-dev/vite/presets/cloudflare.ts | 50 ------------------- 6 files changed, 42 insertions(+), 70 deletions(-) delete mode 100644 packages/remix-dev/vite/presets/cloudflare.ts diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index 93c94f2bc4a..fea7d7dd66d 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -37,7 +37,7 @@ const files: Files = async ({ port }) => ({ typescript: "^5.1.6", vite: "^5.1.0", "vite-tsconfig-paths": "^4.2.1", - wrangler: "^3.24.0", + wrangler: "^3.28.2", }, engines: { node: ">=18.0.0", @@ -190,5 +190,4 @@ test("vite dev", async ({ page, viteDev }) => { await expect(page.locator("[data-text]")).toHaveText("Value: my-value"); expect(page.errors).toEqual([]); - expect(1).toBe("chewbacca"); }); diff --git a/packages/remix-cloudflare-pages/worker.ts b/packages/remix-cloudflare-pages/worker.ts index 450f29ae6dd..a5673466178 100644 --- a/packages/remix-cloudflare-pages/worker.ts +++ b/packages/remix-cloudflare-pages/worker.ts @@ -14,7 +14,19 @@ export type GetLoadContextFunction< Data extends Record = Record > = (args: { request: Request; - context: { cloudflare: EventContext }; + context: { + cloudflare: EventContext & { + cf: EventContext["request"]["cf"]; + ctx: { + waitUntil: EventContext["waitUntil"]; + passThroughOnException: EventContext< + Env, + Params, + Data + >["passThroughOnException"]; + }; + }; + }; }) => AppLoadContext | Promise; export type RequestHandler = PagesFunction; @@ -41,7 +53,16 @@ export function createRequestHandler({ return async (cloudflare) => { let loadContext = await getLoadContext({ request: cloudflare.request, - context: { cloudflare }, + context: { + cloudflare: { + ...cloudflare, + cf: cloudflare.request.cf!, + ctx: { + waitUntil: cloudflare.waitUntil, + passThroughOnException: cloudflare.passThroughOnException, + }, + }, + }, }); return handleRequest(cloudflare.request, loadContext); diff --git a/packages/remix-dev/index.ts b/packages/remix-dev/index.ts index c6cc9f89932..2ba8ee4c660 100644 --- a/packages/remix-dev/index.ts +++ b/packages/remix-dev/index.ts @@ -12,8 +12,4 @@ export type { ServerBundlesFunction, VitePluginConfig, } from "./vite"; -export { - vitePlugin, - cloudflarePreset, - cloudflareProxyVitePlugin, -} from "./vite"; +export { vitePlugin, cloudflareProxyVitePlugin } from "./vite"; diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index 4aeaba472ce..1ad5f1da2bf 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -1,11 +1,12 @@ +import { createRequestHandler } from "@remix-run/server-runtime"; import { - createRequestHandler, + type AppLoadContext, type ServerBuild, } from "@remix-run/server-runtime"; import { type Plugin } from "vite"; -// TODO: make wrangler import lazy?? import { type PlatformProxy, getPlatformProxy } from "wrangler"; +// TODO: make wrangler import lazy?? // TODO: auto-set ssr resolve conditions for workerd import { @@ -16,22 +17,28 @@ import { let serverBuildId = "virtual:remix/server-build"; -type LoadContext = { - cloudflare: PlatformProxy; +type Env = Record; +type CfProperties = Record; + +type LoadContext = { + cloudflare: PlatformProxy; }; -type GetLoadContext = (args: { +type GetLoadContext = (args: { request: Request; - context: LoadContext; -}) => Promise>; + context: LoadContext; +}) => AppLoadContext | Promise; -export const cloudflareProxyVitePlugin = ( - options: { getLoadContext?: GetLoadContext } = {} +export const cloudflareProxyVitePlugin = < + E extends Env, + Cf extends CfProperties +>( + options: { getLoadContext?: GetLoadContext } = {} ): Plugin => { return { name: "vite-plugin-remix-cloudflare-proxy", async configureServer(viteDevServer) { - let cloudflare = await getPlatformProxy(); + let cloudflare = await getPlatformProxy(); let context = { cloudflare }; return () => { if (!viteDevServer.config.server.middlewareMode) { diff --git a/packages/remix-dev/vite/index.ts b/packages/remix-dev/vite/index.ts index 57a4e8e68d3..8379446ecee 100644 --- a/packages/remix-dev/vite/index.ts +++ b/packages/remix-dev/vite/index.ts @@ -15,5 +15,4 @@ export const vitePlugin: RemixVitePlugin = (...args) => { return remixVitePlugin(...args); }; -export { cloudflarePreset } from "./presets/cloudflare"; export { cloudflareProxyVitePlugin } from "./cloudflare-proxy-plugin"; diff --git a/packages/remix-dev/vite/presets/cloudflare.ts b/packages/remix-dev/vite/presets/cloudflare.ts deleted file mode 100644 index aab0e75c8d1..00000000000 --- a/packages/remix-dev/vite/presets/cloudflare.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { type AppLoadContext } from "@remix-run/server-runtime"; - -import { type Preset, setRemixDevLoadContext } from "../plugin"; - -type MaybePromise = T | Promise; - -type GetRemixDevLoadContext = (args: { - request: Request; - env: AppLoadContext["env"]; -}) => MaybePromise>; - -type GetLoadContext = ( - request: Request -) => MaybePromise>; - -type GetBindingsProxy = () => Promise<{ bindings: Record }>; - -/** - * @param options.getRemixDevLoadContext - Augment the load context. - */ -export const cloudflarePreset = ( - getBindingsProxy: GetBindingsProxy, - options: { - getRemixDevLoadContext?: GetRemixDevLoadContext; - } = {} -): Preset => ({ - name: "cloudflare", - remixConfig: async () => { - let getLoadContext: GetLoadContext = async () => { - let { bindings } = await getBindingsProxy(); - return { env: bindings }; - }; - - // eslint-disable-next-line prefer-let/prefer-let - const { getRemixDevLoadContext } = options; - if (getRemixDevLoadContext) { - getLoadContext = async (request: Request) => { - let { bindings } = await getBindingsProxy(); - let loadContext = await getRemixDevLoadContext({ - env: bindings, - request, - }); - return loadContext; - }; - } - - setRemixDevLoadContext(getLoadContext); - return {}; - }, -}); From afcf009718cbe438dd8c626257e5075839f61650 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Tue, 13 Feb 2024 18:45:23 -0500 Subject: [PATCH 03/23] automatically set ssr resolve conditions for workerd --- integration/vite-cloudflare-test.ts | 5 ----- packages/remix-dev/vite/cloudflare-proxy-plugin.ts | 7 +++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index fea7d7dd66d..a4991061e27 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -55,11 +55,6 @@ const files: Files = async ({ port }) => ({ export default { ${await viteConfig.server({ port })} - ssr: { - resolve: { - externalConditions: ["workerd", "worker"], - }, - }, plugins: [ remixCloudflareProxy({ getLoadContext }), remix(), diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index 1ad5f1da2bf..b5c0d4be4d0 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -37,6 +37,13 @@ export const cloudflareProxyVitePlugin = < ): Plugin => { return { name: "vite-plugin-remix-cloudflare-proxy", + config: () => ({ + ssr: { + resolve: { + externalConditions: ["workerd", "worker"], + }, + }, + }), async configureServer(viteDevServer) { let cloudflare = await getPlatformProxy(); let context = { cloudflare }; From 6e9ccec70fa886619a8f9721d75dff046786116b Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Tue, 13 Feb 2024 19:09:14 -0500 Subject: [PATCH 04/23] caches and dispose --- integration/vite-cloudflare-test.ts | 5 +++-- packages/remix-cloudflare-pages/worker.ts | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index a4991061e27..72910323410 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -66,9 +66,10 @@ const files: Files = async ({ port }) => ({ import { type AppLoadContext } from "@remix-run/cloudflare"; import { type PlatformProxy } from "wrangler"; - type Cloudflare = PlatformProxy<{ + type Env = { MY_KV: KVNamespace; - }>; + } + type Cloudflare = Omit, 'dispose'>; declare module "@remix-run/cloudflare" { export interface AppLoadContext { diff --git a/packages/remix-cloudflare-pages/worker.ts b/packages/remix-cloudflare-pages/worker.ts index a5673466178..2d251aa3963 100644 --- a/packages/remix-cloudflare-pages/worker.ts +++ b/packages/remix-cloudflare-pages/worker.ts @@ -1,5 +1,6 @@ import type { AppLoadContext, ServerBuild } from "@remix-run/cloudflare"; import { createRequestHandler as createRemixRequestHandler } from "@remix-run/cloudflare"; +import { type CacheStorage } from "@cloudflare/workers-types"; /** * A function that returns the value to use as `context` in route `loader` and @@ -25,6 +26,7 @@ export type GetLoadContextFunction< Data >["passThroughOnException"]; }; + caches: CacheStorage; }; }; }) => AppLoadContext | Promise; @@ -61,6 +63,7 @@ export function createRequestHandler({ waitUntil: cloudflare.waitUntil, passThroughOnException: cloudflare.passThroughOnException, }, + caches, }, }, }); From b0744c992945f936fe890ccd05e75f7bc54d73fd Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Tue, 13 Feb 2024 19:11:38 -0500 Subject: [PATCH 05/23] inline node request handler --- .../remix-dev/vite/cloudflare-proxy-plugin.ts | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index b5c0d4be4d0..9e9802db703 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -9,11 +9,7 @@ import { type PlatformProxy, getPlatformProxy } from "wrangler"; // TODO: make wrangler import lazy?? // TODO: auto-set ssr resolve conditions for workerd -import { - fromNodeRequest, - toNodeRequest, - type NodeRequestHandler, -} from "./node-adapter"; +import { fromNodeRequest, toNodeRequest } from "./node-adapter"; let serverBuildId = "virtual:remix/server-build"; @@ -49,25 +45,19 @@ export const cloudflareProxyVitePlugin = < let context = { cloudflare }; return () => { if (!viteDevServer.config.server.middlewareMode) { - viteDevServer.middlewares.use(async (req, res, next) => { + viteDevServer.middlewares.use(async (nodeReq, nodeRes, next) => { try { let build = (await viteDevServer.ssrLoadModule( serverBuildId )) as ServerBuild; let handler = createRequestHandler(build, "development"); - let nodeHandler: NodeRequestHandler = async ( - nodeReq, - nodeRes - ) => { - let request = fromNodeRequest(nodeReq); - let loadContext = options.getLoadContext - ? await options.getLoadContext({ request, context }) - : context; - let res = await handler(request, loadContext); - await toNodeRequest(res, nodeRes); - }; - await nodeHandler(req, res); + let req = fromNodeRequest(nodeReq); + let loadContext = options.getLoadContext + ? await options.getLoadContext({ request: req, context }) + : context; + let res = await handler(req, loadContext); + await toNodeRequest(res, nodeRes); } catch (error) { next(error); } From d16d9a87f92a02955a1b18c668e79a9c89d8e003 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 14 Feb 2024 14:52:01 -0500 Subject: [PATCH 06/23] test wrangler --- integration/helpers/vite.ts | 43 ++++++++++++++++++++++++++++- integration/vite-cloudflare-test.ts | 23 +++++++++------ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index 1a7551cb62c..ae9a392be1b 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -148,6 +148,31 @@ export const viteRemixServe = async ({ return () => serveProc.kill(); }; +export const wranglerPagesDev = async ({ + cwd, + port, +}: { + cwd: string; + port: number; +}) => { + let nodeBin = process.argv[0]; + + // grab wrangler bin from remix-run/remix root node_modules since its not copied into integration project's node_modules + let wranglerBin = path.resolve("node_modules/.bin/wrangler"); + + let proc = spawn( + nodeBin, + [wranglerBin, "pages", "dev", "./build/client", "--port", String(port)], + { + cwd, + stdio: "pipe", + env: { NODE_ENV: "production" }, + } + ); + await waitForServer(proc, { port }); + return () => proc.kill(); +}; + type ServerArgs = { cwd: string; port: number; @@ -197,6 +222,10 @@ type Fixtures = { port: number; cwd: string; }>; + wranglerPagesDev: (files: Files) => Promise<{ + port: number; + cwd: string; + }>; }; export const test = base.extend({ @@ -211,7 +240,6 @@ export const test = base.extend({ await use(async (files) => { let port = await getPort(); let cwd = await createProject(await files({ port })); - console.log({ cwd }); stop = await viteDev({ cwd, port }); return { port, cwd }; }); @@ -241,6 +269,19 @@ export const test = base.extend({ }); stop?.(); }, + // eslint-disable-next-line no-empty-pattern + wranglerPagesDev: async ({}, use) => { + let stop: (() => unknown) | undefined; + await use(async (files) => { + let port = await getPort(); + let cwd = await createProject(await files({ port })); + let { status } = viteBuild({ cwd }); + expect(status).toBe(0); + stop = await wranglerPagesDev({ cwd, port }); + return { port, cwd }; + }); + stop?.(); + }, }); function node( diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index 72910323410..f74c89f14a4 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -1,11 +1,10 @@ +import type { Page } from "@playwright/test"; import { expect } from "@playwright/test"; import dedent from "dedent"; import type { Files } from "./helpers/vite.js"; import { test, viteConfig } from "./helpers/vite.js"; -// TODO: test wrangler too - const files: Files = async ({ port }) => ({ "package.json": JSON.stringify( { @@ -51,7 +50,7 @@ const files: Files = async ({ port }) => ({ vitePlugin as remix, cloudflareProxyVitePlugin as remixCloudflareProxy, } from "@remix-run/dev"; - import { getLoadContext } from "./get-load-context"; + import { getLoadContext } from "./load-context"; export default { ${await viteConfig.server({ port })} @@ -61,7 +60,7 @@ const files: Files = async ({ port }) => ({ ], } `, - "get-load-context.ts": ` + "load-context.ts": ` import { type KVNamespace } from "@cloudflare/workers-types"; import { type AppLoadContext } from "@remix-run/cloudflare"; import { type PlatformProxy } from "wrangler"; @@ -72,7 +71,7 @@ const files: Files = async ({ port }) => ({ type Cloudflare = Omit, 'dispose'>; declare module "@remix-run/cloudflare" { - export interface AppLoadContext { + interface AppLoadContext { cloudflare: Cloudflare; env2: Cloudflare["env"]; extra: string; @@ -97,7 +96,7 @@ const files: Files = async ({ port }) => ({ // @ts-ignore - the server build file is generated by \`remix vite:build\` import * as build from "../build/server"; - import { getLoadContext } from "../get-load-context"; + import { getLoadContext } from "../load-context"; export const onRequest = createPagesFunctionHandler({ build, @@ -175,6 +174,15 @@ const files: Files = async ({ port }) => ({ test("vite dev", async ({ page, viteDev }) => { let { port } = await viteDev(files); + await workflow({ page, port }); +}); + +test("wrangler", async ({ page, wranglerPagesDev }) => { + let { port } = await wranglerPagesDev(files); + await workflow({ page, port }); +}); + +async function workflow({ page, port }: { page: Page; port: number }) { await page.goto(`http://localhost:${port}/`, { waitUntil: "networkidle", }); @@ -184,6 +192,5 @@ test("vite dev", async ({ page, viteDev }) => { await page.getByLabel("Set value:").fill("my-value"); await page.getByRole("button").click(); await expect(page.locator("[data-text]")).toHaveText("Value: my-value"); - expect(page.errors).toEqual([]); -}); +} From a15d6b6da9d0599738ed983fefe13850d0d84905 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 14 Feb 2024 15:33:51 -0500 Subject: [PATCH 07/23] template --- templates/vite-cloudflare/app/routes/_index.tsx | 4 ++-- templates/vite-cloudflare/load-context.ts | 12 +++++------- templates/vite-cloudflare/vite.config.ts | 15 ++------------- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/templates/vite-cloudflare/app/routes/_index.tsx b/templates/vite-cloudflare/app/routes/_index.tsx index cad20670fcd..fe86669b534 100644 --- a/templates/vite-cloudflare/app/routes/_index.tsx +++ b/templates/vite-cloudflare/app/routes/_index.tsx @@ -8,13 +8,13 @@ import { Form, useLoaderData } from "@remix-run/react"; const key = "__my-key__"; export async function loader({ context }: LoaderFunctionArgs) { - const { MY_KV } = context.env; + const { MY_KV } = context.cloudflare.env; const value = await MY_KV.get(key); return json({ value }); } export async function action({ request, context }: ActionFunctionArgs) { - const { MY_KV: myKv } = context.env; + const { MY_KV: myKv } = context.cloudflare.env; if (request.method === "POST") { const formData = await request.formData(); diff --git a/templates/vite-cloudflare/load-context.ts b/templates/vite-cloudflare/load-context.ts index 5ebc9a6281a..1d1e2266ad0 100644 --- a/templates/vite-cloudflare/load-context.ts +++ b/templates/vite-cloudflare/load-context.ts @@ -1,14 +1,12 @@ import { type KVNamespace } from "@cloudflare/workers-types"; +import { type PlatformProxy } from "wrangler"; -// In the future, types for bindings will be generated by `wrangler types` -// See https://github.com/cloudflare/workers-sdk/pull/4931 -type Bindings = { - // Add types for bindings configured in `wrangler.toml` - MY_KV: KVNamespace; -}; +// TODO: generate Env via `wrangler types` +type Env = { MY_KV: KVNamespace }; +type Cloudflare = Omit, "dispose">; declare module "@remix-run/cloudflare" { interface AppLoadContext { - env: Bindings; + cloudflare: Cloudflare; } } diff --git a/templates/vite-cloudflare/vite.config.ts b/templates/vite-cloudflare/vite.config.ts index 4842ad88f18..a46118377c1 100644 --- a/templates/vite-cloudflare/vite.config.ts +++ b/templates/vite-cloudflare/vite.config.ts @@ -1,21 +1,10 @@ import { vitePlugin as remix, - cloudflarePreset as cloudflare, + cloudflareProxyVitePlugin as remixCloudflareProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; -import { getBindingsProxy } from "wrangler"; export default defineConfig({ - plugins: [ - remix({ - presets: [cloudflare(getBindingsProxy)], - }), - tsconfigPaths(), - ], - ssr: { - resolve: { - externalConditions: ["workerd", "worker"], - }, - }, + plugins: [remixCloudflareProxy(), remix(), tsconfigPaths()], }); From 28210bbad2415233fab67a453a994564e572a21b Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 14 Feb 2024 16:06:59 -0500 Subject: [PATCH 08/23] docs --- docs/future/presets.md | 17 ++---- docs/future/vite.md | 121 ++++++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 75 deletions(-) diff --git a/docs/future/presets.md b/docs/future/presets.md index 7a5a4a08636..28a0247bd4a 100644 --- a/docs/future/presets.md +++ b/docs/future/presets.md @@ -15,27 +15,20 @@ The config returned by each preset is merged in the order they were defined. Any ## Using a preset -Presets are designed to be published to npm and used within your Vite config. For example, Remix ships with a preset for Cloudflare: +Presets are designed to be published to npm and used within your Vite config. ```ts filename=vite.config.ts lines=[3,11] -import { - vitePlugin as remix, - cloudflarePreset as cloudflare, -} from "@remix-run/dev"; +import { vitePlugin as remix } from "@remix-run/dev"; import { defineConfig } from "vite"; -import { getBindingsProxy } from "wrangler"; +// TODO: better example +import { somePreset } from "some-preset"; export default defineConfig({ plugins: [ remix({ - presets: [cloudflare(getBindingsProxy)], + presets: [somePreset()], }), ], - ssr: { - resolve: { - externalConditions: ["workerd", "worker"], - }, - }, }); ``` diff --git a/docs/future/vite.md b/docs/future/vite.md index d7d9d0b7ddf..1b97f390934 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -109,56 +109,62 @@ wrangler pages dev ./build/client While Vite provides a better development experience, Wrangler provides closer emulation of the Cloudflare environment by running your server code in [Cloudflare's `workerd` runtime][cloudflare-workerd] instead of Node. -#### Bindings - -To simulate the Cloudflare environment in Vite, Wrangler provides [Node proxies for resource bindings][wrangler-getbindingsproxy]. -Bindings for Cloudflare resources can be configured [within `wrangler.toml` for local development][wrangler-toml-bindings] or within the [Cloudflare dashboard for deployments][cloudflare-pages-bindings]. +#### Cloudflare Proxy -Remix's Cloudflare preset accepts Wrangler's `getBindingsProxy` function to simulate resource bindings within Vite's dev server: +To simulate the Cloudflare environment in Vite, Wrangler provides [Node proxies to local `workerd` bindings][wrangler-getplatformproxy]. +Remix's Cloudflare Proxy plugin sets up these proxies for you: -```ts filename=vite.config.ts lines=[6,11] +```ts filename=vite.config.ts lines=[3,8] import { vitePlugin as remix, - cloudflarePreset as cloudflare, + cloudflareProxyVitePlugin as remixCloudflareProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; -import { getBindingsProxy } from "wrangler"; export default defineConfig({ - plugins: [ - remix({ - presets: [cloudflare(getBindingsProxy)], - }), - ], - ssr: { - resolve: { - externalConditions: ["workerd", "worker"], - }, - }, + plugins: [remixCloudflareProxy(), remix()], }); ``` -Then, you can access your bindings via `context.env`. -For example, with a [KV namespace][cloudflare-kv] bound as `MY_KV`: +The proxies are then available within `context.cloudflare` in your `loader` or `action` functions: -```ts filename=app/routes/_index.tsx -export async function loader({ context }) { - const { MY_KV } = context.env; - const value = await MY_KV.get("my-key"); - return json({ value }); -} +```ts +export const loader = ({ context }: LoaderFunctionArgs) => { + const { env, cf, ctx } = context.cloudflare; + // ... more loader code here... +}; ``` - +Check out [Cloudflare's `getPlatformProxy` docs][wrangler-getplatformproxy-return] for more information on each of these proxies. -The Cloudflare team is working to improve their Node proxies to support: + -- [Cloudflare request][cloudflare-proxy-cf] (`cf`) -- [Context][cloudflare-proxy-ctx] (`ctx`) -- [Cache][cloudflare-proxy-caches] (`caches`) +The Cloudflare team is working to improve support for the [caches][cloudflare-proxy-caches] proxy. +#### Bindings + +To configure bindings for Cloudflare resources: + +- For locally development, use [wrangler.toml][wrangler-toml-bindings] +- For deployments, use the [Cloudflare dashboard][cloudflare-pages-bindings] + +// TODO: typegen + +Then, you can access your bindings via `context.cloudflare.env`. +For example, with a [KV namespace][cloudflare-kv] bound as `MY_KV`: + +```ts filename=app/routes/_index.tsx +export async function loader({ + context, +}: LoaderFunctionArgs) { + const { MY_KV } = context.cloudflare.env; + const value = await MY_KV.get("my-key"); + return json({ value }); +} +``` + #### Augmenting Cloudflare load context If you'd like to add additional properties to the load context, @@ -167,27 +173,28 @@ you can export a `getLoadContext` function from `load-context.ts` that you can w ```ts filename=load-context.ts lines=[2,14,18-28] import { type KVNamespace } from "@cloudflare/workers-types"; import { type AppLoadContext } from "@remix-run/cloudflare"; +import { type PlatformProxy } from "wrangler"; -// In the future, types for bindings will be generated by `wrangler types` -// See https://github.com/cloudflare/workers-sdk/pull/4931 -type Bindings = { - // Add types for bindings configured in `wrangler.toml` - MY_KV: KVNamespace; -}; +// TODO: generate Env via `wrangler types` +type Env = { MY_KV: KVNamespace }; +type Cloudflare = Omit, "dispose">; declare module "@remix-run/cloudflare" { interface AppLoadContext { - env: Bindings; + cloudflare: Cloudflare; extra: string; } } -type Context = { request: Request; env: Bindings }; +type GetLoadContext = (args: { + request: Request; + context: { cloudflare: Cloudflare }; +}) => AppLoadContext; // Shared implementation compatible with Vite, Wrangler, and Cloudflare Pages -export const getLoadContext = async ( - context: Context -): Promise => { +export const getLoadContext: GetLoadContext = ({ + context, +}) => { return { ...context, extra: "stuff", @@ -195,40 +202,29 @@ export const getLoadContext = async ( }; ``` -The Cloudflare preset accepts a `getRemixDevLoadContext` function whose return value is merged into the load context for each request in development: +The Cloudflare Proxy plugin accepts a `getLoadContext` function: ```ts filename=vite.config.ts lines=[9,16] import { vitePlugin as remix, - cloudflarePreset as cloudflare, + cloudflareProxyVitePlugin as remixCloudflareProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; -import { getBindingsProxy } from "wrangler"; import { getLoadContext } from "./load-context"; export default defineConfig({ plugins: [ - remix({ - presets: [ - cloudflare(getBindingsProxy, { - getRemixDevLoadContext: getLoadContext, - }), - ], - }), - tsconfigPaths(), + remixCloudflareProxy({ getLoadContext }), + remix(), ], - ssr: { - resolve: { - externalConditions: ["workerd", "worker"], - }, - }, }); ``` -As the name implies, `getRemixDevLoadContext` **only augments the load context within Vite's dev server**, not within Wrangler nor in Cloudflare Pages deployments. -To wire up Wrangler and deployments, you'll need to add `getLoadContext` to `functions/[[path]].ts`: +The Remix Cloudflare Proxy plugin's `getLoadContext` **only augments the load context within Vite's dev server**, not within Wrangler nor in Cloudflare Pages deployments. + +To wire up Wrangler and deployments, you'll also need to add `getLoadContext` to `functions/[[path]].ts`: ```ts filename=functions/[[path]].ts lines=[5,9] import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages"; @@ -1276,11 +1272,10 @@ We're definitely late to the Vite party, but we're excited to be here now! [cloudflare-pages-bindings]: https://developers.cloudflare.com/pages/functions/bindings/ [cloudflare-kv]: https://developers.cloudflare.com/pages/functions/bindings/#kv-namespaces [cloudflare-workerd]: https://blog.cloudflare.com/workerd-open-source-workers-runtime -[wrangler-getbindingsproxy]: https://developers.cloudflare.com/workers/wrangler/api/#getbindingsproxy +[wrangler-getplatformproxy]: https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy +[wrangler-getplatformproxy-return]: https://developers.cloudflare.com/workers/wrangler/api/#return-type-1 [remix-config-server]: https://remix.run/docs/en/main/file-conventions/remix-config#server [cloudflare-vite-and-wrangler]: #vite--wrangler -[cloudflare-proxy-cf]: https://github.com/cloudflare/workers-sdk/issues/4875 -[cloudflare-proxy-ctx]: https://github.com/cloudflare/workers-sdk/issues/4876 [cloudflare-proxy-caches]: https://github.com/cloudflare/workers-sdk/issues/4879 [rr-basename]: https://reactrouter.com/routers/create-browser-router#basename [vite-public-base-path]: https://vitejs.dev/config/shared-options.html#base From 20e72694767b84c5fbcb93c1f1e01f2254e97afd Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 14 Feb 2024 16:17:35 -0500 Subject: [PATCH 09/23] typegen --- docs/future/vite.md | 18 +++++++++++++++--- templates/vite-cloudflare/load-context.ts | 3 --- templates/vite-cloudflare/package.json | 4 +++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/future/vite.md b/docs/future/vite.md index 1b97f390934..1055d88fbf7 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -150,7 +150,21 @@ To configure bindings for Cloudflare resources: - For locally development, use [wrangler.toml][wrangler-toml-bindings] - For deployments, use the [Cloudflare dashboard][cloudflare-pages-bindings] -// TODO: typegen +Whenever you change your bindings in `wrangler.toml`, you'll want to regenerate types for those bindings via `wrangler types`. +The `wrangler types` command generates a TypeScript file that defines the `Env` interface with your bindings in the global scope. +For example, the [Cloudflare template][template-vite-cloudflare] automatically generates initial types with `postinstall` script and then references those types in `load-context.ts`: + +```ts filename=load-context.ts lines=[3] +import { type PlatformProxy } from "wrangler"; + +type Cloudflare = Omit, "dispose">; + +declare module "@remix-run/cloudflare" { + interface AppLoadContext { + cloudflare: Cloudflare; + } +} +``` Then, you can access your bindings via `context.cloudflare.env`. For example, with a [KV namespace][cloudflare-kv] bound as `MY_KV`: @@ -175,8 +189,6 @@ import { type KVNamespace } from "@cloudflare/workers-types"; import { type AppLoadContext } from "@remix-run/cloudflare"; import { type PlatformProxy } from "wrangler"; -// TODO: generate Env via `wrangler types` -type Env = { MY_KV: KVNamespace }; type Cloudflare = Omit, "dispose">; declare module "@remix-run/cloudflare" { diff --git a/templates/vite-cloudflare/load-context.ts b/templates/vite-cloudflare/load-context.ts index 1d1e2266ad0..fcf3a3ce659 100644 --- a/templates/vite-cloudflare/load-context.ts +++ b/templates/vite-cloudflare/load-context.ts @@ -1,8 +1,5 @@ -import { type KVNamespace } from "@cloudflare/workers-types"; import { type PlatformProxy } from "wrangler"; -// TODO: generate Env via `wrangler types` -type Env = { MY_KV: KVNamespace }; type Cloudflare = Omit, "dispose">; declare module "@remix-run/cloudflare" { diff --git a/templates/vite-cloudflare/package.json b/templates/vite-cloudflare/package.json index 9e7e301ba02..6715b3041b0 100644 --- a/templates/vite-cloudflare/package.json +++ b/templates/vite-cloudflare/package.json @@ -3,12 +3,14 @@ "sideEffects": false, "type": "module", "scripts": { + "postinstall": "npm run typegen", "dev": "remix vite:dev", "build": "remix vite:build", "start": "wrangler pages dev ./build/client", "deploy": "wrangler pages deploy ./build/client", "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", - "typecheck": "tsc" + "typecheck": "tsc", + "typegen": "wrangler types" }, "dependencies": { "@remix-run/cloudflare": "*", From f610aa031209747ac3b75e81c715392a61d3a004 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 14 Feb 2024 16:28:08 -0500 Subject: [PATCH 10/23] template readme - vite vs wrangler - deployment warning for bindings --- templates/vite-cloudflare/README.md | 37 +++++++++++++++++++---------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/templates/vite-cloudflare/README.md b/templates/vite-cloudflare/README.md index c05e097d923..b0b49f9ab41 100644 --- a/templates/vite-cloudflare/README.md +++ b/templates/vite-cloudflare/README.md @@ -2,14 +2,32 @@ 📖 See the [Remix docs](https://remix.run/docs) and the [Remix Vite docs](https://remix.run/docs/en/main/future/vite) for details on supported features. +## Typegen + +Generate types for your Cloudflare bindings in `wrangler.toml`: + +```sh +npm run typegen +``` + +This should have been done for you initially via the `postinstall` script, +but you will need to rerun typegen whenever you make changes to `wrangler.toml`. + ## Development Run the Vite dev server: -```shellscript +```sh npm run dev ``` +To run Wrangler: + +```sh +npm run build +npm run start +``` + ## Deployment First, build your app for production: @@ -18,19 +36,12 @@ First, build your app for production: npm run build ``` -Then run the app in production mode: +Then, deploy your app to Cloudflare Pages: ```sh -npm start +npm run deploy ``` -Now you'll need to pick a host to deploy it to. - -### DIY - -If you're familiar with deploying Node applications, the built-in Remix app server is production-ready. - -Make sure to deploy the output of `npm run build` - -- `build/server` -- `build/client` +> [!WARNING] +> Cloudflare does _not_ use `wrangler.toml` to configure deployment bindings. +> You **MUST** configure deployment bindings manually in the Cloudflare dashboard. From 0d2e0f8d719beee30be1816c8ec32ab4ab6e182b Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 11:22:38 -0500 Subject: [PATCH 11/23] fix windows? --- integration/helpers/vite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index ae9a392be1b..bdb65f536a1 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -158,7 +158,7 @@ export const wranglerPagesDev = async ({ let nodeBin = process.argv[0]; // grab wrangler bin from remix-run/remix root node_modules since its not copied into integration project's node_modules - let wranglerBin = path.resolve("node_modules/.bin/wrangler"); + let wranglerBin = path.resolve("node_modules/wrangler/bin/wrangler.js"); let proc = spawn( nodeBin, From 8921af5b535b99873326696e056fa8c7281a7e28 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 12:46:20 -0500 Subject: [PATCH 12/23] fix types --- .../remix-dev/vite/cloudflare-proxy-plugin.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index 9e9802db703..ba4d3abdced 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -7,29 +7,24 @@ import { type Plugin } from "vite"; import { type PlatformProxy, getPlatformProxy } from "wrangler"; // TODO: make wrangler import lazy?? -// TODO: auto-set ssr resolve conditions for workerd import { fromNodeRequest, toNodeRequest } from "./node-adapter"; let serverBuildId = "virtual:remix/server-build"; -type Env = Record; type CfProperties = Record; -type LoadContext = { - cloudflare: PlatformProxy; +type LoadContext = { + cloudflare: PlatformProxy; }; -type GetLoadContext = (args: { +type GetLoadContext = (args: { request: Request; - context: LoadContext; + context: LoadContext; }) => AppLoadContext | Promise; -export const cloudflareProxyVitePlugin = < - E extends Env, - Cf extends CfProperties ->( - options: { getLoadContext?: GetLoadContext } = {} +export const cloudflareProxyVitePlugin = ( + options: { getLoadContext?: GetLoadContext } = {} ): Plugin => { return { name: "vite-plugin-remix-cloudflare-proxy", @@ -41,7 +36,7 @@ export const cloudflareProxyVitePlugin = < }, }), async configureServer(viteDevServer) { - let cloudflare = await getPlatformProxy(); + let cloudflare = await getPlatformProxy(); let context = { cloudflare }; return () => { if (!viteDevServer.config.server.middlewareMode) { From 2255c11dabfc268176636e4a1d1a5999f952a0bb Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 12:46:44 -0500 Subject: [PATCH 13/23] remove unused import --- integration/vite-cloudflare-test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index f74c89f14a4..cd95a1b6967 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -61,7 +61,6 @@ const files: Files = async ({ port }) => ({ } `, "load-context.ts": ` - import { type KVNamespace } from "@cloudflare/workers-types"; import { type AppLoadContext } from "@remix-run/cloudflare"; import { type PlatformProxy } from "wrangler"; From 8bef7b46ec857ed320ef0e2b367f2f925555cc3d Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 12:46:58 -0500 Subject: [PATCH 14/23] pr feedback --- docs/future/vite.md | 36 +++++++----------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/docs/future/vite.md b/docs/future/vite.md index 1055d88fbf7..b882ceada5a 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -137,34 +137,14 @@ export const loader = ({ context }: LoaderFunctionArgs) => { Check out [Cloudflare's `getPlatformProxy` docs][wrangler-getplatformproxy-return] for more information on each of these proxies. - - -The Cloudflare team is working to improve support for the [caches][cloudflare-proxy-caches] proxy. - - - #### Bindings To configure bindings for Cloudflare resources: -- For locally development, use [wrangler.toml][wrangler-toml-bindings] +- For local development with Vite or Wrangler, use [wrangler.toml][wrangler-toml-bindings] - For deployments, use the [Cloudflare dashboard][cloudflare-pages-bindings] -Whenever you change your bindings in `wrangler.toml`, you'll want to regenerate types for those bindings via `wrangler types`. -The `wrangler types` command generates a TypeScript file that defines the `Env` interface with your bindings in the global scope. -For example, the [Cloudflare template][template-vite-cloudflare] automatically generates initial types with `postinstall` script and then references those types in `load-context.ts`: - -```ts filename=load-context.ts lines=[3] -import { type PlatformProxy } from "wrangler"; - -type Cloudflare = Omit, "dispose">; - -declare module "@remix-run/cloudflare" { - interface AppLoadContext { - cloudflare: Cloudflare; - } -} -``` +Whenever you change your `wrangler.toml` file, you'll need to run `wrangler types` to regenerate your bindings. Then, you can access your bindings via `context.cloudflare.env`. For example, with a [KV namespace][cloudflare-kv] bound as `MY_KV`: @@ -182,10 +162,9 @@ export async function loader({ #### Augmenting Cloudflare load context If you'd like to add additional properties to the load context, -you can export a `getLoadContext` function from `load-context.ts` that you can wire up to Vite and Cloudflare Pages: +you can export a `getLoadContext` function from a shared module that you can wire up to Vite and Cloudflare Pages: -```ts filename=load-context.ts lines=[2,14,18-28] -import { type KVNamespace } from "@cloudflare/workers-types"; +```ts filename=load-context.ts lines=[2,10,14-27] import { type AppLoadContext } from "@remix-run/cloudflare"; import { type PlatformProxy } from "wrangler"; @@ -214,9 +193,9 @@ export const getLoadContext: GetLoadContext = ({ }; ``` -The Cloudflare Proxy plugin accepts a `getLoadContext` function: +For local development with Vite, you can then pass this `getLoadContext` function to the Cloudflare Proxy plugin in your Vite config: -```ts filename=vite.config.ts lines=[9,16] +```ts filename=vite.config.ts lines=[8,12] import { vitePlugin as remix, cloudflareProxyVitePlugin as remixCloudflareProxy, @@ -234,7 +213,7 @@ export default defineConfig({ }); ``` -The Remix Cloudflare Proxy plugin's `getLoadContext` **only augments the load context within Vite's dev server**, not within Wrangler nor in Cloudflare Pages deployments. +The Cloudflare Proxy plugin's `getLoadContext` **only augments the load context within Vite's dev server**, not within Wrangler nor in Cloudflare Pages deployments. To wire up Wrangler and deployments, you'll also need to add `getLoadContext` to `functions/[[path]].ts`: @@ -1288,7 +1267,6 @@ We're definitely late to the Vite party, but we're excited to be here now! [wrangler-getplatformproxy-return]: https://developers.cloudflare.com/workers/wrangler/api/#return-type-1 [remix-config-server]: https://remix.run/docs/en/main/file-conventions/remix-config#server [cloudflare-vite-and-wrangler]: #vite--wrangler -[cloudflare-proxy-caches]: https://github.com/cloudflare/workers-sdk/issues/4879 [rr-basename]: https://reactrouter.com/routers/create-browser-router#basename [vite-public-base-path]: https://vitejs.dev/config/shared-options.html#base [vite-base]: https://vitejs.dev/config/shared-options.html#base From 2ca42c0e9d831395b96040a85d7a68ea835e541b Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 12:49:40 -0500 Subject: [PATCH 15/23] lazy wrangler import --- packages/remix-dev/vite/cloudflare-proxy-plugin.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index ba4d3abdced..522af483e2b 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -4,9 +4,7 @@ import { type ServerBuild, } from "@remix-run/server-runtime"; import { type Plugin } from "vite"; -import { type PlatformProxy, getPlatformProxy } from "wrangler"; - -// TODO: make wrangler import lazy?? +import { type PlatformProxy } from "wrangler"; import { fromNodeRequest, toNodeRequest } from "./node-adapter"; @@ -23,6 +21,14 @@ type GetLoadContext = (args: { context: LoadContext; }) => AppLoadContext | Promise; +function importWrangler() { + try { + return import("wrangler"); + } catch (_) { + throw Error("Could not import `wrangler`. Do you have it installed?"); + } +} + export const cloudflareProxyVitePlugin = ( options: { getLoadContext?: GetLoadContext } = {} ): Plugin => { @@ -36,6 +42,7 @@ export const cloudflareProxyVitePlugin = ( }, }), async configureServer(viteDevServer) { + let { getPlatformProxy } = await importWrangler(); let cloudflare = await getPlatformProxy(); let context = { cloudflare }; return () => { From cf845a39bf13e0026099eb70d4d014d831f5d81a Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 12:52:11 -0500 Subject: [PATCH 16/23] update presets docs --- docs/future/presets.md | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/future/presets.md b/docs/future/presets.md index 28a0247bd4a..d0dc9f10454 100644 --- a/docs/future/presets.md +++ b/docs/future/presets.md @@ -13,25 +13,6 @@ Presets can only do two things: The config returned by each preset is merged in the order they were defined. Any config directly passed to the Remix Vite plugin will be merged last. This means that user config will always take precedence over any presets. -## Using a preset - -Presets are designed to be published to npm and used within your Vite config. - -```ts filename=vite.config.ts lines=[3,11] -import { vitePlugin as remix } from "@remix-run/dev"; -import { defineConfig } from "vite"; -// TODO: better example -import { somePreset } from "some-preset"; - -export default defineConfig({ - plugins: [ - remix({ - presets: [somePreset()], - }), - ], -}); -``` - ## Creating a preset Presets conform to the following `Preset` type: @@ -114,5 +95,23 @@ export function myCoolPreset(): Preset { The `remixConfigResolved` hook should only be used in cases where it would be an error to merge or override your preset's config. +## Using a preset + +Presets are designed to be published to npm and used within your Vite config. + +```ts filename=vite.config.ts lines=[3,8] +import { vitePlugin as remix } from "@remix-run/dev"; +import { defineConfig } from "vite"; +import { myCoolPreset } from "remix-preset-cool"; + +export default defineConfig({ + plugins: [ + remix({ + presets: [myCoolPreset()], + }), + ], +}); +``` + [remix-vite]: ./vite [server-bundles]: ./server-bundles From 84feff784c07efe67acaf3ca437e7293526546b1 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 12:55:15 -0500 Subject: [PATCH 17/23] do not include `dispose` in cloudflare context --- packages/remix-dev/vite/cloudflare-proxy-plugin.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index 522af483e2b..93d6ac81028 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -13,7 +13,7 @@ let serverBuildId = "virtual:remix/server-build"; type CfProperties = Record; type LoadContext = { - cloudflare: PlatformProxy; + cloudflare: Omit, "dispose">; }; type GetLoadContext = (args: { @@ -43,7 +43,8 @@ export const cloudflareProxyVitePlugin = ( }), async configureServer(viteDevServer) { let { getPlatformProxy } = await importWrangler(); - let cloudflare = await getPlatformProxy(); + // Do not include `dispose` in Cloudflare context + let { dispose: _, ...cloudflare } = await getPlatformProxy(); let context = { cloudflare }; return () => { if (!viteDevServer.config.server.middlewareMode) { From 2e0b37e7cad65ea4381263625dc15109a69835ec Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 13:01:13 -0500 Subject: [PATCH 18/23] rename cloudflare proxy plugin to include `dev` devCloudflareProxyVitePlugin as remixDevCloudflareProxy --- docs/future/vite.md | 8 ++++---- integration/vite-cloudflare-test.ts | 4 ++-- packages/remix-dev/index.ts | 2 +- packages/remix-dev/vite/cloudflare-proxy-plugin.ts | 2 +- packages/remix-dev/vite/index.ts | 2 +- templates/vite-cloudflare/vite.config.ts | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/future/vite.md b/docs/future/vite.md index b882ceada5a..e388541883e 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -117,12 +117,12 @@ Remix's Cloudflare Proxy plugin sets up these proxies for you: ```ts filename=vite.config.ts lines=[3,8] import { vitePlugin as remix, - cloudflareProxyVitePlugin as remixCloudflareProxy, + devCloudflareProxyVitePlugin as remixDevCloudflareProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; export default defineConfig({ - plugins: [remixCloudflareProxy(), remix()], + plugins: [remixDevCloudflareProxy(), remix()], }); ``` @@ -198,7 +198,7 @@ For local development with Vite, you can then pass this `getLoadContext` functio ```ts filename=vite.config.ts lines=[8,12] import { vitePlugin as remix, - cloudflareProxyVitePlugin as remixCloudflareProxy, + devCloudflareProxyVitePlugin as remixDevCloudflareProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; @@ -207,7 +207,7 @@ import { getLoadContext } from "./load-context"; export default defineConfig({ plugins: [ - remixCloudflareProxy({ getLoadContext }), + remixDevCloudflareProxy({ getLoadContext }), remix(), ], }); diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index cd95a1b6967..a145a9f8c4c 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -48,14 +48,14 @@ const files: Files = async ({ port }) => ({ "vite.config.ts": dedent` import { vitePlugin as remix, - cloudflareProxyVitePlugin as remixCloudflareProxy, + devCloudflareProxyVitePlugin as remixDevCloudflareProxy, } from "@remix-run/dev"; import { getLoadContext } from "./load-context"; export default { ${await viteConfig.server({ port })} plugins: [ - remixCloudflareProxy({ getLoadContext }), + remixDevCloudflareProxy({ getLoadContext }), remix(), ], } diff --git a/packages/remix-dev/index.ts b/packages/remix-dev/index.ts index 2ba8ee4c660..f03f761bc27 100644 --- a/packages/remix-dev/index.ts +++ b/packages/remix-dev/index.ts @@ -12,4 +12,4 @@ export type { ServerBundlesFunction, VitePluginConfig, } from "./vite"; -export { vitePlugin, cloudflareProxyVitePlugin } from "./vite"; +export { vitePlugin, devCloudflareProxyVitePlugin } from "./vite"; diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index 93d6ac81028..e57c93ef68f 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -29,7 +29,7 @@ function importWrangler() { } } -export const cloudflareProxyVitePlugin = ( +export const devCloudflareProxyVitePlugin = ( options: { getLoadContext?: GetLoadContext } = {} ): Plugin => { return { diff --git a/packages/remix-dev/vite/index.ts b/packages/remix-dev/vite/index.ts index 8379446ecee..e54ab04066f 100644 --- a/packages/remix-dev/vite/index.ts +++ b/packages/remix-dev/vite/index.ts @@ -15,4 +15,4 @@ export const vitePlugin: RemixVitePlugin = (...args) => { return remixVitePlugin(...args); }; -export { cloudflareProxyVitePlugin } from "./cloudflare-proxy-plugin"; +export { devCloudflareProxyVitePlugin } from "./cloudflare-proxy-plugin"; diff --git a/templates/vite-cloudflare/vite.config.ts b/templates/vite-cloudflare/vite.config.ts index a46118377c1..7fa3ad939e1 100644 --- a/templates/vite-cloudflare/vite.config.ts +++ b/templates/vite-cloudflare/vite.config.ts @@ -1,10 +1,10 @@ import { vitePlugin as remix, - cloudflareProxyVitePlugin as remixCloudflareProxy, + devCloudflareProxyVitePlugin as remixDevCloudflareProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [remixCloudflareProxy(), remix(), tsconfigPaths()], + plugins: [remixDevCloudflareProxy(), remix(), tsconfigPaths()], }); From abad9af6d9ef0148302b2f020cc7a9375f31abd3 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 13:06:36 -0500 Subject: [PATCH 19/23] fix lint --- docs/future/presets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/future/presets.md b/docs/future/presets.md index d0dc9f10454..158a0873f98 100644 --- a/docs/future/presets.md +++ b/docs/future/presets.md @@ -101,8 +101,8 @@ Presets are designed to be published to npm and used within your Vite config. ```ts filename=vite.config.ts lines=[3,8] import { vitePlugin as remix } from "@remix-run/dev"; -import { defineConfig } from "vite"; import { myCoolPreset } from "remix-preset-cool"; +import { defineConfig } from "vite"; export default defineConfig({ plugins: [ From 3a857be5aaf5c10d5ee9846d6f38f88939378df0 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 13:06:47 -0500 Subject: [PATCH 20/23] accept platform proxy options --- .../remix-dev/vite/cloudflare-proxy-plugin.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index e57c93ef68f..888d5e967a0 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -4,7 +4,7 @@ import { type ServerBuild, } from "@remix-run/server-runtime"; import { type Plugin } from "vite"; -import { type PlatformProxy } from "wrangler"; +import { type GetPlatformProxyOptions, type PlatformProxy } from "wrangler"; import { fromNodeRequest, toNodeRequest } from "./node-adapter"; @@ -29,9 +29,12 @@ function importWrangler() { } } -export const devCloudflareProxyVitePlugin = ( - options: { getLoadContext?: GetLoadContext } = {} -): Plugin => { +export const devCloudflareProxyVitePlugin = ({ + getLoadContext, + ...options +}: { + getLoadContext?: GetLoadContext; +} & GetPlatformProxyOptions = {}): Plugin => { return { name: "vite-plugin-remix-cloudflare-proxy", config: () => ({ @@ -44,7 +47,7 @@ export const devCloudflareProxyVitePlugin = ( async configureServer(viteDevServer) { let { getPlatformProxy } = await importWrangler(); // Do not include `dispose` in Cloudflare context - let { dispose: _, ...cloudflare } = await getPlatformProxy(); + let { dispose, ...cloudflare } = await getPlatformProxy(options); let context = { cloudflare }; return () => { if (!viteDevServer.config.server.middlewareMode) { @@ -56,8 +59,8 @@ export const devCloudflareProxyVitePlugin = ( let handler = createRequestHandler(build, "development"); let req = fromNodeRequest(nodeReq); - let loadContext = options.getLoadContext - ? await options.getLoadContext({ request: req, context }) + let loadContext = getLoadContext + ? await getLoadContext({ request: req, context }) : context; let res = await handler(req, loadContext); await toNodeRequest(res, nodeRes); From 4929b451ed4baa2ece678f31636fd3d4be1e966a Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 13:18:28 -0500 Subject: [PATCH 21/23] changeset --- .changeset/khaki-starfishes-rest.md | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .changeset/khaki-starfishes-rest.md diff --git a/.changeset/khaki-starfishes-rest.md b/.changeset/khaki-starfishes-rest.md new file mode 100644 index 00000000000..2b7e6aca8b4 --- /dev/null +++ b/.changeset/khaki-starfishes-rest.md @@ -0,0 +1,44 @@ +--- +"@remix-run/cloudflare-pages": minor +"@remix-run/dev": minor +--- + +Vite: Cloudflare Proxy as a Vite plugin + +**This is a breaking change for projects relying on Cloudflare support from the unstable Vite plugin** + +The Cloudflare preset (`unstable_cloudflarePreset`) as been removed and replaced with a new Vite plugin: + +```diff + import { + unstable_vitePlugin as remix, +- unstable_cloudflarePreset as cloudflare, ++ devCloudflareProxyVitePlugin as remixDevCloudflareProxy, + } from "@remix-run/dev"; + import { defineConfig } from "vite"; + + export default defineConfig({ + plugins: [ ++ remixDevCloudflareProxy(), ++ remix(), +- remix({ +- presets: [cloudflare()], +- }), + ], +- ssr: { +- resolve: { +- externalConditions: ["workerd", "worker"], +- }, +- }, + }); +``` + +`remixDevCloudflareProxy` must come _before_ the `remix` plugin so that it can override Vite's dev server middleware to be compatible with Cloudflare's proxied environment. + +Because it is a Vite plugin, `remixDevCloudflareProxy` can set `ssr.resolve.externalConditions` to be `workerd`-compatible for you. + +`remixDevCloudflareProxy` accepts a `getLoadContext` function that replaces the old `getRemixDevLoadContext`. +If you were using a `nightly` version that required `getBindingsProxy` or `getPlatformProxy`, that is no longer required. +Any options you were passing to `getBindingsProxy` or `getPlatformProxy` should now be passed to `remixDevCloudflareProxy` instead. + +This API also better aligns with future plans to support Cloudflare with a framework-agnostic Vite plugin that makes use of Vite's (experimental) Runtime API. From 3abd1afbbd4f538b311cc5cc9b21ae03b2082dd6 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 13:35:25 -0500 Subject: [PATCH 22/23] validate that cloudflare proxy plugin comes _before_ remix plugin --- .../remix-dev/vite/cloudflare-proxy-plugin.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index 888d5e967a0..dbfcd0791cf 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -29,6 +29,8 @@ function importWrangler() { } } +const NAME = "vite-plugin-remix-cloudflare-proxy"; + export const devCloudflareProxyVitePlugin = ({ getLoadContext, ...options @@ -36,7 +38,7 @@ export const devCloudflareProxyVitePlugin = ({ getLoadContext?: GetLoadContext; } & GetPlatformProxyOptions = {}): Plugin => { return { - name: "vite-plugin-remix-cloudflare-proxy", + name: NAME, config: () => ({ ssr: { resolve: { @@ -44,7 +46,17 @@ export const devCloudflareProxyVitePlugin = ({ }, }, }), - async configureServer(viteDevServer) { + configResolved: (viteConfig) => { + let pluginIndex = (name: string) => + viteConfig.plugins.findIndex((plugin) => plugin.name === name); + let remixIndex = pluginIndex("remix"); + if (remixIndex >= 0 && remixIndex < pluginIndex(NAME)) { + throw new Error( + `The "${NAME}" plugin should be placed before the Remix plugin in your Vite config file` + ); + } + }, + configureServer: async (viteDevServer) => { let { getPlatformProxy } = await importWrangler(); // Do not include `dispose` in Cloudflare context let { dispose, ...cloudflare } = await getPlatformProxy(options); From 5373f3841a0f5659632d7d84dad078815cdb1e86 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 15 Feb 2024 16:48:08 -0500 Subject: [PATCH 23/23] rename --- .changeset/khaki-starfishes-rest.md | 12 ++++++------ docs/future/vite.md | 8 ++++---- integration/vite-cloudflare-test.ts | 4 ++-- packages/remix-dev/index.ts | 2 +- packages/remix-dev/vite/cloudflare-proxy-plugin.ts | 2 +- packages/remix-dev/vite/index.ts | 2 +- templates/vite-cloudflare/vite.config.ts | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.changeset/khaki-starfishes-rest.md b/.changeset/khaki-starfishes-rest.md index 2b7e6aca8b4..aa0d8ba41d4 100644 --- a/.changeset/khaki-starfishes-rest.md +++ b/.changeset/khaki-starfishes-rest.md @@ -13,13 +13,13 @@ The Cloudflare preset (`unstable_cloudflarePreset`) as been removed and replaced import { unstable_vitePlugin as remix, - unstable_cloudflarePreset as cloudflare, -+ devCloudflareProxyVitePlugin as remixDevCloudflareProxy, ++ cloudflareDevProxyVitePlugin as remixCloudflareDevProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ -+ remixDevCloudflareProxy(), ++ remixCloudflareDevProxy(), + remix(), - remix({ - presets: [cloudflare()], @@ -33,12 +33,12 @@ The Cloudflare preset (`unstable_cloudflarePreset`) as been removed and replaced }); ``` -`remixDevCloudflareProxy` must come _before_ the `remix` plugin so that it can override Vite's dev server middleware to be compatible with Cloudflare's proxied environment. +`remixCloudflareDevProxy` must come _before_ the `remix` plugin so that it can override Vite's dev server middleware to be compatible with Cloudflare's proxied environment. -Because it is a Vite plugin, `remixDevCloudflareProxy` can set `ssr.resolve.externalConditions` to be `workerd`-compatible for you. +Because it is a Vite plugin, `remixCloudflareDevProxy` can set `ssr.resolve.externalConditions` to be `workerd`-compatible for you. -`remixDevCloudflareProxy` accepts a `getLoadContext` function that replaces the old `getRemixDevLoadContext`. +`remixCloudflareDevProxy` accepts a `getLoadContext` function that replaces the old `getRemixDevLoadContext`. If you were using a `nightly` version that required `getBindingsProxy` or `getPlatformProxy`, that is no longer required. -Any options you were passing to `getBindingsProxy` or `getPlatformProxy` should now be passed to `remixDevCloudflareProxy` instead. +Any options you were passing to `getBindingsProxy` or `getPlatformProxy` should now be passed to `remixCloudflareDevProxy` instead. This API also better aligns with future plans to support Cloudflare with a framework-agnostic Vite plugin that makes use of Vite's (experimental) Runtime API. diff --git a/docs/future/vite.md b/docs/future/vite.md index e388541883e..b1dfe291196 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -117,12 +117,12 @@ Remix's Cloudflare Proxy plugin sets up these proxies for you: ```ts filename=vite.config.ts lines=[3,8] import { vitePlugin as remix, - devCloudflareProxyVitePlugin as remixDevCloudflareProxy, + cloudflareDevProxyVitePlugin as remixCloudflareDevProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; export default defineConfig({ - plugins: [remixDevCloudflareProxy(), remix()], + plugins: [remixCloudflareDevProxy(), remix()], }); ``` @@ -198,7 +198,7 @@ For local development with Vite, you can then pass this `getLoadContext` functio ```ts filename=vite.config.ts lines=[8,12] import { vitePlugin as remix, - devCloudflareProxyVitePlugin as remixDevCloudflareProxy, + cloudflareDevProxyVitePlugin as remixCloudflareDevProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; @@ -207,7 +207,7 @@ import { getLoadContext } from "./load-context"; export default defineConfig({ plugins: [ - remixDevCloudflareProxy({ getLoadContext }), + remixCloudflareDevProxy({ getLoadContext }), remix(), ], }); diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index a145a9f8c4c..0c48c7b31d2 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -48,14 +48,14 @@ const files: Files = async ({ port }) => ({ "vite.config.ts": dedent` import { vitePlugin as remix, - devCloudflareProxyVitePlugin as remixDevCloudflareProxy, + cloudflareDevProxyVitePlugin as remixCloudflareDevProxy, } from "@remix-run/dev"; import { getLoadContext } from "./load-context"; export default { ${await viteConfig.server({ port })} plugins: [ - remixDevCloudflareProxy({ getLoadContext }), + remixCloudflareDevProxy({ getLoadContext }), remix(), ], } diff --git a/packages/remix-dev/index.ts b/packages/remix-dev/index.ts index f03f761bc27..1c28706e7c2 100644 --- a/packages/remix-dev/index.ts +++ b/packages/remix-dev/index.ts @@ -12,4 +12,4 @@ export type { ServerBundlesFunction, VitePluginConfig, } from "./vite"; -export { vitePlugin, devCloudflareProxyVitePlugin } from "./vite"; +export { vitePlugin, cloudflareDevProxyVitePlugin } from "./vite"; diff --git a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts index dbfcd0791cf..314c94cf33e 100644 --- a/packages/remix-dev/vite/cloudflare-proxy-plugin.ts +++ b/packages/remix-dev/vite/cloudflare-proxy-plugin.ts @@ -31,7 +31,7 @@ function importWrangler() { const NAME = "vite-plugin-remix-cloudflare-proxy"; -export const devCloudflareProxyVitePlugin = ({ +export const cloudflareDevProxyVitePlugin = ({ getLoadContext, ...options }: { diff --git a/packages/remix-dev/vite/index.ts b/packages/remix-dev/vite/index.ts index e54ab04066f..f563856b381 100644 --- a/packages/remix-dev/vite/index.ts +++ b/packages/remix-dev/vite/index.ts @@ -15,4 +15,4 @@ export const vitePlugin: RemixVitePlugin = (...args) => { return remixVitePlugin(...args); }; -export { devCloudflareProxyVitePlugin } from "./cloudflare-proxy-plugin"; +export { cloudflareDevProxyVitePlugin } from "./cloudflare-proxy-plugin"; diff --git a/templates/vite-cloudflare/vite.config.ts b/templates/vite-cloudflare/vite.config.ts index 7fa3ad939e1..37b93b6d033 100644 --- a/templates/vite-cloudflare/vite.config.ts +++ b/templates/vite-cloudflare/vite.config.ts @@ -1,10 +1,10 @@ import { vitePlugin as remix, - devCloudflareProxyVitePlugin as remixDevCloudflareProxy, + cloudflareDevProxyVitePlugin as remixCloudflareDevProxy, } from "@remix-run/dev"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [remixDevCloudflareProxy(), remix(), tsconfigPaths()], + plugins: [remixCloudflareDevProxy(), remix(), tsconfigPaths()], });