Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vite: Cloudflare Proxy plugin #8749

Merged
merged 24 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions docs/future/presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
pcattori marked this conversation as resolved.
Show resolved Hide resolved
import { somePreset } from "some-preset";

export default defineConfig({
plugins: [
remix({
presets: [cloudflare(getBindingsProxy)],
presets: [somePreset()],
}),
],
ssr: {
resolve: {
externalConditions: ["workerd", "worker"],
},
},
});
```

Expand Down
133 changes: 70 additions & 63 deletions docs/future/vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,56 +109,76 @@ 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...
};
```

<docs-info>
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:
<docs-info>

- [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.

</docs-info>

#### 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]

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<PlatformProxy<Env>, "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`:

```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,
Expand All @@ -167,68 +187,56 @@ 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;
};
type Cloudflare = Omit<PlatformProxy<Env>, "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<AppLoadContext> => {
export const getLoadContext: GetLoadContext = ({
context,
}) => {
return {
...context,
extra: "stuff",
};
};
```

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";
Expand Down Expand Up @@ -1276,11 +1284,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
Expand Down
42 changes: 42 additions & 0 deletions integration/helpers/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -197,6 +222,10 @@ type Fixtures = {
port: number;
cwd: string;
}>;
wranglerPagesDev: (files: Files) => Promise<{
port: number;
cwd: string;
}>;
};

export const test = base.extend<Fixtures>({
Expand Down Expand Up @@ -240,6 +269,19 @@ export const test = base.extend<Fixtures>({
});
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(
Expand Down
2 changes: 1 addition & 1 deletion integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Loading
Loading