cf-workers-og 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -73
- package/dist/cache.d.ts +10 -0
- package/dist/core/image-response.d.ts +5 -24
- package/dist/fonts/default-font.d.ts +4 -0
- package/dist/fonts.d.ts +1 -2
- package/dist/fonts.node.d.ts +1 -2
- package/dist/fonts.shared-D2SyBjJH.js +3052 -0
- package/dist/fonts.shared-D2SyBjJH.js.map +1 -0
- package/dist/fonts.shared.d.ts +25 -2
- package/dist/html.d.ts +2 -2
- package/dist/html.js +10 -11
- package/dist/html.js.map +1 -1
- package/dist/html.node.d.ts +2 -2
- package/dist/html.node.js +10 -11
- package/dist/html.node.js.map +1 -1
- package/dist/image-response.d.ts +2 -1
- package/dist/image-response.node.d.ts +2 -1
- package/dist/index.js +9 -9
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +1 -1
- package/dist/index.node.js +9 -9
- package/dist/index.node.js.map +1 -1
- package/dist/runtime/resvg.node.d.ts +1 -0
- package/dist/runtime/resvg.workerd.d.ts +1 -0
- package/dist/runtime/satori.node.d.ts +4 -0
- package/dist/runtime/satori.workerd.d.ts +4 -0
- package/dist/runtime/yoga.node.d.ts +1 -0
- package/dist/runtime/yoga.workerd.d.ts +1 -0
- package/dist/satori.node-DsptYmuJ.js +76 -0
- package/dist/satori.node-DsptYmuJ.js.map +1 -0
- package/dist/satori.workerd-fjuHieRr.js +68 -0
- package/dist/satori.workerd-fjuHieRr.js.map +1 -0
- package/dist/types.d.ts +3 -2
- package/dist/wasm/resvg.wasm +0 -0
- package/dist/wasm/yoga.wasm +0 -0
- package/package.json +4 -25
- package/dist/compat.d.ts +0 -11
- package/dist/compat.js +0 -20
- package/dist/compat.js.map +0 -1
- package/dist/compat.node.d.ts +0 -9
- package/dist/compat.node.js +0 -20
- package/dist/compat.node.js.map +0 -1
- package/dist/fonts.shared-RU0Lt2Ku.js +0 -135
- package/dist/fonts.shared-RU0Lt2Ku.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Generate Open Graph images on Cloudflare Workers with Node.js bindings for local Vite dev.
|
|
4
4
|
|
|
5
|
-
A
|
|
5
|
+
A Workers-first wrapper around [Satori](https://github.com/vercel/satori) + Yoga WASM
|
|
6
|
+
with PNG output via resvg that provides:
|
|
6
7
|
|
|
7
8
|
- Designed for Workers; includes Node.js bindings for local dev
|
|
8
9
|
- Works with both **Vite dev** and **Wrangler dev**
|
|
9
10
|
- Uses modern, maintained WASM dependencies
|
|
11
|
+
- SVG and PNG output (PNG via resvg WASM)
|
|
10
12
|
- Optional HTML string parsing (using battle-tested libraries)
|
|
11
|
-
- Backwards-compatible entrypoint for workers-og users (via `cf-workers-og/compat`)
|
|
12
13
|
- TypeScript support
|
|
13
14
|
|
|
15
|
+
## V3 Release Highlights (3.0.0)
|
|
16
|
+
|
|
17
|
+
- Latest Satori 0.18.3 + Yoga 3.2.1 with Workers-safe WASM initialization
|
|
18
|
+
- PNG output by default (SVG still available with `format: "svg"`)
|
|
19
|
+
- SVG -> PNG via `@resvg/resvg-wasm` with vendored WASM assets
|
|
20
|
+
- HTML strings accepted directly in `cf-workers-og/html`
|
|
21
|
+
- Bundled Roboto Regular fallback font (no more "no fonts loaded" errors)
|
|
22
|
+
|
|
14
23
|
## Installation
|
|
15
24
|
|
|
16
25
|
```bash
|
|
@@ -51,6 +60,8 @@ export default {
|
|
|
51
60
|
|
|
52
61
|
### With Google Fonts
|
|
53
62
|
|
|
63
|
+
Fonts are optional; if you don’t pass any, the bundled Roboto Regular is used.
|
|
64
|
+
|
|
54
65
|
```tsx
|
|
55
66
|
import { ImageResponse, GoogleFont, cache } from "cf-workers-og";
|
|
56
67
|
|
|
@@ -88,10 +99,11 @@ export default {
|
|
|
88
99
|
### HTML String Usage
|
|
89
100
|
|
|
90
101
|
Use HTML parsing only if you need it. For new projects, JSX is the simplest and most reliable.
|
|
91
|
-
HTML parsing is available via the opt-in `cf-workers-og/html` entrypoint
|
|
102
|
+
HTML parsing is available via the opt-in `cf-workers-og/html` entrypoint, and you can pass
|
|
103
|
+
raw HTML strings directly to `ImageResponse.create(...)`.
|
|
92
104
|
|
|
93
105
|
```typescript
|
|
94
|
-
import { ImageResponse
|
|
106
|
+
import { ImageResponse } from "cf-workers-og/html";
|
|
95
107
|
|
|
96
108
|
export default {
|
|
97
109
|
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
@@ -101,7 +113,7 @@ export default {
|
|
|
101
113
|
</div>
|
|
102
114
|
`;
|
|
103
115
|
|
|
104
|
-
return ImageResponse.create(
|
|
116
|
+
return ImageResponse.create(html, {
|
|
105
117
|
width: 1200,
|
|
106
118
|
height: 630,
|
|
107
119
|
});
|
|
@@ -127,61 +139,67 @@ Vite dev runs in Node.js, so this package ships Node bindings that should be pic
|
|
|
127
139
|
## Which entrypoint should I use?
|
|
128
140
|
|
|
129
141
|
- `cf-workers-og` (recommended): JSX input only, clean API for new users.
|
|
130
|
-
- `cf-workers-og/html`:
|
|
131
|
-
-
|
|
132
|
-
|
|
142
|
+
- `cf-workers-og/html`: accepts raw HTML strings in `ImageResponse.create`.
|
|
143
|
+
- If your bundler ignores export conditions, use explicit paths like `cf-workers-og/node`,
|
|
144
|
+
`cf-workers-og/workerd`, and their `/html` variants.
|
|
145
|
+
|
|
146
|
+
## How cf-workers-og works (and why it is reliable)
|
|
147
|
+
|
|
148
|
+
This package is intentionally small, but the pipeline is carefully tuned for the Workers runtime:
|
|
149
|
+
|
|
150
|
+
- **Inputs**: You can pass JSX directly, or (via `cf-workers-og/html`) supply a raw HTML string.
|
|
151
|
+
The HTML entrypoint uses `htmlparser2` + `style-to-js` to turn HTML into React nodes that
|
|
152
|
+
Satori understands.
|
|
153
|
+
- **Layout**: Satori performs layout with Yoga WASM. In Workers, raw-byte compilation is
|
|
154
|
+
disallowed, so Yoga is imported as a WebAssembly module and initialized once. The loader is
|
|
155
|
+
patched to accept `WebAssembly.Module`/`WebAssembly.Instance`, which is the only safe path
|
|
156
|
+
in workerd.
|
|
157
|
+
- **Rendering**: Satori outputs SVG. For PNG output, the SVG is rendered by `@resvg/resvg-wasm`,
|
|
158
|
+
again using a precompiled WASM module so there is no runtime compilation.
|
|
159
|
+
- **Assets**: WASM binaries are vendored and copied into `dist/wasm`, so they can be loaded
|
|
160
|
+
without network access at runtime.
|
|
161
|
+
- **Fonts**: A bundled Roboto Regular fallback prevents layout failure when no fonts are
|
|
162
|
+
provided. If you use `GoogleFont`, the Cloudflare Cache API is used to avoid re-fetching
|
|
163
|
+
(and requires `cache.setExecutionContext(ctx)`).
|
|
164
|
+
|
|
165
|
+
The result is a Workers-first pipeline that runs in both **Vite dev** (Node) and **Wrangler dev**
|
|
166
|
+
workerd with consistent behavior. We do not claim it is the only solution, but it is a practical
|
|
167
|
+
and well-scoped one that matches Workers' constraints without sacrificing output quality.
|
|
133
168
|
|
|
134
169
|
## Why Not workers-og?
|
|
135
170
|
|
|
136
171
|
The original [workers-og](https://github.com/syedashar1/workers-og) has fundamental issues that make it unsuitable for production use.
|
|
137
172
|
|
|
138
|
-
###
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
workers-og uses esbuild's copy loader for WASM, which is incompatible with Vite. The library only works with `wrangler dev`, not `vite dev` with `@cloudflare/vite-plugin`.
|
|
163
|
-
|
|
164
|
-
### 5. Debug Logs in Production
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
console.log("init RESVG"); // Left in production code
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### How cf-workers-og Solves These
|
|
171
|
-
|
|
172
|
-
| Issue | cf-workers-og Solution |
|
|
173
|
-
|-------|----------------------|
|
|
174
|
-
| **Outdated WASM** | Uses `@cf-wasm/og` (actively maintained, Dec 2025) with current yoga and resvg |
|
|
175
|
-
| **Brittle HTML parsing** | Uses [htmlparser2](https://github.com/fb55/htmlparser2) - a battle-tested streaming parser with no browser dependencies |
|
|
176
|
-
| **Style parsing** | Uses [style-to-js](https://www.npmjs.com/package/style-to-js) (1M+ weekly downloads) - handles all CSS edge cases |
|
|
177
|
-
| **No Vite support** | Works with `@cloudflare/vite-plugin` via proper conditional exports for node/workerd |
|
|
178
|
-
| **Debug logs** | Clean production code |
|
|
173
|
+
### The blockers we hit in Workers
|
|
174
|
+
|
|
175
|
+
- **WASM byte compilation is disallowed** in workerd, so loaders that call
|
|
176
|
+
`WebAssembly.instantiate(bytes)` fail at runtime.
|
|
177
|
+
- **Outdated Yoga WASM**: `yoga-wasm-web@0.3.3` is unmaintained and incompatible
|
|
178
|
+
with Yoga 3’s `SINGLE_FILE=1` base64 output (also not Workers-safe).
|
|
179
|
+
- **Brittle HTML/CSS parsing**: regex-based parsing breaks on real-world CSS.
|
|
180
|
+
- **Build tooling gap**: workers-og is esbuild-only and doesn’t play well with
|
|
181
|
+
Vite library builds and export conditions.
|
|
182
|
+
|
|
183
|
+
### How cf-workers-og v3 solves this (engineering highlights)
|
|
184
|
+
|
|
185
|
+
- **Latest Satori + Yoga**: uses `satori@0.18.3` with `yoga-layout@3.2.1` and a
|
|
186
|
+
patched loader that accepts `WebAssembly.Module/Instance`.
|
|
187
|
+
- **Workers-safe WASM**: Yoga + resvg WASM are vendored and imported as modules,
|
|
188
|
+
never compiled from raw bytes at runtime.
|
|
189
|
+
- **PNG by default**: SVG → PNG via `@resvg/resvg-wasm`, with `format: "svg"`
|
|
190
|
+
available when you want raw SVG.
|
|
191
|
+
- **Zero-config fonts**: bundled Roboto Regular so layout never fails when users
|
|
192
|
+
omit fonts; custom fonts still fully supported.
|
|
193
|
+
- **Reliable HTML/CSS**: `htmlparser2` + `style-to-js` handle edge cases that
|
|
194
|
+
workers-og’s regex parsing misses.
|
|
195
|
+
- **Vite + Wrangler ready**: proper export conditions, wasm assets copied to
|
|
196
|
+
`dist/wasm`, and no bundler-specific hacks required.
|
|
179
197
|
|
|
180
198
|
### Design Decisions
|
|
181
199
|
|
|
182
|
-
**Why
|
|
200
|
+
**Why Satori + Yoga instead of building from scratch?**
|
|
183
201
|
|
|
184
|
-
WASM on Cloudflare Workers is genuinely hard. Workers cannot compile WASM from arbitrary data blobs - you must import as modules.
|
|
202
|
+
WASM on Cloudflare Workers is genuinely hard. Workers cannot compile WASM from arbitrary data blobs - you must import as modules. Satori already provides a well-tested SVG renderer, so we focus on making its Yoga WASM initialization work in Workers.
|
|
185
203
|
|
|
186
204
|
**Why htmlparser2 instead of html-react-parser?**
|
|
187
205
|
|
|
@@ -191,7 +209,9 @@ WASM on Cloudflare Workers is genuinely hard. Workers cannot compile WASM from a
|
|
|
191
209
|
|
|
192
210
|
### Entry points
|
|
193
211
|
|
|
194
|
-
Use `cf-workers-og` for Workers with JSX input
|
|
212
|
+
Use `cf-workers-og` for Workers with JSX input and `cf-workers-og/html` for HTML strings.
|
|
213
|
+
If your bundler ignores export conditions, use explicit paths like `cf-workers-og/node` or
|
|
214
|
+
`cf-workers-og/workerd` (and the `/html` variants).
|
|
195
215
|
|
|
196
216
|
### `ImageResponse.create(element, options)`
|
|
197
217
|
|
|
@@ -203,7 +223,7 @@ const response = await ImageResponse.create(element, {
|
|
|
203
223
|
width: 1200, // Default: 1200
|
|
204
224
|
height: 630, // Default: 630
|
|
205
225
|
format: "png", // 'png' | 'svg', Default: 'png'
|
|
206
|
-
fonts: [], // Font configurations
|
|
226
|
+
fonts: [], // Font configurations (defaults to bundled Roboto Regular)
|
|
207
227
|
emoji: "twemoji", // Emoji provider
|
|
208
228
|
debug: false, // Disable caching for debugging
|
|
209
229
|
headers: {}, // Additional response headers
|
|
@@ -212,14 +232,6 @@ const response = await ImageResponse.create(element, {
|
|
|
212
232
|
});
|
|
213
233
|
```
|
|
214
234
|
|
|
215
|
-
### `parseHtml(html)`
|
|
216
|
-
|
|
217
|
-
Parse an HTML string into React elements for Satori. Exported from `cf-workers-og/html` and `cf-workers-og/compat`.
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
const element = parseHtml('<div style="display: flex;">Hello</div>');
|
|
221
|
-
```
|
|
222
|
-
|
|
223
235
|
### `GoogleFont(family, options)`
|
|
224
236
|
|
|
225
237
|
Load a Google Font.
|
|
@@ -261,13 +273,13 @@ Note that cache is only used if you use GoogleFonts. Otherwise it is a drop-in r
|
|
|
261
273
|
|
|
262
274
|
```diff
|
|
263
275
|
- import { ImageResponse } from 'workers-og';
|
|
264
|
-
+ import { ImageResponse, cache } from 'cf-workers-og
|
|
276
|
+
+ import { ImageResponse, cache } from 'cf-workers-og';
|
|
265
277
|
|
|
266
278
|
export default {
|
|
267
279
|
async fetch(request, env, ctx) {
|
|
268
280
|
+ cache.setExecutionContext(ctx);
|
|
269
281
|
|
|
270
|
-
return
|
|
282
|
+
return ImageResponse.create(element, options);
|
|
271
283
|
}
|
|
272
284
|
};
|
|
273
285
|
```
|
|
@@ -276,21 +288,20 @@ For HTML string users:
|
|
|
276
288
|
|
|
277
289
|
```diff
|
|
278
290
|
- return new ImageResponse(htmlString, options);
|
|
279
|
-
+ import { ImageResponse
|
|
280
|
-
+ return ImageResponse.create(
|
|
291
|
+
+ import { ImageResponse } from 'cf-workers-og/html';
|
|
292
|
+
+ return ImageResponse.create(htmlString, options);
|
|
281
293
|
```
|
|
282
294
|
|
|
283
295
|
## Architecture
|
|
284
296
|
|
|
285
|
-
This package is a **thin wrapper**
|
|
286
|
-
|
|
287
|
-
| Package | Size | Purpose |
|
|
288
|
-
|---------|------|---------|
|
|
289
|
-
| `@cf-wasm/resvg` | 2.4 MB | SVG → PNG rendering (WASM) |
|
|
290
|
-
| `@cf-wasm/satori` | 87 KB | Flexbox layout engine (WASM) |
|
|
291
|
-
| `htmlparser2` | ~42 KB | HTML parsing (pure JS) |
|
|
297
|
+
This package is a **thin wrapper** around Satori + Yoga. The heavy lifting is done by:
|
|
292
298
|
|
|
293
|
-
|
|
299
|
+
| Package | Purpose | Notes |
|
|
300
|
+
|---------|---------|-------|
|
|
301
|
+
| `satori` | SVG rendering | Outputs SVG |
|
|
302
|
+
| `yoga-layout` | Flexbox layout (WASM) | Yoga 3; wasm is vendored for Workers |
|
|
303
|
+
| `@resvg/resvg-wasm` | SVG → PNG | WASM renderer |
|
|
304
|
+
| `htmlparser2` | HTML parsing (pure JS) | Optional entrypoint |
|
|
294
305
|
|
|
295
306
|
## License
|
|
296
307
|
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ExecutionContext = {
|
|
2
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
3
|
+
};
|
|
4
|
+
declare class CacheUtils {
|
|
5
|
+
private waitUntilFn?;
|
|
6
|
+
setExecutionContext(ctx: ExecutionContext): void;
|
|
7
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const cache: CacheUtils;
|
|
10
|
+
export {};
|
|
@@ -1,34 +1,15 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
type
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
format: "png" | "svg";
|
|
7
|
-
fonts: FontInput[];
|
|
8
|
-
emoji?: EmojiType;
|
|
9
|
-
};
|
|
10
|
-
type CfImageResponse = {
|
|
11
|
-
async: (element: ReactNode, options: CfImageResponseOptions) => Promise<Response>;
|
|
12
|
-
};
|
|
2
|
+
import type { ImageResponseOptions } from "../types";
|
|
3
|
+
import type { SatoriOptions } from "satori/standalone";
|
|
13
4
|
type ParseHtml = (html: string) => ReactNode;
|
|
14
5
|
type CreateImageResponseConfig = {
|
|
15
|
-
|
|
6
|
+
renderSvg: (element: ReactNode, options: SatoriOptions) => Promise<string>;
|
|
7
|
+
renderPng?: (element: ReactNode, options: SatoriOptions) => Promise<Uint8Array>;
|
|
16
8
|
parseHtml?: ParseHtml;
|
|
17
|
-
compatConstructor?: boolean;
|
|
18
9
|
};
|
|
19
10
|
type ImageInput = ReactNode | string;
|
|
20
11
|
export type ImageResponseClass<Input extends ImageInput> = {
|
|
21
|
-
new (element: Input, options?: ImageResponseOptions): Response;
|
|
22
|
-
create(element: Input, options?: ImageResponseOptions): Promise<Response>;
|
|
23
|
-
};
|
|
24
|
-
export type ImageResponseCompatClass<Input extends ImageInput> = {
|
|
25
|
-
new (element: Input, options?: ImageResponseOptions): Promise<Response>;
|
|
26
12
|
create(element: Input, options?: ImageResponseOptions): Promise<Response>;
|
|
27
13
|
};
|
|
28
|
-
export declare function createImageResponseClass<Input extends ImageInput>(config: CreateImageResponseConfig
|
|
29
|
-
compatConstructor: true;
|
|
30
|
-
}): ImageResponseCompatClass<Input>;
|
|
31
|
-
export declare function createImageResponseClass<Input extends ImageInput>(config: CreateImageResponseConfig & {
|
|
32
|
-
compatConstructor?: false;
|
|
33
|
-
}): ImageResponseClass<Input>;
|
|
14
|
+
export declare function createImageResponseClass<Input extends ImageInput>(config: CreateImageResponseConfig): ImageResponseClass<Input>;
|
|
34
15
|
export {};
|
package/dist/fonts.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { GoogleFont, CustomFont } from "
|
|
2
|
-
export { loadGoogleFont, createFontConfig } from "./fonts.shared";
|
|
1
|
+
export { GoogleFont, CustomFont, loadGoogleFont, createFontConfig, } from "./fonts.shared";
|
package/dist/fonts.node.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { GoogleFont, CustomFont } from "
|
|
2
|
-
export { loadGoogleFont, createFontConfig } from "./fonts.shared";
|
|
1
|
+
export { GoogleFont, CustomFont, loadGoogleFont, createFontConfig, } from "./fonts.shared";
|