Skip to content

Commit

Permalink
docs: add cloudflare adapter docs and guide (#254)
Browse files Browse the repository at this point in the history
  • Loading branch information
michalkvasnicak authored Mar 27, 2024
1 parent 9bbaef3 commit 63b6c6b
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changeset/heavy-nails-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"frames.js": minor
"docs": minor
---

feat: cloudflare workers adapter
4 changes: 2 additions & 2 deletions docs/pages/guides/deployment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ description: "A guide on shipping your frames.js frame to production"

Frames.js works in most javascript frameworks or libraries, and should work out of the box with whereever you would normally deploy those.

We suggest deploying your frames to Vercel, but Docker, AWS or other platforms are compatible.
We suggest deploying your frames to Vercel, but [Cloudflare Workers](/reference/core/cloudflare-workers), Docker, AWS or other platforms are compatible.

## Ensure you set up the correct `env` variables

For the starter, `NEXT_PUBLIC_HOST` should be the full url (including https://) of the homepage.

## Vercel Deployments

Run the `vercel` command-line tool from your frame's folder to deploy your frame to vercel.
Run the `vercel` command-line tool from your frame's folder to deploy your frame to vercel.
6 changes: 0 additions & 6 deletions docs/pages/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ import { HomePage } from "vocs/components";
<HomePage.Button href="https://github.com/framesjs/frames.js">
GitHub
</HomePage.Button>
<HomePage.Button href="#quickstart" variant="accent">
Quickstart
</HomePage.Button>
<HomePage.Button href="https://github.com/framesjs/frames.js">
GitHub
</HomePage.Button>
</HomePage.Buttons>
</HomePage.Root>

Expand Down
186 changes: 186 additions & 0 deletions docs/pages/reference/core/cloudflare-workers/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Cloudflare Workers integration

Frames.js can be easily deployed to [Cloudflare Workers](https://workers.cloudflare.com).

## Create a new project using `wrangler`

:::code-group

```bash [npm]
npm create cloudflare -- my-cf-frames --type hello-world --ts && cd ./my-cf-frames
```

```bash [yarn]
yarn create cloudflare my-cf-frames --type hello-world --ts && cd ./my-cf-frames
```

```bash [pnpm]
pnpm create cloudflare my-cf-frames --type hello-world --ts && cd ./my-cf-frames
```

:::

## Install `frames.js`

In order for `frames.js` to work properly in [Cloudflare Workers](https://workers.cloudflare.com) you must replace the `@vercel/og` dependency with `workers-og`. Follow following steps to override the dependency.

::::steps

### Override `@vercel/og` package with `workers-og`

Add following to your `package.json`.

:::code-group

```json [npm]
{
"overrides": {
"frames.js": {
"@vercel/og": "npm:workers-og@^0.0.23"
}
}
}
```

```json [yarn]
{
"resolutions": {
"@vercel/og": "npm:workers-og"
}
}
```

```json [pnpm]
{
"pnpm": {
"overrides": {
"@vercel/og": "npm:workers-og@^0.0.23"
}
}
}
```

:::

### Install the dependencies

After you have overridden the `@vercel/og` package with `workers-og`, you can install the dependencies.

:::code-group

```bash [npm]
npm install frames.js
```

```bash [yarn]
yarn add frames.js
```

```bash [pnpm]
pnpm add frames.js
```

:::
::::

## Write your Frames handler

Open the `src/index.ts` file and replace its content with the following code.

```tsx
import { createFrames, Button } from "frames.js/cloudflare-workers";

const frames = createFrames();
const handleRequest = frames(async (ctx) => {
const hasClicked = !!(ctx.message && ctx.searchParams.clicked);

return {
image: <span>Clicked: {hasClicked ? "Yes" : "No"}</span>,
buttons: hasClicked
? [
<Button action="post" target={{ query: { clicked: true } }}>
Click me
</Button>,
]
: [
<Button action="post" target="/">
Reset
</Button>,
],
};
});

export default {
fetch: handleRequest,
};
```

## Develop and test locally

You can test your Cloudflare Worker locally using `wrangler dev` and our [debugger](/guides/debugger#local-debugger-cli). Follow these steps to start developing locally:

::::steps

#### Start the local server

:::code-group

```bash [npm]
npm run dev
```

```bash [yarn]
yarn dev
```

```bash [pnpm]
pnpm dev
```

:::

#### Start the debugger

After you started the local server, you can start the debugger by running the following command where you replace `<local-url>` with a URL on which local server is running (see the output of above command, e.g. `http://localhost:8787`).

:::code-group

```bash [npm]
npx @frames.js/debugger --url <local-url>
```

```bash [yarn]
# yarn v1 doesn't have an alternative to npx, so you have to install the debugger globally (or use npx)
yarn global add @frames.js/debugger && frames --url <local-url>

# yarn v2
yarn dlx @frames.js/debugger --url <local-url>
```

```bash [pnpm]
pnpm dlx @frames.js/debugger --url <local-url>
```

:::

::::

## Deploy to Cloudflare Workers

When you tested your Frames app locally and you are ready to deploy it to Cloudflare Workers, run the following command.

:::code-group

```bash [npm]
npm run deploy
```

```bash [yarn]
yarn deploy
```

```bash [pnpm]
pnpm deploy
```

:::
4 changes: 4 additions & 0 deletions docs/vocs.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ const sidebar = [
text: "Remix",
link: "/reference/core/remix",
},
{
text: 'Cloudflare Workers',
link: '/reference/core/cloudflare-workers'
}
],
},
{
Expand Down
10 changes: 10 additions & 0 deletions packages/frames.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@
"types": "./dist/express/index.d.cts",
"default": "./dist/express/index.cjs"
}
},
"./cloudflare-workers": {
"import": {
"types": "./dist/cloudflare-workers/index.d.ts",
"default": "./dist/cloudflare-workers/index.js"
},
"require": {
"types": "./dist/cloudflare-workers/index.d.cts",
"default": "./dist/cloudflare-workers/index.cjs"
}
}
},
"files": [
Expand Down
27 changes: 27 additions & 0 deletions packages/frames.js/src/cloudflare-workers/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as lib from ".";

describe("cloudflare workers adapter", () => {
it.each(["createFrames", "Button"])("exports %s", (exportName) => {
expect(lib).toHaveProperty(exportName);
});

it("correctly integrates with Cloudflare Workers", async () => {
const frames = lib.createFrames();
const handler = frames(async (ctx) => {
expect(ctx.request.url).toBe("http://localhost:3000/");

return {
image: <span>Test</span>,
buttons: [<lib.Button action="post">Click me</lib.Button>],
};
});

const request = new Request("http://localhost:3000");

// @ts-expect-error - expects fetcher property on request but it is not used by our lib
const response = await handler(request, {}, {});

expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe("text/html");
});
});
56 changes: 56 additions & 0 deletions packages/frames.js/src/cloudflare-workers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export { Button, type types } from "../core";
import { createFrames as coreCreateFrames, types } from "../core";
import { CoreMiddleware } from "../middleware";
import { Buffer } from "node:buffer";

// make Buffer available on globalThis so it is compatible with cloudflare workers
// eslint-disable-next-line no-undef
globalThis.Buffer = Buffer;

type CreateFramesForCloudflareWorkers = types.CreateFramesFunctionDefinition<
CoreMiddleware,
(req: Request) => Promise<Response>
>;

/**
* Creates Frames instance to use with you Hono server
*
* @example
* import { createFrames, Button } from 'frames.js/cloudflare-workers';
*
* const frames = createFrames();
* const fetch = frames(async (ctx) => {
* return {
* image: <span>Test</span>,
* buttons: [
* <Button action="post">
* Click me
* </Button>,
* ],
* };
* });
*
* export default {
* fetch,
* } satisfies ExportedHandler;
*/
// @ts-expect-error
export const createFrames: CreateFramesForCloudflareWorkers =
function createFramesForCloudflareWorkers(
options?: types.FramesOptions<any, any>
) {
const frames = coreCreateFrames(options);

return function cloudflareWorkersFramesHandler<
TPerRouteMiddleware extends types.FramesMiddleware<any, any>[],
>(
handler: types.FrameHandlerFunction<any, any>,
handlerOptions?: types.FramesRequestHandlerFunctionOptions<TPerRouteMiddleware>
) {
const framesHandler = frames(handler, handlerOptions);

return async function handleCloudflareWorkersRequest(req) {
return framesHandler(req);
};
};
};
3 changes: 3 additions & 0 deletions packages/frames.js/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { defineConfig } from "tsup";
import glob from "fast-glob";

export default defineConfig({
// this is necessary for cloudfare workers adapter to be able to import
// buffer from cloudflare compatiblity layer
external: ["node:buffer"],
dts: true,
format: ["cjs", "esm"],
clean: true,
Expand Down

0 comments on commit 63b6c6b

Please sign in to comment.