diff --git a/.changeset/spicy-buses-play.md b/.changeset/spicy-buses-play.md new file mode 100644 index 00000000000..15c6701f0ac --- /dev/null +++ b/.changeset/spicy-buses-play.md @@ -0,0 +1,5 @@ +--- +"@remix-run/dev": patch +--- + +Vite: add dev load context option to Cloudflare preset diff --git a/docs/future/vite.md b/docs/future/vite.md index 0beed9f2389..7e0ab86a631 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -126,7 +126,7 @@ To simulate the Cloudflare environment in Vite, Wrangler provides [Node proxies ```ts filename=vite.config.ts lines=[3,10] import { unstable_vitePlugin as remix, - unstable_vitePluginPresetCloudflare as cloudflare, + unstable_cloudflarePreset as cloudflare, } from "@remix-run/dev"; import { defineConfig } from "vite"; @@ -147,6 +147,34 @@ The Cloudflare team is working to improve their Node proxies to support: Vite will not use your Cloudflare Pages Functions (`functions/*`) in development as those are purely for Wrangler routing. +#### Augmenting Cloudflare load context in development + +The Cloudflare preset accepts a `getRemixDevLoadContext` function that can be used to augment the load context in development: + +```ts filename=vite.config.ts lines=[6-8] +export default defineConfig({ + plugins: [ + remix({ + presets: [ + cloudflare({ + getRemixDevLoadContext: (context) => ({ + ...context, + extra: "add on whatever else you want", + }), + }), + ], + }), + ], +}); +``` + +As the name implies, it **only augments the load context within Vite's dev server**, not within Wrangler nor in production. +This limitation exists because Vite is not yet able to delegate server code execution to other non-Node runtimes like Cloudflare's `workerd` runtime. + +To get a consistent load context across Vite, Wrangler, and production you can define a module like `get-load-context.ts` that exports +shared logic for augmenting the load context. +Then you can apply the same logic within `getRemixDevLoadContext` and within `functions/[[page]].ts`. + ## Splitting up client and server code Remix lets you write code that [runs on both the client and the server][server-vs-client]. @@ -490,7 +518,7 @@ The Remix Vite plugin only officially supports [Cloudflare Pages][cloudflare-pag ```ts filename=vite.config.ts lines=[3,8-12,15] import { unstable_vitePlugin as remix, - unstable_vitePluginPresetCloudflare as cloudflare, + unstable_cloudflarePreset as cloudflare, } from "@remix-run/dev"; import { defineConfig } from "vite"; diff --git a/integration/vite-cloudflare-test.ts b/integration/vite-cloudflare-test.ts index ea636bb03b7..0ab23dc1547 100644 --- a/integration/vite-cloudflare-test.ts +++ b/integration/vite-cloudflare-test.ts @@ -52,7 +52,13 @@ test.describe("Vite / cloudflare", async () => { "vite.config.ts": await VITE_CONFIG({ port, viteSsrResolveExternalConditions: ["workerd", "worker"], - pluginOptions: `{ presets: [(await import("@remix-run/dev")).unstable_vitePluginPresetCloudflare()] }`, + pluginOptions: `{ + presets: [ + (await import("@remix-run/dev")).unstable_cloudflarePreset({ + getRemixDevLoadContext: (ctx) => ({ ...ctx, extra: "stuff" }) + }) + ] + }`, }), "functions/[[page]].ts": ` import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages"; @@ -83,7 +89,7 @@ test.describe("Vite / cloudflare", async () => { export async function loader({ context }: LoaderFunctionArgs) { const { MY_KV } = context.env; const value = await MY_KV.get(key); - return json({ value }); + return json({ value, extra: context.extra }); } export async function action({ request, context }: ActionFunctionArgs) { @@ -105,10 +111,11 @@ test.describe("Vite / cloudflare", async () => { } export default function Index() { - const { value } = useLoaderData(); + const { value, extra } = useLoaderData(); return (

Welcome to Remix

+

Extra: {extra}

{value ? ( <>

Value: {value}

@@ -142,6 +149,7 @@ test.describe("Vite / cloudflare", async () => { await page.goto(`http://localhost:${port}/`, { waitUntil: "networkidle", }); + await expect(page.locator("[data-extra]")).toHaveText("Extra: stuff"); await expect(page.locator("[data-text]")).toHaveText("No value"); await page.getByLabel("Set value:").fill("my-value"); diff --git a/packages/remix-dev/index.ts b/packages/remix-dev/index.ts index 72bc165736d..7351c973f0b 100644 --- a/packages/remix-dev/index.ts +++ b/packages/remix-dev/index.ts @@ -6,8 +6,5 @@ export * as cli from "./cli/index"; export type { Manifest as AssetsManifest } from "./manifest"; export { getDependenciesToBundle } from "./dependencies"; -export type { Unstable_BuildManifest, Unstable_VitePluginPreset } from "./vite"; -export { - unstable_vitePlugin, - unstable_vitePluginPresetCloudflare, -} from "./vite"; +export type { Unstable_BuildManifest, Unstable_Preset } from "./vite"; +export { unstable_vitePlugin, unstable_cloudflarePreset } from "./vite"; diff --git a/packages/remix-dev/vite/index.ts b/packages/remix-dev/vite/index.ts index e906c55f0b0..e1ab108b224 100644 --- a/packages/remix-dev/vite/index.ts +++ b/packages/remix-dev/vite/index.ts @@ -4,7 +4,7 @@ import type { RemixVitePlugin } from "./plugin"; export type { BuildManifest as Unstable_BuildManifest, - VitePluginPreset as Unstable_VitePluginPreset, + Preset as Unstable_Preset, } from "./plugin"; export const unstable_vitePlugin: RemixVitePlugin = (...args) => { @@ -13,4 +13,4 @@ export const unstable_vitePlugin: RemixVitePlugin = (...args) => { return remixVitePlugin(...args); }; -export { preset as unstable_vitePluginPresetCloudflare } from "./presets/cloudflare"; +export { preset as unstable_cloudflarePreset } from "./presets/cloudflare"; diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts index 7c5daa4b5ad..5a828b85185 100644 --- a/packages/remix-dev/vite/plugin.ts +++ b/packages/remix-dev/vite/plugin.ts @@ -124,7 +124,7 @@ type ExcludedRemixConfigPresetKey = type RemixConfigPreset = Omit; -export type VitePluginPreset = { +export type Preset = { name: string; remixConfig?: () => RemixConfigPreset | Promise; remixConfigResolved?: (args: { @@ -155,7 +155,7 @@ export type VitePluginConfig = RemixEsbuildUserConfigJsdocOverrides & * An array of Remix config presets to ease integration with other platforms * and tools. */ - presets?: Array; + presets?: Array; /** * The file name of the server build output. This file * should end in a `.js` extension and should be deployed to your server. diff --git a/packages/remix-dev/vite/presets/cloudflare.ts b/packages/remix-dev/vite/presets/cloudflare.ts index 29b978c63e8..6835cb6f754 100644 --- a/packages/remix-dev/vite/presets/cloudflare.ts +++ b/packages/remix-dev/vite/presets/cloudflare.ts @@ -1,14 +1,34 @@ -import { type VitePluginPreset, setRemixDevLoadContext } from "../plugin"; +import { type Preset, setRemixDevLoadContext } from "../plugin"; -export const preset: () => VitePluginPreset = () => ({ +type GetRemixDevLoadContext = ( + loadContext: Record +) => Record | Promise>; + +const importWrangler = async () => { + try { + return await import("wrangler"); + } catch (_) { + throw Error("Could not import `wrangler`. Do you have it installed?"); + } +}; + +/** + * @param options.getRemixDevLoadContext - Augment the load context. + */ +export const preset = ( + options: { + getRemixDevLoadContext?: GetRemixDevLoadContext; + } = {} +): Preset => ({ name: "cloudflare", remixConfig: async () => { - let { getBindingsProxy } = await import("wrangler"); + let { getBindingsProxy } = await importWrangler(); let { bindings } = await getBindingsProxy(); - let loadContext = bindings && { env: bindings }; - + let loadContext: Record = { env: bindings }; + if (options.getRemixDevLoadContext) { + loadContext = await options.getRemixDevLoadContext(loadContext); + } setRemixDevLoadContext(loadContext); - return {}; }, }); diff --git a/templates/unstable-vite-cloudflare/vite.config.ts b/templates/unstable-vite-cloudflare/vite.config.ts index 64a80932aca..63d6e3516fa 100644 --- a/templates/unstable-vite-cloudflare/vite.config.ts +++ b/templates/unstable-vite-cloudflare/vite.config.ts @@ -1,6 +1,6 @@ import { unstable_vitePlugin as remix, - unstable_vitePluginPresetCloudflare as cloudflare, + unstable_cloudflarePreset as cloudflare, } from "@remix-run/dev"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths";