Skip to content

Commit 3875369

Browse files
feat: add cloudflare worker template to cli (#259)
1 parent b691796 commit 3875369

File tree

17 files changed

+1038
-194
lines changed

17 files changed

+1038
-194
lines changed

.changeset/small-islands-fold.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-frames": minor
3+
---
4+
5+
feat: add cloudflare worker template

.changeset/tiny-papayas-guess.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"frames.js": patch
3+
"docs": patch
4+
---
5+
6+
fix: add missing ctx, env, and req access to cloudflare workers handlers

docs/pages/reference/core/cloudflare-workers/index.mdx

+10-158
Original file line numberDiff line numberDiff line change
@@ -2,157 +2,33 @@
22

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

5-
## Create a new project using `wrangler`
6-
7-
:::code-group
8-
9-
```bash [npm]
10-
npm create cloudflare -- my-cf-frames --type hello-world --ts && cd ./my-cf-frames
11-
```
12-
13-
```bash [yarn]
14-
yarn create cloudflare my-cf-frames --type hello-world --ts && cd ./my-cf-frames
15-
```
16-
17-
```bash [pnpm]
18-
pnpm create cloudflare my-cf-frames --type hello-world --ts && cd ./my-cf-frames
19-
```
20-
21-
:::
22-
23-
## Install `frames.js`
24-
25-
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.
26-
275
::::steps
286

29-
### Override `@vercel/og` package with `workers-og`
30-
31-
Add following to your `package.json`.
32-
33-
:::code-group
34-
35-
```json [npm]
36-
{
37-
"overrides": {
38-
"frames.js": {
39-
"@vercel/og": "npm:workers-og@^0.0.23"
40-
}
41-
}
42-
}
43-
```
44-
45-
```json [yarn]
46-
{
47-
"resolutions": {
48-
"@vercel/og": "npm:workers-og"
49-
}
50-
}
51-
```
52-
53-
```json [pnpm]
54-
{
55-
"pnpm": {
56-
"overrides": {
57-
"@vercel/og": "npm:workers-og@^0.0.23"
58-
}
59-
}
60-
}
61-
```
62-
63-
:::
64-
65-
### Install the dependencies
66-
67-
After you have overridden the `@vercel/og` package with `workers-og`, you can install the dependencies.
7+
## Create a new project from template
688

699
:::code-group
7010

7111
```bash [npm]
72-
npm install frames.js react
12+
npm create frames -- --name my-cf-frames --template cloudflare-worker && cd ./my-cf-frames
7313
```
7414

7515
```bash [yarn]
76-
yarn add frames.js react
16+
yarn create frames --name my-cf-frames --template cloudflare-worker && cd ./my-cf-frames
7717
```
7818

7919
```bash [pnpm]
80-
pnpm add frames.js react
20+
pnpm create frames --name my-cf-frames --template cloudflare-worker && cd ./my-cf-frames
8121
```
8222

8323
:::
84-
::::
85-
86-
## Write your Frames handler
87-
88-
::::steps
89-
90-
### Delete generated file
91-
92-
Delete the `src/index.ts` file that was generated by `wrangler`.
93-
94-
### Create a file with your Frames app handler
95-
96-
Create `src/index.tsx` file and paste the following code inside.
9724

98-
```tsx [src/index.tsx]
99-
import { createFrames, Button } from "frames.js/cloudflare-workers";
25+
## Edit the generated Frame handler
10026

101-
const frames = createFrames();
102-
103-
const fetch = frames(async ({ message, searchParams }) => {
104-
const hasClicked = !!(message && searchParams.clicked);
105-
106-
return {
107-
image: <span>{hasClicked ? `Clicked ✅` : `Clicked ❌`}</span>,a
108-
buttons: !hasClicked
109-
? [
110-
<Button action="post" target={{ query: { clicked: true } }}>
111-
Click Me
112-
</Button>,
113-
]
114-
: [
115-
<Button action="post" target="/">
116-
Reset
117-
</Button>,
118-
],
119-
};
120-
});
121-
122-
export default {
123-
fetch,
124-
} satisfies ExportedHandler;
125-
```
126-
127-
### Configure Typescript to use React jsx runtime
128-
129-
Open `tsconfig.json` and change the value of `compilerOptions.tsx` to `react-jsx`.
130-
131-
```json [tsconfig.json]
132-
{
133-
"compilerOptions": {
134-
"jsx": "react-jsx"
135-
}
136-
}
137-
```
138-
139-
### Change the entrypoint for your Frames handler
140-
141-
Open `wrangler.toml` and change the value of `main` to `src/index.tsx`.
142-
143-
```toml [wrangler.toml]
144-
main = "src/index.tsx"
145-
```
146-
147-
::::
27+
Open `src/index.tsx` and edit the handler to your needs.
14828

14929
## Develop and test locally
15030

151-
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:
152-
153-
::::steps
154-
155-
#### Start the local server
31+
Run following command to start the local server and debugger to test your Frames app locally.
15632

15733
:::code-group
15834

@@ -170,35 +46,9 @@ pnpm dev
17046

17147
:::
17248

173-
#### Start the debugger
174-
175-
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`).
176-
177-
:::code-group
178-
179-
```bash [npm]
180-
npx @frames.js/debugger --url <local-url>
181-
```
182-
183-
```bash [yarn]
184-
# yarn v1 doesn't have an alternative to npx, so you have to install the debugger globally (or use npx)
185-
yarn global add @frames.js/debugger && frames --url <local-url>
186-
187-
# yarn v2
188-
yarn dlx @frames.js/debugger --url <local-url>
189-
```
190-
191-
```bash [pnpm]
192-
pnpm dlx @frames.js/debugger --url <local-url>
193-
```
194-
195-
:::
196-
197-
::::
198-
19949
## Deploy to Cloudflare Workers
20050

201-
When you tested your Frames app locally and you are ready to deploy it to Cloudflare Workers, run the following command.
51+
When you are done with development and testing, run the following command to deploy your Frames app to Cloudflare Workers.
20252

20353
:::code-group
20454

@@ -215,3 +65,5 @@ pnpm deploy
21565
```
21666

21767
:::
68+
69+
::::

packages/frames.js/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@
240240
"src"
241241
],
242242
"devDependencies": {
243+
"@cloudflare/workers-types": "^4.20240320.1",
243244
"@open-frames/types": "^0.0.6",
244245
"@remix-run/node": "^2.8.1",
245246
"@types/supertest": "^6.0.2",
@@ -255,6 +256,7 @@
255256
},
256257
"license": "MIT",
257258
"peerDependencies": {
259+
"@cloudflare/workers-types": "^4.20240320.1",
258260
"@xmtp/frames-validator": "^0.5.2",
259261
"next": "^14.1.0",
260262
"react": "^18.2.0",
@@ -266,4 +268,4 @@
266268
"protobufjs": "^7.2.6",
267269
"viem": "^2.7.8"
268270
}
269-
}
271+
}
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
export { Button, type types } from "../core";
22
import { createFrames as coreCreateFrames, types } from "../core";
3-
import { CoreMiddleware } from "../middleware";
3+
import type { CoreMiddleware } from "../middleware";
44
import { Buffer } from "node:buffer";
5+
import {
6+
type CloudflareWorkersMiddleware,
7+
cloudflareWorkersMiddleware,
8+
} from "./middleware";
9+
import type { ExportedHandlerFetchHandler } from "@cloudflare/workers-types";
10+
import type {
11+
FramesMiddleware,
12+
FramesRequestHandlerFunction,
13+
JsonValue,
14+
} from "../core/types";
15+
16+
export { cloudflareWorkersMiddleware } from "./middleware";
517

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

10-
type CreateFramesForCloudflareWorkers = types.CreateFramesFunctionDefinition<
11-
CoreMiddleware,
12-
(req: Request) => Promise<Response>
13-
>;
22+
type DefaultMiddleware<TEnv> = [
23+
...CoreMiddleware,
24+
CloudflareWorkersMiddleware<TEnv>,
25+
];
1426

1527
/**
1628
* Creates Frames instance to use with you Hono server
@@ -30,27 +42,70 @@ type CreateFramesForCloudflareWorkers = types.CreateFramesFunctionDefinition<
3042
* };
3143
* });
3244
*
45+
* @example
46+
* // With custom type for Env
47+
* import { createFrames, Button } from 'frames.js/cloudflare-workers';
48+
*
49+
* type Env = {
50+
* secret: string;
51+
* };
52+
*
53+
* const frames = createFrames<Env>();
54+
* const fetch = frames(async (ctx) => {
55+
* return {
56+
* image: <span>{ctx.cf.env.secret}</span>,
57+
* buttons: [
58+
* <Button action="post">
59+
* Click me
60+
* </Button>,
61+
* ],
62+
* };
63+
* });
64+
*
3365
* export default {
3466
* fetch,
3567
* } satisfies ExportedHandler;
3668
*/
37-
// @ts-expect-error
38-
export const createFrames: CreateFramesForCloudflareWorkers =
39-
function createFramesForCloudflareWorkers(
40-
options?: types.FramesOptions<any, any>
69+
export function createFrames<
70+
TEnv = unknown,
71+
TFramesMiddleware extends
72+
| FramesMiddleware<any, any>[]
73+
| undefined = undefined,
74+
TState extends JsonValue = JsonValue,
75+
>(
76+
options?: types.FramesOptions<TState, TFramesMiddleware>
77+
): FramesRequestHandlerFunction<
78+
TState,
79+
DefaultMiddleware<TEnv>,
80+
TFramesMiddleware,
81+
ExportedHandlerFetchHandler<TEnv, unknown>
82+
> {
83+
return function cloudflareWorkersFramesHandler<
84+
TPerRouteMiddleware extends
85+
| types.FramesMiddleware<any, any>[]
86+
| undefined = undefined,
87+
>(
88+
handler: types.FrameHandlerFunction<any, any>,
89+
handlerOptions?: types.FramesRequestHandlerFunctionOptions<TPerRouteMiddleware>
4190
) {
42-
const frames = coreCreateFrames(options);
43-
44-
return function cloudflareWorkersFramesHandler<
45-
TPerRouteMiddleware extends types.FramesMiddleware<any, any>[],
46-
>(
47-
handler: types.FrameHandlerFunction<any, any>,
48-
handlerOptions?: types.FramesRequestHandlerFunctionOptions<TPerRouteMiddleware>
49-
) {
91+
return function handleCloudflareWorkersRequest(req, env, ctx) {
92+
const frames = coreCreateFrames({
93+
...options,
94+
middleware: [
95+
cloudflareWorkersMiddleware<TEnv>({
96+
ctx,
97+
env,
98+
req,
99+
}),
100+
...(options?.middleware || []),
101+
],
102+
});
50103
const framesHandler = frames(handler, handlerOptions);
51104

52-
return async function handleCloudflareWorkersRequest(req) {
53-
return framesHandler(req);
54-
};
105+
return framesHandler(
106+
// @ts-expect-error - req is almost compatible, there are some differences in the types but it mostly fits all the needs
107+
req
108+
) as unknown as ReturnType<ExportedHandlerFetchHandler<unknown>>;
55109
};
56110
};
111+
}

0 commit comments

Comments
 (0)