@ethercorps/sveltekit-og 4.2.0 → 4.3.0-beta.1

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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -193
  3. package/dist/fonts.d.ts +5 -13
  4. package/dist/fonts.js +28 -24
  5. package/dist/helpers/create.d.ts +7 -5
  6. package/dist/helpers/create.js +38 -35
  7. package/dist/helpers/defaults.d.ts +4 -4
  8. package/dist/helpers/defaults.js +28 -18
  9. package/dist/helpers/emoji.d.ts +1 -1
  10. package/dist/helpers/emoji.js +31 -21
  11. package/dist/helpers/error-handler.d.ts +37 -0
  12. package/dist/helpers/error-handler.js +75 -0
  13. package/dist/helpers/logger.d.ts +7 -0
  14. package/dist/helpers/logger.js +21 -0
  15. package/dist/helpers/response.d.ts +30 -0
  16. package/dist/helpers/response.js +52 -0
  17. package/dist/helpers/to-html.d.ts +10 -0
  18. package/dist/helpers/to-html.js +10 -0
  19. package/dist/helpers/toJSX.d.ts +3 -0
  20. package/dist/helpers/toJSX.js +11 -0
  21. package/dist/helpers/utils.d.ts +2 -0
  22. package/dist/helpers/utils.js +25 -0
  23. package/dist/image-response.d.ts +2 -2
  24. package/dist/image-response.js +13 -21
  25. package/dist/plugin.js +6 -6
  26. package/dist/providers/instances.d.ts +3 -3
  27. package/dist/providers/instances.js +43 -15
  28. package/dist/providers/resvg/edge.d.ts +1 -1
  29. package/dist/providers/resvg/edge.js +10 -6
  30. package/dist/providers/resvg/node.d.ts +1 -1
  31. package/dist/providers/resvg/node.js +18 -9
  32. package/dist/providers/satori/edge.d.ts +6 -0
  33. package/dist/providers/satori/edge.js +12 -0
  34. package/dist/providers/satori/node.d.ts +1 -1
  35. package/dist/providers/satori/node.js +4 -4
  36. package/dist/takumi/fonts.d.ts +16 -0
  37. package/dist/takumi/fonts.js +15 -0
  38. package/dist/takumi/image-response.d.ts +6 -0
  39. package/dist/takumi/image-response.js +27 -0
  40. package/dist/takumi/index.d.ts +5 -0
  41. package/dist/takumi/index.js +4 -0
  42. package/dist/takumi/render.d.ts +4 -0
  43. package/dist/takumi/render.js +23 -0
  44. package/dist/takumi/renderer.d.ts +7 -0
  45. package/dist/takumi/renderer.js +39 -0
  46. package/dist/takumi/types.d.ts +64 -0
  47. package/dist/takumi/types.js +1 -0
  48. package/dist/types.d.ts +10 -14
  49. package/package.json +73 -17
  50. package/dist/providers/resvg/resvg.wasm +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Ethercorps
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,224 +1,70 @@
1
- # SvelteKit Open Graph Image Generation
2
-
3
- Dynamically generate Open Graph images from an HTML+CSS template or Svelte component using fast and efficient conversion from HTML > SVG > PNG. Based on [Satori](https://github.com/vercel/satori#documentation). No headless browser required.
1
+ [![npm version](https://flat.badgen.net/npm/v/@ethercorps/sveltekit-og?color=orange)](https://npmjs.com/package/bits-ui)
2
+ [![npm downloads](https://flat.badgen.net/npm/dm/@ethercorps/sveltekit-og?color=orange)](https://npmjs.com/package/@ethercorps/sveltekit-og)
3
+ [![license](https://flat.badgen.net/github/license/ethercorps/sveltekit-og?color=orange)](https://github.com/ethercorps/sveltekit-og/blob/main/LICENSE)
4
4
 
5
- ## Docs
6
- - With sveltekit-og@4, we have [official documentation](https://sveltekit-og.dev).
5
+ # SvelteKit Open Graph Image Generation
7
6
 
8
- ## Installation
7
+ Dynamically generate Open Graph images from an HTML+CSS template or Svelte component. No headless browser required.
9
8
 
10
- ```bash
11
- pnpm install @ethercorps/sveltekit-og
12
- ```
9
+ Pick the rendering engine that fits your needs:
13
10
 
14
- ## Usage
11
+ - **Satori** (default) — HTML → SVG → PNG, based on [Satori](https://github.com/vercel/satori#documentation).
12
+ - **[Takumi](https://takumi.kane.tw)** — a Rust/WASM engine with more output formats (`webp`, `jpeg`, `ico`, `svg`, …) and a built-in font, available from `@ethercorps/sveltekit-og/takumi` (v4.3.0+).
15
13
 
16
- ### Vite (Recommended)
14
+ ## Table of Contents
17
15
 
18
- - Add vite plugin
16
+ - [SvelteKit Open Graph Image Generation](#sveltekit-open-graph-image-generation)
17
+ - [Table of Contents](#table-of-contents)
18
+ - [Docs](#docs)
19
+ - [Installation](#installation)
20
+ - [Usage](#usage)
21
+ - [Examples](#examples)
22
+ - [Contributing](#contributing)
23
+ - [Changelog](#changelog)
24
+ - [License](#license)
25
+ - [Acknowledgements](#acknowledgements)
26
+ - [Authors](#authors)
27
+ - [Contributors](#contributors)
19
28
 
20
- ```typescript title="vite.cofig.js"
21
- import { sveltekit } from '@sveltejs/kit/vite';
22
- import { sveltekitOG } from '@ethercorps/sveltekit-og/plugin';
23
- const config = {
24
- plugins: [sveltekit(), sveltekitOG()]
25
- };
26
-
27
- export default config;
28
- ```
29
+ ## Docs
29
30
 
30
- ### Rollup (will be deprecated in v5)
31
- - Add `rollupWasm` to `build.rollupOptions.plugins` in `vite.cofig.js` file.
32
- - For more information, check [docs](https://sveltekit-og.dev/docs/getting-started)
33
-
34
- ```ts title="vite.cofig.js"
35
- import { sveltekit } from '@sveltejs/kit/vite';
36
- import { defineConfig } from 'vitest/config';
37
- import { rollupWasm } from '@ethercorps/sveltekit-og/plugin';
38
-
39
- export default defineConfig({
40
- plugins: [sveltekit()],
41
- build: {
42
- rollupOptions: {
43
- plugins: [rollupWasm()],
44
- }
45
- }
46
- });
47
- ```
31
+ For more detailed information and advanced usage, please refer to the [official documentation](https://sveltekit-og.dev).
48
32
 
49
- - For node adapter update config with `rollupWasm`
50
- - Check node runtime [docs](https://sveltekit-og.dev/docs/runtime/node)
51
-
52
- ```ts title="vite.cofig.js"
53
- import { sveltekit } from '@sveltejs/kit/vite';
54
- import { defineConfig } from 'vitest/config';
55
- import { rollupWasm } from '@ethercorps/sveltekit-og/plugin';
56
-
57
- export default defineConfig({
58
- plugins: [sveltekit()],
59
- build: {
60
- rollupOptions: {
61
- plugins: [
62
- rollupWasm({ esmImport: false })
63
- ],
64
- }
65
- }
66
- });
67
- ```
33
+ ## Installation
68
34
 
69
- - Create a file at `/src/routes/og/+server.ts`. Alternatively, you can use JavaScript by removing the types from this example.
70
-
71
- ```typescript
72
- // src/routes/og/+server.ts
73
- import { ImageResponse } from '@ethercorps/sveltekit-og';
74
- import { RequestHandler } from './$types';
75
-
76
- const template = `
77
- <div tw="bg-gray-50 flex w-full h-full items-center justify-center">
78
- <div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
79
- <h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
80
- <span>Ready to dive in?</span>
81
- <span tw="text-indigo-600">Start your free trial today.</span>
82
- </h2>
83
- <div tw="mt-8 flex md:mt-0">
84
- <div tw="flex rounded-md shadow">
85
- <a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white">Get started</a>
86
- </div>
87
- <div tw="ml-3 flex rounded-md shadow">
88
- <a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600">Learn more</a>
89
- </div>
90
- </div>
91
- </div>
92
- </div>
93
- `;
94
-
95
- const fontFile = await fetch('https://og-playground.vercel.app/inter-latin-ext-400-normal.woff');
96
- const fontData: ArrayBuffer = await fontFile.arrayBuffer();
97
-
98
- export const GET: RequestHandler = async () => {
99
- return await new ImageResponse(template, {
100
- height: 630,
101
- width: 1200,
102
- fonts: [
103
- {
104
- name: 'Inter Latin',
105
- data: fontData,
106
- weight: 400
107
- }
108
- ]
109
- });
110
- };
35
+ ```bash
36
+ pnpm install @ethercorps/sveltekit-og
111
37
  ```
112
38
 
113
- Then run `npm dev` and visit `localhost:5173/og` to view your generated PNG. Remember that hot module reloading does not work with server routes, so if you change your HTML or CSS, hard refresh the route to see changes.
114
-
115
- ## Example Output
116
-
117
- ![Rendered OG image](https://vercel.sveltekit-og.dev)
118
-
119
- ## Headers
120
-
121
- When run in development, image headers contain `cache-control: no-cache, no-store`. In production, image headers contain `'cache-control': 'public, immutable, no-transform, max-age=31536000'`, which caches the image for 1 year. In both cases, the `'content-type': 'image/png'` is used.
122
-
123
- ## Styling
124
-
125
- Notice that our example uses TailwindCSS classes (e.g. `tw="bg-gray-50"`). Alternatively, your HTML can contain style attributes using any of [the subset of CSS supported by Satori](https://github.com/vercel/satori#css).
126
-
127
- Satori supports only a subset of HTML and CSS. For full details, see [Satori’s documentation](https://github.com/vercel/satori#documentation). Notably, Satori only supports flex-based layouts.
128
-
129
- ## Fonts
39
+ ## Usage
130
40
 
131
- Satori supports `ttf`, `otf`, and `woff` font formats; `woff2` is not supported. To maximize the font parsing speed, `ttf` or `otf` are recommended over `woff`.
41
+ For detailed usage instructions, please see the [Getting Started](https://sveltekit-og.dev/docs/getting-started) section of our documentation.
132
42
 
133
- By default, `@ethercorps/sveltekit-og` includes only 'Noto Sans' font. If you need to use other fonts, you can specify them as shown in the example. Notably, you can also import a font file that is stored locally within your project and are not required to use fetch.
43
+ Prefer the Takumi engine — more output formats and a built-in font? See the [Takumi Engine](https://sveltekit-og.dev/docs/usage/takumi) guide.
134
44
 
135
45
  ## Examples
136
46
 
137
- - `ImageResponse` · [_source_](/src/routes/+server.ts) · [_demo_](https://vercel.sveltekit-og.dev)
138
- - `Component Rendering` · [_source_](/src/routes/sc/+server.ts) · [_demo_](https://vercel.sveltekit-og.dev/sc)
139
-
140
- ## API Reference
141
-
142
- The package exposes an `ImageResponse` constructors, with the following options available:
143
-
144
- ```typescript
145
- import {ImageResponse} from '@ethercorps/sveltekit-og'
146
- import {SvelteComponent} from "svelte";
147
-
148
- ImageResponse(
149
- element : string | Component,
150
- options : {
151
- width ? : number = 1200
152
- height ? : number = 630,
153
- backgroundColor ? : string = "#fff"
154
- fonts ? : {
155
- name: string,
156
- data: ArrayBuffer,
157
- weight: number,
158
- style: 'normal' | 'italic'
159
- }[]
160
- debug ? : boolean = false
161
- // Options that will be passed to the HTTP response
162
- status ? : number = 200
163
- statusText ? : string
164
- headers ? : Record<string, string>
165
- },
166
- // Component props if components.
167
- ComponentProps<Component>
168
- )
169
- ```
170
-
171
- ## Changelog
172
-
173
- ### v4.0.0 (Breaking Changes)
174
-
175
- > Just install @ethercorps/sveltekit-og
176
-
177
- > Support for NodeJS, Deno, Cloudflare Pages, Cloudflare Workers, Vercel and Netlify.
178
-
179
- > No support for Bun tried and failed.
180
-
181
-
182
- ### v3.0.0 (Breaking Changes)
47
+ - **ImageResponse**: [_source_](/src/routes/+server.ts) · [_demo_](https://vercel.sveltekit-og.dev)
48
+ - **Component Rendering**: [_source_](/src/routes/sc/+server.ts) · [_demo_](https://vercel.sveltekit-og.dev/sc)
183
49
 
184
- > Just install @ethercorps/sveltekit-og
185
- > No wasm as of now, only support for nodejs based runtime.
50
+ ## Contributing
186
51
 
187
- ### v1.2.3 Update (Breaking Changes)
52
+ Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) to get started.
188
53
 
189
- > Now you have to install dependency by yourself which will make it easier to build for all plateforms.
190
-
191
- ```
192
- npm i @resvg/resvg-js
193
- ```
194
-
195
- ```
196
- npm i satori
197
- ```
198
-
199
- > From now on their will be no issues related to build, and soon this library going to have its own documentation.
200
-
201
- ### v1.2.2 Update (Breaking Change)
202
-
203
- - We don't provide access to satori from `@ethercorps/sveltekit-og`.
204
-
205
- ### v1.0.0 Update (Breaking Changes)
54
+ ## Changelog
206
55
 
207
- Finally, We have added html to react like element like object converter out of the box and with svelte compiler.
208
- Now you can use `{ toReactElement }` with `"@ethercorps/sveltekit-og"` like:
56
+ All notable changes to this project are documented in the [changelog](CHANGELOG.md).
209
57
 
210
- - We have changed to function based instead of class based ImageResponse and componentToImageResponse.
211
- - Removed `@resvg/resvg-wasm` with `@resvg/resvg-js` because of internal errors.
212
- - Removed `satori-html` because now we have `toReactElement` out of the box with svelte compiler.
213
- > If you find a problem related to undefined a please check [_vite.config.js_](/vite.config.ts) and add ` define: { _a: 'undefined' } in config.`
58
+ ## License
214
59
 
215
- > If you find any issue and have suggestion for this project please open a ticket and if you want to contribute please create a new discussion.
60
+ This project is licensed under the [MIT License](LICENSE).
216
61
 
217
62
  ## Acknowledgements
218
63
 
219
- This project will not be possible without the following projects:
64
+ This project would not be possible without the following projects:
220
65
 
221
66
  - [Satori & @vercel/og](https://github.com/vercel/satori)
67
+ - [Takumi](https://takumi.kane.tw)
222
68
  - [Noto by Google Fonts](https://fonts.google.com/noto)
223
69
  - [fineshopdesign](https://github.com/fineshopdesign/cf-wasm)
224
70
 
package/dist/fonts.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FinalFontOptions, FontStyle, FontWeight, MayBePromise } from './types.js';
1
+ import type { FinalFontOptions, FontStyle, FontWeight, MayBePromise } from "./types.js";
2
2
  interface BaseFontOptions {
3
3
  weight?: FontWeight;
4
4
  style?: FontStyle;
@@ -7,32 +7,24 @@ interface BaseFontOptions {
7
7
  * All font types inherit from this class.
8
8
  */
9
9
  export declare class BaseFont {
10
- protected input: any;
11
10
  name: string;
12
11
  style: FontStyle;
13
12
  weight: FontWeight;
14
- constructor(name: string, input: any, { weight, style }?: BaseFontOptions);
13
+ constructor(name: string, { weight, style }?: BaseFontOptions);
15
14
  /** * Abstract getter that returns the promised ArrayBuffer.
16
15
  * Overridden by CustomFont and GoogleFont for lazy loading.
17
16
  */
18
17
  get data(): MayBePromise<Buffer | ArrayBuffer>;
19
18
  }
20
- /** * A helper class to load Custom fonts, typically from local files.
21
- * The input must be a function provided by the user (e.g., using $app/server/read).
22
- */
23
19
  export declare class CustomFont extends BaseFont {
20
+ protected input: MayBePromise<Buffer | ArrayBuffer> | (() => MayBePromise<Buffer | ArrayBuffer>);
24
21
  private promise?;
25
- /**
26
- * Creates an instance of CustomFont.
27
- * @param name The name of the font (for CSS font-family).
28
- * @param input Font data as ArrayBuffer or a function that resolves to ArrayBuffer (user must provide the loading logic).
29
- */
30
22
  constructor(name: string, input: MayBePromise<Buffer | ArrayBuffer> | (() => MayBePromise<Buffer | ArrayBuffer>), options?: BaseFontOptions);
31
- /** A promise which resolves to font data as `ArrayBuffer` (Lazy load) */
23
+ /** A promise which resolves to font data as `ArrayBuffer` (Lazy load and CACHED) */
32
24
  get data(): Promise<Buffer | ArrayBuffer>;
33
25
  }
34
26
  /** Loads Google font ArrayBuffer with caching. */
35
- export declare const loadGoogleFont: (family: string, { text, weight, style, display }?: {
27
+ export declare const loadGoogleFont: (family: string, { text, weight, style, display, }?: {
36
28
  text?: string;
37
29
  weight?: string | number;
38
30
  style?: FontStyle;
package/dist/fonts.js CHANGED
@@ -1,14 +1,12 @@
1
- import { FONT_CACHE_MAP } from './helpers/cache.js';
1
+ import { FONT_CACHE_MAP } from "./helpers/cache.js";
2
2
  /** * Base font class defining the structure required by Satori.
3
3
  * All font types inherit from this class.
4
4
  */
5
5
  export class BaseFont {
6
- input;
7
6
  name;
8
7
  style;
9
8
  weight;
10
- constructor(name, input, { weight = 400, style = 'normal' } = {}) {
11
- this.input = input;
9
+ constructor(name, { weight = 400, style = "normal" } = {}) {
12
10
  this.name = name;
13
11
  this.style = style;
14
12
  this.weight = weight;
@@ -17,43 +15,49 @@ export class BaseFont {
17
15
  * Overridden by CustomFont and GoogleFont for lazy loading.
18
16
  */
19
17
  get data() {
20
- return this.input;
18
+ throw new Error("The 'data' getter must be implemented by subclasses of BaseFont.");
21
19
  }
22
20
  }
23
- /** * A helper class to load Custom fonts, typically from local files.
24
- * The input must be a function provided by the user (e.g., using $app/server/read).
25
- */
26
21
  export class CustomFont extends BaseFont {
22
+ input;
27
23
  promise;
28
- /**
29
- * Creates an instance of CustomFont.
30
- * @param name The name of the font (for CSS font-family).
31
- * @param input Font data as ArrayBuffer or a function that resolves to ArrayBuffer (user must provide the loading logic).
32
- */
33
24
  constructor(name, input, options) {
34
- super(name, input, options);
25
+ super(name, options);
26
+ this.input = input;
35
27
  }
36
- /** A promise which resolves to font data as `ArrayBuffer` (Lazy load) */
28
+ /** A promise which resolves to font data as `ArrayBuffer` (Lazy load and CACHED) */
37
29
  get data() {
38
- // Defines the loading mechanism: execute the input function or resolve the promise/buffer.
39
- const fallback = async () => (typeof this.input === 'function' ? this.input() : this.input);
40
- // Memoization: ensures the input function is executed only once.
30
+ const cacheKey = `${this.name}-${this.weight}-${this.style}`;
31
+ const cachedData = FONT_CACHE_MAP.get(cacheKey);
32
+ if (cachedData) {
33
+ return Promise.resolve(cachedData);
34
+ }
35
+ const fallback = async () => {
36
+ const buffer = typeof this.input === "function" ? this.input() : this.input;
37
+ const resolvedBuffer = await buffer;
38
+ FONT_CACHE_MAP.set(cacheKey, resolvedBuffer);
39
+ return resolvedBuffer;
40
+ };
41
41
  this.promise = this.promise?.then(null, fallback) ?? fallback();
42
42
  return this.promise;
43
43
  }
44
44
  }
45
45
  /** Constructs Google font css url */
46
- const constructGoogleFontCssUrl = (family, { text, weight = 400, style = 'normal', display } = {}) => {
46
+ const constructGoogleFontCssUrl = (family, { text, weight = 400, style = "normal", display, } = {}) => {
47
47
  // Logic to build the URL (e.g., https://fonts.googleapis.com/css2?family=...wght@...)
48
48
  const params = {
49
- family: `${family.replaceAll(' ', '+')}:${style === 'italic' ? 'ital,' : ''}wght@${style === 'italic' ? '1,' : ''}${weight}`,
49
+ family: `${family.replaceAll(" ", "+")}:${style === "italic" ? "ital," : ""}wght@${style === "italic" ? "1," : ""}${weight}`,
50
50
  };
51
51
  if (text)
52
52
  params.text = encodeURIComponent(text);
53
- return `https://fonts.googleapis.com/css2?${Object.keys(params).map((key) => `${key}=${params[key]}`).join('&')}`;
53
+ if (display)
54
+ params.display = display;
55
+ return `https://fonts.googleapis.com/css2?${Object.entries(params)
56
+ .map(([key, value]) => `${key}=${value}`)
57
+ .join("&")}`;
54
58
  };
55
59
  /** Loads Google font ArrayBuffer with caching. */
56
- export const loadGoogleFont = async (family, { text, weight = 400, style = 'normal', display } = {}) => {
60
+ export const loadGoogleFont = async (family, { text, weight = 400, style = "normal", display, } = {}) => {
57
61
  const cssUrl = constructGoogleFontCssUrl(family, { text, weight, display, style });
58
62
  const fromMap = FONT_CACHE_MAP.get(cssUrl);
59
63
  if (fromMap) {
@@ -84,7 +88,7 @@ export class GoogleFont extends BaseFont {
84
88
  text;
85
89
  promise;
86
90
  constructor(family, options = {}) {
87
- super(options.name || family, undefined, options);
91
+ super(options.name || family, options);
88
92
  this.family = family;
89
93
  this.text = options.text;
90
94
  }
@@ -108,5 +112,5 @@ export async function resolveFonts(fontClasses) {
108
112
  style: fontClass.style,
109
113
  };
110
114
  }));
111
- return resolvedFonts.filter(font => font !== null);
115
+ return resolvedFonts.filter((font) => font !== null);
112
116
  }
@@ -1,5 +1,7 @@
1
- import type { Component } from 'svelte';
2
- import type { ComponentOptions, ImageOptions, VNode } from '../types.js';
3
- export declare function createVNode(element: string | Component, componentOptions?: ComponentOptions): Promise<VNode>;
4
- export declare function createSvg(element: string | Component, imageOptions: ImageOptions, componentOptions?: ComponentOptions): Promise<string>;
5
- export declare function createPng(element: string | Component, imageOptions: ImageOptions, componentOptions?: ComponentOptions): Promise<Uint8Array<ArrayBufferLike>>;
1
+ import type { Component } from "svelte";
2
+ import type { ComponentOptions, ImageOptions } from "../types.js";
3
+ /** Single entry for the Satori and ReSVG engine */
4
+ export declare function createImage(element: string | Component<any>, imageOptions: ImageOptions, componentOptions?: ComponentOptions): Promise<Uint8Array | string>;
5
+ /** Create an SVG string from a Svelte component or HTML string using Satori */
6
+ export declare function createSvg(element: string | Component<any>, imageOptions: ImageOptions, componentOptions?: ComponentOptions): Promise<string>;
7
+ export declare function createPng(element: string | Component<any>, imageOptions: ImageOptions, componentOptions?: ComponentOptions): Promise<Uint8Array<ArrayBufferLike>>;
@@ -1,46 +1,49 @@
1
- import { html } from 'satori-html';
2
- import { loadDynamicAsset } from './emoji.js';
3
- import { default_fonts, DEFAULT_WIDTH } from '../helpers/defaults.js';
4
- import { useResvg, useSatori } from '../providers/instances.js';
5
- import { render } from 'svelte/server';
6
- function svelteComponentToJsx(component, props = {}) {
7
- const { body, head } = render(component, { props });
8
- return html(body + head);
9
- }
10
- export async function createVNode(element, componentOptions) {
11
- return typeof element === 'string' ? html(element.replaceAll('\n', '').trim()) : svelteComponentToJsx(element, componentOptions?.props);
1
+ import { loadDynamicAsset } from "./emoji.js";
2
+ import { default_fonts, DEFAULT_WIDTH } from "../helpers/defaults.js";
3
+ import { useResvg, useSatori } from "../providers/instances.js";
4
+ import { createVNode } from "./toJSX.js";
5
+ import { createLogger } from "./logger.js";
6
+ import { handleAsync, ErrorCodes } from "./error-handler.js";
7
+ /** Single entry for the Satori and ReSVG engine */
8
+ export function createImage(element, imageOptions, componentOptions) {
9
+ return imageOptions.format === "svg"
10
+ ? createSvg(element, imageOptions, componentOptions)
11
+ : createPng(element, imageOptions, componentOptions);
12
12
  }
13
+ /** Create an SVG string from a Svelte component or HTML string using Satori */
13
14
  export async function createSvg(element, imageOptions, componentOptions) {
14
- const [satori, vnodes] = await Promise.all([useSatori(), createVNode(element, componentOptions)]);
15
- const satoriOptions = structuredClone(imageOptions);
16
- if (!Object.hasOwn(satoriOptions, 'fonts')) {
17
- satoriOptions['fonts'] = await default_fonts();
15
+ const log = createLogger(imageOptions.debug ?? false);
16
+ const vnodes = createVNode(element, componentOptions);
17
+ const satori = await useSatori(imageOptions.debug);
18
+ const satoriOptions = { ...imageOptions };
19
+ if (!satoriOptions.fonts) {
20
+ satoriOptions.fonts = await handleAsync(() => default_fonts(), ErrorCodes.FONT_LOAD_FAILED, "Failed to load default fonts for Satori");
18
21
  }
19
- satoriOptions['loadAdditionalAsset'] = loadDynamicAsset({
20
- emoji: imageOptions.emoji
22
+ satoriOptions.loadAdditionalAsset = loadDynamicAsset({
23
+ emoji: imageOptions.emoji,
21
24
  });
22
- if (satoriOptions.debug) {
23
- console.info('VNode proivided to satori:', vnodes, '\n');
24
- console.info('Options proivided to satori:', imageOptions, '\n');
25
- }
26
- return satori(vnodes, satoriOptions);
25
+ log.debug("Generating SVG with Satori");
26
+ log.info("VNode provided to satori:", JSON.stringify(vnodes, null, 2), "\n");
27
+ log.info("Options provided to satori:", imageOptions);
28
+ return handleAsync(() => satori(vnodes, satoriOptions), ErrorCodes.SATORI_RENDER_FAILED, "Failed to render SVG with Satori");
27
29
  }
30
+ /* Create a PNG image from a Svelte component or HTML string using Satori and ReSVG */
28
31
  export async function createPng(element, imageOptions, componentOptions) {
29
- const svg = await createSvg(element, imageOptions, componentOptions);
30
- if (imageOptions.debug) {
31
- console.info('SVG generated by satori:', svg, '\n');
32
- }
33
- const resvg_instance = await useResvg();
32
+ const log = createLogger(imageOptions.debug ?? false);
33
+ const svg = await handleAsync(() => createSvg(element, imageOptions, componentOptions), ErrorCodes.SATORI_RENDER_FAILED, "Failed to create SVG for PNG rendering");
34
+ log.debug("SVG generated by satori for ReSVG: \n", svg, "\n");
35
+ const resvg_instance = await handleAsync(() => useResvg(imageOptions.debug), ErrorCodes.RESVG_INIT_FAILED, "Failed to initialize ReSVG");
34
36
  const resvg_options = {
35
37
  fitTo: {
36
- mode: 'width',
38
+ mode: "width",
37
39
  value: imageOptions.width || DEFAULT_WIDTH,
38
- }
40
+ },
39
41
  };
40
- if (imageOptions.debug) {
41
- console.info('Options provided to ReSVG:', resvg_options, '\n');
42
- }
43
- const resvg = new resvg_instance(svg, resvg_options);
44
- const png_data = resvg.render();
45
- return png_data.asPng();
42
+ log.debug("Rendering PNG with ReSVG");
43
+ log.info("Options provided to ReSVG:", resvg_options, "\n");
44
+ return handleAsync(async () => {
45
+ const resvg = new resvg_instance(svg, resvg_options);
46
+ const png_data = resvg.render();
47
+ return png_data.asPng();
48
+ }, ErrorCodes.RESVG_RENDER_FAILED, "Failed to render PNG with ReSVG");
46
49
  }
@@ -1,7 +1,7 @@
1
- import type { SatoriOptions } from 'satori';
2
- import type { ImageOptions } from '../types.js';
3
- import type { EmojiType } from './emoji.js';
4
- export declare function default_fonts(): Promise<SatoriOptions['fonts']>;
1
+ import type { SatoriOptions } from "satori";
2
+ import type { ImageOptions } from "../types.js";
3
+ import type { EmojiType } from "./emoji.js";
4
+ export declare function default_fonts(): Promise<SatoriOptions["fonts"]>;
5
5
  export declare const DEFAULT_FORMAT = "png";
6
6
  export declare const DEFAULT_WIDTH = 1200;
7
7
  export declare const DEFAULT_HEIGHT = 630;
@@ -1,37 +1,47 @@
1
- export async function default_fonts() {
2
- const [noto_sans_regular_font_resp, noto_sans_bold_font_reps] = await Promise.all([
3
- fetch('https://cdn-sveltekit-og.ethercorps.io/NotoSans-Regular.ttf'), fetch('https://cdn-sveltekit-og.ethercorps.io/NotoSans-Bold.ttf')
4
- ]);
5
- if (!(noto_sans_bold_font_reps.ok || noto_sans_bold_font_reps.ok)) {
6
- console.error('Not able to load default fonts');
7
- throw new Error('Not able to load default fonts');
8
- }
9
- const [noto_sans_regular_font, noto_sans_bold_font] = await Promise.all([noto_sans_regular_font_resp.arrayBuffer(), noto_sans_bold_font_reps.arrayBuffer()]);
1
+ import { handleAsyncAll, validateResponse, ErrorCodes } from "./error-handler.js";
2
+ // fetched once per process; a rejected fetch clears the cache so the next request retries
3
+ let defaultFontsPromise;
4
+ export function default_fonts() {
5
+ defaultFontsPromise ??= loadDefaultFonts().catch((error) => {
6
+ defaultFontsPromise = undefined;
7
+ throw error;
8
+ });
9
+ return defaultFontsPromise;
10
+ }
11
+ async function loadDefaultFonts() {
12
+ const [noto_sans_regular_font_resp, noto_sans_bold_font_reps] = await handleAsyncAll([
13
+ () => fetch("https://cdn-sveltekit-og.ethercorps.io/NotoSans-Regular.ttf"),
14
+ () => fetch("https://cdn-sveltekit-og.ethercorps.io/NotoSans-Bold.ttf"),
15
+ ], ErrorCodes.FONT_LOAD_FAILED, "Failed to fetch default fonts");
16
+ const [noto_sans_regular_font, noto_sans_bold_font] = await handleAsyncAll([
17
+ () => validateResponse(noto_sans_regular_font_resp, ErrorCodes.FONT_LOAD_FAILED, "Failed to validate regular font response"),
18
+ () => validateResponse(noto_sans_bold_font_reps, ErrorCodes.FONT_LOAD_FAILED, "Failed to validate bold font response"),
19
+ ], ErrorCodes.FONT_LOAD_FAILED, "Failed to process font responses");
10
20
  return [
11
21
  {
12
22
  data: noto_sans_regular_font,
13
- name: 'Inter',
23
+ name: "Inter",
14
24
  weight: 400,
15
- style: 'normal'
25
+ style: "normal",
16
26
  },
17
27
  {
18
28
  data: noto_sans_bold_font,
19
- name: 'Inter',
29
+ name: "Inter",
20
30
  weight: 700,
21
- style: 'normal'
22
- }
31
+ style: "normal",
32
+ },
23
33
  ];
24
34
  }
25
- export const DEFAULT_FORMAT = 'png';
35
+ export const DEFAULT_FORMAT = "png";
26
36
  export const DEFAULT_WIDTH = 1200;
27
37
  export const DEFAULT_HEIGHT = 630;
28
- export const DEFAULT_EMOJI_PROVIDER = 'twemoji';
38
+ export const DEFAULT_EMOJI_PROVIDER = "twemoji";
29
39
  export const DEFAULT_STATUS_CODE = 200;
30
- export const DEFAULT_STATUS_TEXT = 'Success';
40
+ export const DEFAULT_STATUS_TEXT = "Success";
31
41
  export const DEFAULT_OPTIONS = {
32
42
  height: DEFAULT_HEIGHT,
33
43
  width: DEFAULT_WIDTH,
34
44
  debug: false,
35
45
  format: DEFAULT_FORMAT,
36
- emoji: 'twemoji'
46
+ emoji: "twemoji",
37
47
  };
@@ -8,6 +8,6 @@ declare const emoji_apis: {
8
8
  };
9
9
  export declare const loadDynamicAsset: ({ emoji }: {
10
10
  emoji: EmojiType;
11
- }) => (...args: any[]) => Promise<string | undefined>;
11
+ }) => (code: string, text: string) => Promise<string | undefined>;
12
12
  export type EmojiType = keyof typeof emoji_apis;
13
13
  export {};