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.
Files changed (44) hide show
  1. package/README.md +84 -73
  2. package/dist/cache.d.ts +10 -0
  3. package/dist/core/image-response.d.ts +5 -24
  4. package/dist/fonts/default-font.d.ts +4 -0
  5. package/dist/fonts.d.ts +1 -2
  6. package/dist/fonts.node.d.ts +1 -2
  7. package/dist/fonts.shared-D2SyBjJH.js +3052 -0
  8. package/dist/fonts.shared-D2SyBjJH.js.map +1 -0
  9. package/dist/fonts.shared.d.ts +25 -2
  10. package/dist/html.d.ts +2 -2
  11. package/dist/html.js +10 -11
  12. package/dist/html.js.map +1 -1
  13. package/dist/html.node.d.ts +2 -2
  14. package/dist/html.node.js +10 -11
  15. package/dist/html.node.js.map +1 -1
  16. package/dist/image-response.d.ts +2 -1
  17. package/dist/image-response.node.d.ts +2 -1
  18. package/dist/index.js +9 -9
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.node.d.ts +1 -1
  21. package/dist/index.node.js +9 -9
  22. package/dist/index.node.js.map +1 -1
  23. package/dist/runtime/resvg.node.d.ts +1 -0
  24. package/dist/runtime/resvg.workerd.d.ts +1 -0
  25. package/dist/runtime/satori.node.d.ts +4 -0
  26. package/dist/runtime/satori.workerd.d.ts +4 -0
  27. package/dist/runtime/yoga.node.d.ts +1 -0
  28. package/dist/runtime/yoga.workerd.d.ts +1 -0
  29. package/dist/satori.node-DsptYmuJ.js +76 -0
  30. package/dist/satori.node-DsptYmuJ.js.map +1 -0
  31. package/dist/satori.workerd-fjuHieRr.js +68 -0
  32. package/dist/satori.workerd-fjuHieRr.js.map +1 -0
  33. package/dist/types.d.ts +3 -2
  34. package/dist/wasm/resvg.wasm +0 -0
  35. package/dist/wasm/yoga.wasm +0 -0
  36. package/package.json +4 -25
  37. package/dist/compat.d.ts +0 -11
  38. package/dist/compat.js +0 -20
  39. package/dist/compat.js.map +0 -1
  40. package/dist/compat.node.d.ts +0 -9
  41. package/dist/compat.node.js +0 -20
  42. package/dist/compat.node.js.map +0 -1
  43. package/dist/fonts.shared-RU0Lt2Ku.js +0 -135
  44. 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 thin wrapper around [@cf-wasm/og](https://github.com/fineshopdesign/cf-wasm) that provides:
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. For workers-og constructor compatibility, use `cf-workers-og/compat`.
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, parseHtml } from "cf-workers-og/html";
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(parseHtml(html), {
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`: adds `parseHtml` and accepts HTML strings in `ImageResponse.create`.
131
- - `cf-workers-og/compat`: legacy constructor behavior and HTML strings for migrating from workers-og.
132
- - If your bundler ignores export conditions, use explicit paths like `cf-workers-og/node`, `cf-workers-og/workerd`, and their `/html` or `/compat` variants.
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
- ### 1. Outdated WASM Dependencies
139
-
140
- workers-og uses `yoga-wasm-web@0.3.3` which has been **unmaintained since 2023**. The Yoga project moved to Yoga 3.0 with a `SINGLE_FILE=1` compilation that inlines WASM as base64 - incompatible with Cloudflare Workers' module-based WASM loading. Satori itself now uses an internal patched version instead of `yoga-wasm-web`, fragmenting the ecosystem.
141
-
142
- ### 2. Brittle HTML Parsing
143
-
144
- The HTML parser builds JSON via string concatenation:
145
-
146
- ```typescript
147
- // workers-og approach - error-prone
148
- vdomStr += `{"type":"${element.tagName}", "props":{${attrs}"children": [`;
149
- ```
150
-
151
- The code comments even acknowledge: *"very error prone. So it might need more hardening"*. The `sanitizeJSON` function only handles basic escapes, missing edge cases.
152
-
153
- ### 3. Style Parsing Fails on Complex CSS
154
-
155
- workers-og uses regex to parse CSS: `;(?![^(]*\))`. This fails on:
156
- - Nested parentheses: `calc(100% - (10px + 5px))`
157
- - Data URIs: `url(data:image/png;base64,...)`
158
- - Complex CSS with multiple function calls
159
-
160
- ### 4. No Vite Support
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 wrap @cf-wasm/og instead of building from scratch?**
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. Rather than maintain custom yoga-wasm builds, we wrap `@cf-wasm/og` which already solves Vite + Wrangler compatibility.
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, `cf-workers-og/html` for HTML strings, and `cf-workers-og/compat` only when migrating from workers-og. If your bundler ignores export conditions, use explicit paths like `cf-workers-og/node` or `cf-workers-og/workerd` (and the `/html` or `/compat` variants).
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/compat';
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 new ImageResponse(element, options);
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, parseHtml } from 'cf-workers-og/html';
280
- + return ImageResponse.create(parseHtml(htmlString), options);
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** (6 KB) around `@cf-wasm/og`. The heavy lifting is done by:
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
- The WASM files are installed as transitive dependencies - they're not bundled in this package.
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
 
@@ -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 { EmojiType, FontInput, ImageResponseOptions } from "../types";
3
- type CfImageResponseOptions = {
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
- cfImageResponse: CfImageResponse;
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 {};
@@ -0,0 +1,4 @@
1
+ export declare const DEFAULT_FONT_NAME = "Roboto";
2
+ export declare const DEFAULT_FONT_WEIGHT = 400;
3
+ export declare const DEFAULT_FONT_STYLE = "normal";
4
+ export declare function getDefaultFontData(): ArrayBuffer;
package/dist/fonts.d.ts CHANGED
@@ -1,2 +1 @@
1
- export { GoogleFont, CustomFont } from "@cf-wasm/og/workerd";
2
- export { loadGoogleFont, createFontConfig } from "./fonts.shared";
1
+ export { GoogleFont, CustomFont, loadGoogleFont, createFontConfig, } from "./fonts.shared";
@@ -1,2 +1 @@
1
- export { GoogleFont, CustomFont } from "@cf-wasm/og/node";
2
- export { loadGoogleFont, createFontConfig } from "./fonts.shared";
1
+ export { GoogleFont, CustomFont, loadGoogleFont, createFontConfig, } from "./fonts.shared";