@takumi-rs/image-response 1.0.0-beta.2 → 1.0.0-beta.20

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 CHANGED
@@ -7,7 +7,7 @@ Checkout the migration guide [From Next.js ImageResponse](https://takumi.kane.tw
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install @takumi-rs/image-response @takumi-rs/core @takumi-rs/helpers
10
+ npm install @takumi-rs/image-response
11
11
  ```
12
12
 
13
13
  ## Usage
@@ -27,11 +27,31 @@ export function GET(request: Request) {
27
27
  }
28
28
  ```
29
29
 
30
+ For shared configuration and cache isolation, create your own image response factory.
31
+
32
+ ```tsx
33
+ import { createImageResponse } from "@takumi-rs/image-response";
34
+
35
+ const ogImage = createImageResponse({
36
+ fonts: [
37
+ {
38
+ data: () => fetch("/fonts/Inter-Regular.ttf").then((res) => res.arrayBuffer()),
39
+ key: "inter-regular",
40
+ name: "Inter",
41
+ },
42
+ ],
43
+ });
44
+
45
+ export function GET() {
46
+ return ogImage(<OgImage />);
47
+ }
48
+ ```
49
+
30
50
  ### Fonts
31
51
 
32
- Takumi comes with full axis [Geist](https://vercel.com/font) and Geist Mono by default.
52
+ Takumi comes with full-axis [Geist](https://vercel.com/font) and Geist Mono by default.
33
53
 
34
- We have global fonts cache to avoid loading the same fonts multiple times.
54
+ We have a global fonts cache to avoid loading the same fonts multiple times.
35
55
 
36
56
  If your environment supports top-level await, you can load the fonts in global scope and reuse the fonts array.
37
57
 
@@ -48,30 +68,27 @@ const fonts = [
48
68
  new ImageResponse(<OgImage />, { fonts });
49
69
  ```
50
70
 
51
- If your environment doesn't support top-level await, or just want the fonts to get garbage collected after initialization, you can load the fonts like this.
71
+ If your environment doesn't support top-level await, you can pass a lazy `data` loader instead.
52
72
 
53
73
  ```tsx
54
- let isFontsLoaded = false;
55
-
56
74
  export function GET(request: Request) {
57
- const fonts = [];
58
-
59
- if (!isFontsLoaded) {
60
- isFontsLoaded = true;
61
- fonts = [
75
+ return new ImageResponse(<OgImage />, {
76
+ fonts: [
62
77
  {
63
78
  name: "Inter",
64
- data: await fetch("/fonts/Inter-Regular.ttf").then((res) => res.arrayBuffer()),
79
+ data: () => fetch("/fonts/Inter-Regular.ttf").then((res) => res.arrayBuffer()),
65
80
  style: "normal",
66
81
  weight: 400,
67
82
  },
68
- ];
69
- }
70
-
71
- return new ImageResponse(<OgImage />, { fonts });
83
+ ],
84
+ });
72
85
  }
73
86
  ```
74
87
 
88
+ The same pattern also works for `persistentImages`. Caches are scoped to each `createImageResponse()` instance.
89
+
90
+ `loadDefaultFonts` is only supported by the native `@takumi-rs/core` renderer. It has no effect when using the WASM renderer.
91
+
75
92
  ### Bring-Your-Own-Renderer (BYOR)
76
93
 
77
94
  If you want to use your own renderer instance, you can pass it to the `ImageResponse` constructor.
@@ -89,33 +106,28 @@ new ImageResponse(<OgImage />, { renderer });
89
106
  You can pass the JSX options to the `ImageResponse` constructor.
90
107
 
91
108
  ```tsx
92
- new ImageResponse(<OgImage />, {
93
- jsx: {
109
+ new ImageResponse(<OgImage />, {
110
+ jsx: {
94
111
  defaultStyles: false,
95
- }
112
+ },
96
113
  });
97
114
  ```
98
115
 
99
- ---
116
+ ### Error Handling
100
117
 
101
- ## WASM Usage
118
+ `ImageResponse` exposes a `ready` promise that resolves when rendering succeeds and rejects when rendering fails.
102
119
 
103
- If you want to use this package in browser environment/cloudflare, you can import from the wasm entry point.
120
+ ```tsx
121
+ const response = new ImageResponse(<OgImage />);
104
122
 
105
- Make sure you have the `@takumi-rs/wasm` package installed as well.
123
+ await response.ready;
124
+ return response;
125
+ ```
106
126
 
107
- Check the additional [bundler setup section](https://takumi.kane.tw/docs#additional-bundler-setup) for more setup details.
127
+ You can also provide `onError` to render a fallback image instead of failing the response stream.
108
128
 
109
129
  ```tsx
110
- import { describe, expect, test } from "bun:test";
111
- import { ImageResponse } from "@takumi-rs/image-response/wasm";
112
- import module from "@takumi-rs/wasm/next";
113
-
114
- export default {
115
- fetch() {
116
- return new ImageResponse(<div>Hello</div>, {
117
- module,
118
- });
119
- }
120
- }
130
+ const response = new ImageResponse(<OgImage />, {
131
+ onError: () => <div>Failed to generate image</div>,
132
+ });
121
133
  ```
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: !0 });
10
+ }, __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from == "object" || typeof from == "function")
12
+ for (let key of __getOwnPropNames(from))
13
+ !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target,
22
+ mod
23
+ )), __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod);
24
+
25
+ // src/response.tsx
26
+ var response_exports = {};
27
+ __export(response_exports, {
28
+ ImageResponse: () => ImageResponse,
29
+ createImageResponse: () => createImageResponse,
30
+ default: () => response_default
31
+ });
32
+ module.exports = __toCommonJS(response_exports);
33
+ var import_helpers = require("@takumi-rs/helpers"), import_emoji = require("@takumi-rs/helpers/emoji"), import_jsx = require("@takumi-rs/helpers/jsx");
34
+
35
+ // src/cache.ts
36
+ function createResourceCache() {
37
+ return {
38
+ functionDataCache: /* @__PURE__ */ new WeakMap(),
39
+ loadedFontKeys: /* @__PURE__ */ new Set(),
40
+ loadedFontObjects: /* @__PURE__ */ new WeakSet(),
41
+ loadedImageKeys: /* @__PURE__ */ new Set(),
42
+ loadedImageObjects: /* @__PURE__ */ new WeakSet(),
43
+ promiseDataCache: /* @__PURE__ */ new WeakMap(),
44
+ stringDataCache: /* @__PURE__ */ new Map()
45
+ };
46
+ }
47
+ function getOrCreateStringDataCache(cache, key, load) {
48
+ let cached = cache.stringDataCache.get(key);
49
+ if (cached)
50
+ return cached;
51
+ let pending = Promise.resolve(load()).catch((error) => {
52
+ throw cache.stringDataCache.delete(key), error;
53
+ });
54
+ return cache.stringDataCache.set(key, pending), pending;
55
+ }
56
+ function getOrCreateFunctionDataCache(cache, loader) {
57
+ let cached = cache.functionDataCache.get(loader);
58
+ if (cached)
59
+ return cached;
60
+ let pending = Promise.resolve(loader()).catch((error) => {
61
+ throw cache.functionDataCache.delete(loader), error;
62
+ });
63
+ return cache.functionDataCache.set(loader, pending), pending;
64
+ }
65
+ function getOrCreatePromiseDataCache(cache, promise) {
66
+ let cached = cache.promiseDataCache.get(promise);
67
+ if (cached)
68
+ return cached;
69
+ let pending = promise.catch((error) => {
70
+ throw cache.promiseDataCache.delete(promise), error;
71
+ });
72
+ return cache.promiseDataCache.set(promise, pending), pending;
73
+ }
74
+ function hasBinaryData(value) {
75
+ return value instanceof Uint8Array || value instanceof ArrayBuffer;
76
+ }
77
+ function createFontCacheKey(font) {
78
+ return hasBinaryData(font) ? font : font.key ? `font:${font.key}` : font;
79
+ }
80
+ function createImageCacheKey(image) {
81
+ return image.key ? `image:${image.key}` : image;
82
+ }
83
+ async function resolveData(cache, data, cacheKey) {
84
+ return typeof data == "function" ? cacheKey ? getOrCreateStringDataCache(cache, cacheKey, data) : getOrCreateFunctionDataCache(cache, data) : data instanceof Promise ? cacheKey ? getOrCreateStringDataCache(cache, cacheKey, () => data) : getOrCreatePromiseDataCache(cache, data) : data;
85
+ }
86
+ async function resolveFont(cache, font) {
87
+ if (hasBinaryData(font))
88
+ return font;
89
+ let cacheKey = createFontCacheKey(font);
90
+ return {
91
+ ...font,
92
+ data: await resolveData(cache, font.data, typeof cacheKey == "string" ? cacheKey : void 0)
93
+ };
94
+ }
95
+ async function resolvePersistentImage(cache, image) {
96
+ let cacheKey = createImageCacheKey(image);
97
+ return {
98
+ ...image,
99
+ data: await resolveData(cache, image.data, typeof cacheKey == "string" ? cacheKey : void 0)
100
+ };
101
+ }
102
+
103
+ // src/import.ts
104
+ var wasm = __toESM(require("@takumi-rs/wasm"), 1), importPromise = null;
105
+ function getImports(module2) {
106
+ return importPromise ??= getImportsImpl(module2), importPromise;
107
+ }
108
+ async function getImportsImpl(module2) {
109
+ if (module2)
110
+ return initializeWasm(module2);
111
+ let importedModule = await importBindings();
112
+ return importedModule && "Renderer" in importedModule ? importedModule : initializeWasm(importedModule);
113
+ }
114
+ async function initializeWasm(module2) {
115
+ let wasmModule = module2 !== void 0 && typeof module2 == "object" && "default" in module2 ? module2.default : module2;
116
+ try {
117
+ return await wasm.default(wasmModule ? { module_or_path: wasmModule } : void 0), wasm;
118
+ } catch (error) {
119
+ throw new Error(
120
+ "Couldn't automatically resolve Takumi native bindings. Please specify the module option with the WASM module.",
121
+ {
122
+ cause: error
123
+ }
124
+ );
125
+ }
126
+ }
127
+ async function importBindings() {
128
+ if (shouldSkipCoreImport())
129
+ return importWasmBindings();
130
+ try {
131
+ return await import("@takumi-rs/core");
132
+ } catch (error) {
133
+ console.warn(
134
+ "Unable to import @takumi-rs/core. Falling back to auto-detection of WASM bindings.",
135
+ {
136
+ cause: error
137
+ }
138
+ );
139
+ }
140
+ return importWasmBindings();
141
+ }
142
+ async function importWasmBindings() {
143
+ let nextPath = "@takumi-rs/wasm/next";
144
+ return typeof process < "u" && process.env.NEXT_RUNTIME ? import(
145
+ /* @vite-ignore */
146
+ nextPath
147
+ ) : import(
148
+ /* turbopackIgnore: true */
149
+ /* webpackIgnore: true */
150
+ "@takumi-rs/wasm/auto"
151
+ );
152
+ }
153
+ function shouldSkipCoreImport() {
154
+ if (typeof process < "u" && process.env.NEXT_RUNTIME === "edge" || typeof window < "u" || typeof navigator < "u" && navigator.userAgent === "Cloudflare-Workers" || "WebSocketPair" in globalThis || "EdgeRuntime" in globalThis)
155
+ return !0;
156
+ let maybeWorkerGlobalScope = globalThis.WorkerGlobalScope;
157
+ return !!(maybeWorkerGlobalScope !== void 0 && maybeWorkerGlobalScope.prototype.isPrototypeOf(globalThis));
158
+ }
159
+
160
+ // src/response.tsx
161
+ var defaultFormat = "webp";
162
+ function hasLoadedResource(key, loadedKeys, loadedObjects) {
163
+ return typeof key == "string" ? loadedKeys.has(key) : loadedObjects.has(key);
164
+ }
165
+ function markLoadedResource(key, loadedKeys, loadedObjects) {
166
+ if (typeof key == "string") {
167
+ loadedKeys.add(key);
168
+ return;
169
+ }
170
+ loadedObjects.add(key);
171
+ }
172
+ function mergeOptions(defaultOptions, options) {
173
+ if (!defaultOptions)
174
+ return options;
175
+ if (!options)
176
+ return defaultOptions;
177
+ let headers = new Headers(defaultOptions.headers);
178
+ return options.headers && new Headers(options.headers).forEach((value, key) => {
179
+ headers.set(key, value);
180
+ }), "renderer" in options ? {
181
+ ...defaultOptions,
182
+ ...options,
183
+ headers
184
+ } : {
185
+ ...defaultOptions,
186
+ ...options,
187
+ fonts: options.fonts ?? defaultOptions.fonts,
188
+ headers,
189
+ persistentImages: options.persistentImages ?? defaultOptions.persistentImages,
190
+ stylesheets: [...defaultOptions.stylesheets ?? [], ...options.stylesheets ?? []]
191
+ };
192
+ }
193
+ var contentTypeMap = {
194
+ png: "image/png",
195
+ jpeg: "image/jpeg",
196
+ webp: "image/webp",
197
+ raw: "application/octet-stream"
198
+ };
199
+ async function renderComponent(component, options, imports, renderer) {
200
+ let { node: originalNode, stylesheets } = await (0, import_jsx.fromJsx)(component, options?.jsx), node = options?.emoji && options.emoji !== "from-font" ? (0, import_emoji.extractEmojis)(originalNode, options.emoji) : originalNode, fetchedResources = options?.fetchedResources !== void 0 ? options.fetchedResources : await (0, import_helpers.fetchResources)(imports.extractResourceUrls(node)), renderOptions = {
201
+ ...options,
202
+ fetchedResources,
203
+ format: options?.format ?? defaultFormat,
204
+ stylesheets: [...options?.stylesheets ?? [], ...stylesheets]
205
+ };
206
+ return renderer.render(node, renderOptions, options?.signal);
207
+ }
208
+ function defaultErrorHandler(error) {
209
+ console.error("Takumi failed to render image."), console.error(error);
210
+ }
211
+ async function loadRendererResources(renderer, options, cache) {
212
+ let tasks = [];
213
+ if (options?.fonts && options.fonts.length > 0) {
214
+ let resolvedFonts = await Promise.all(
215
+ options.fonts.map(async (font) => ({
216
+ cacheKey: createFontCacheKey(font),
217
+ font: await resolveFont(cache, font)
218
+ }))
219
+ );
220
+ if ("loadFonts" in renderer) {
221
+ let filteredFonts = resolvedFonts.filter(({ cacheKey }) => hasLoadedResource(cacheKey, cache.loadedFontKeys, cache.loadedFontObjects) ? !1 : (markLoadedResource(cacheKey, cache.loadedFontKeys, cache.loadedFontObjects), !0));
222
+ filteredFonts.length > 0 && tasks.push(renderer.loadFonts(filteredFonts.map(({ font }) => font)));
223
+ } else
224
+ for (let { cacheKey, font } of resolvedFonts)
225
+ hasLoadedResource(cacheKey, cache.loadedFontKeys, cache.loadedFontObjects) || (markLoadedResource(cacheKey, cache.loadedFontKeys, cache.loadedFontObjects), renderer.loadFont(font));
226
+ }
227
+ if (options?.persistentImages && options.persistentImages.length > 0) {
228
+ let resolvedImages = await Promise.all(
229
+ options.persistentImages.map(async (image) => ({
230
+ cacheKey: createImageCacheKey(image),
231
+ image: await resolvePersistentImage(cache, image)
232
+ }))
233
+ );
234
+ for (let { cacheKey, image } of resolvedImages) {
235
+ if (hasLoadedResource(cacheKey, cache.loadedImageKeys, cache.loadedImageObjects))
236
+ continue;
237
+ markLoadedResource(cacheKey, cache.loadedImageKeys, cache.loadedImageObjects);
238
+ let maybePromise = renderer.putPersistentImage(image, options.signal);
239
+ maybePromise instanceof Promise && tasks.push(maybePromise);
240
+ }
241
+ }
242
+ tasks.length > 0 && await Promise.all(tasks);
243
+ }
244
+ function createImageResponse(defaultOptions) {
245
+ let cache = createResourceCache();
246
+ return function(component, options) {
247
+ let mergedOptions = mergeOptions(defaultOptions, options), {
248
+ promise: ready,
249
+ reject: rejectReady,
250
+ resolve: resolveReady
251
+ } = Promise.withResolvers(), module2 = mergedOptions && "module" in mergedOptions ? mergedOptions.module : void 0, stream = new ReadableStream({
252
+ type: "bytes",
253
+ async start(controller) {
254
+ try {
255
+ let imports = await getImports(module2), isExternalRenderer = mergedOptions && "renderer" in mergedOptions, renderer = isExternalRenderer ? mergedOptions.renderer : cache.renderer ??= new imports.Renderer({
256
+ loadDefaultFonts: mergedOptions?.loadDefaultFonts
257
+ });
258
+ isExternalRenderer || await loadRendererResources(renderer, mergedOptions, cache);
259
+ let image = await renderComponent(component, mergedOptions, imports, renderer);
260
+ controller.enqueue(image), controller.close(), resolveReady();
261
+ } catch (error) {
262
+ controller.error(error), await (mergedOptions?.onError ?? defaultErrorHandler)(error), rejectReady(error);
263
+ }
264
+ }
265
+ }), headers = new Headers(mergedOptions?.headers);
266
+ headers.get("content-type") || headers.set("content-type", contentTypeMap[mergedOptions?.format ?? defaultFormat]);
267
+ let response = new Response(stream, {
268
+ headers,
269
+ status: mergedOptions?.status,
270
+ statusText: mergedOptions?.statusText
271
+ });
272
+ return Object.defineProperty(response, "ready", {
273
+ enumerable: !1,
274
+ value: ready,
275
+ writable: !1
276
+ });
277
+ };
278
+ }
279
+ var defaultImageResponse, ImageResponse = class extends Response {
280
+ ready;
281
+ constructor(component, options) {
282
+ defaultImageResponse ??= createImageResponse();
283
+ let response = defaultImageResponse(component, options);
284
+ super(response.body, response), this.ready = response.ready;
285
+ }
286
+ }, response_default = ImageResponse;
287
+ // Annotate the CommonJS export names for ESM import in node:
288
+ 0 && (module.exports = {
289
+ ImageResponse,
290
+ createImageResponse
291
+ });
@@ -0,0 +1,62 @@
1
+ import { EmojiType } from '@takumi-rs/helpers/emoji';
2
+ import { FromJsxOptions } from '@takumi-rs/helpers/jsx';
3
+ import { ReactNode } from 'react';
4
+ import * as napi from '@takumi-rs/core';
5
+ import * as wasm from '@takumi-rs/wasm';
6
+
7
+ type BinaryData = Uint8Array | ArrayBuffer;
8
+ type BinaryDataLoader = BinaryData | Promise<BinaryData> | (() => BinaryData | Promise<BinaryData>);
9
+ type ImageResponseFont = BinaryData | {
10
+ data: BinaryDataLoader;
11
+ key?: string;
12
+ name?: string;
13
+ style?: napi.FontDetails["style"] | wasm.FontDetails["style"];
14
+ weight?: number;
15
+ };
16
+ type ImageResponsePersistentImage = {
17
+ data: BinaryDataLoader;
18
+ key?: string;
19
+ src: string;
20
+ };
21
+
22
+ declare module "react" {
23
+ interface DOMAttributes<T> {
24
+ tw?: string;
25
+ }
26
+ }
27
+ type RenderOptions = napi.RenderOptions | wasm.RenderOptions;
28
+ type ManagedRendererOptions = {
29
+ fonts?: ImageResponseFont[];
30
+ /**
31
+ * Only supported by the native `@takumi-rs/core` renderer.
32
+ * This option is ignored when using the WASM renderer.
33
+ */
34
+ loadDefaultFonts?: boolean;
35
+ persistentImages?: ImageResponsePersistentImage[];
36
+ /**
37
+ * @description The WebAssembly module to use for the renderer. If not provided, the default resolving strategy will be used.
38
+ */
39
+ module?: wasm.InitInput | Promise<wasm.InitInput> | {
40
+ default: wasm.InitInput;
41
+ };
42
+ };
43
+ type ImageResponseOptionsWithRenderer = ResponseInit & RenderOptions & {
44
+ renderer: napi.Renderer | wasm.Renderer;
45
+ signal?: AbortSignal;
46
+ jsx?: FromJsxOptions;
47
+ emoji?: EmojiType | "from-font";
48
+ onError?: (error: unknown) => void | Promise<void>;
49
+ };
50
+ type ImageResponseOptionsWithoutRenderer = Omit<ImageResponseOptionsWithRenderer, "renderer"> & ManagedRendererOptions;
51
+ type ImageResponseOptions = ImageResponseOptionsWithRenderer | ImageResponseOptionsWithoutRenderer;
52
+ type ImageResponseResult = Response & {
53
+ readonly ready: Promise<void>;
54
+ };
55
+ type ImageResponseFactory = (component: ReactNode, options?: ImageResponseOptions) => ImageResponseResult;
56
+ declare function createImageResponse(defaultOptions?: ImageResponseOptionsWithoutRenderer): ImageResponseFactory;
57
+ declare class ImageResponse extends Response {
58
+ readonly ready: Promise<void>;
59
+ constructor(component: ReactNode, options?: ImageResponseOptions);
60
+ }
61
+
62
+ export { ImageResponse, type ImageResponseFactory, type ImageResponseOptions, type ImageResponseOptionsWithoutRenderer, type ImageResponseResult, createImageResponse, ImageResponse as default };
@@ -0,0 +1,62 @@
1
+ import { EmojiType } from '@takumi-rs/helpers/emoji';
2
+ import { FromJsxOptions } from '@takumi-rs/helpers/jsx';
3
+ import { ReactNode } from 'react';
4
+ import * as napi from '@takumi-rs/core';
5
+ import * as wasm from '@takumi-rs/wasm';
6
+
7
+ type BinaryData = Uint8Array | ArrayBuffer;
8
+ type BinaryDataLoader = BinaryData | Promise<BinaryData> | (() => BinaryData | Promise<BinaryData>);
9
+ type ImageResponseFont = BinaryData | {
10
+ data: BinaryDataLoader;
11
+ key?: string;
12
+ name?: string;
13
+ style?: napi.FontDetails["style"] | wasm.FontDetails["style"];
14
+ weight?: number;
15
+ };
16
+ type ImageResponsePersistentImage = {
17
+ data: BinaryDataLoader;
18
+ key?: string;
19
+ src: string;
20
+ };
21
+
22
+ declare module "react" {
23
+ interface DOMAttributes<T> {
24
+ tw?: string;
25
+ }
26
+ }
27
+ type RenderOptions = napi.RenderOptions | wasm.RenderOptions;
28
+ type ManagedRendererOptions = {
29
+ fonts?: ImageResponseFont[];
30
+ /**
31
+ * Only supported by the native `@takumi-rs/core` renderer.
32
+ * This option is ignored when using the WASM renderer.
33
+ */
34
+ loadDefaultFonts?: boolean;
35
+ persistentImages?: ImageResponsePersistentImage[];
36
+ /**
37
+ * @description The WebAssembly module to use for the renderer. If not provided, the default resolving strategy will be used.
38
+ */
39
+ module?: wasm.InitInput | Promise<wasm.InitInput> | {
40
+ default: wasm.InitInput;
41
+ };
42
+ };
43
+ type ImageResponseOptionsWithRenderer = ResponseInit & RenderOptions & {
44
+ renderer: napi.Renderer | wasm.Renderer;
45
+ signal?: AbortSignal;
46
+ jsx?: FromJsxOptions;
47
+ emoji?: EmojiType | "from-font";
48
+ onError?: (error: unknown) => void | Promise<void>;
49
+ };
50
+ type ImageResponseOptionsWithoutRenderer = Omit<ImageResponseOptionsWithRenderer, "renderer"> & ManagedRendererOptions;
51
+ type ImageResponseOptions = ImageResponseOptionsWithRenderer | ImageResponseOptionsWithoutRenderer;
52
+ type ImageResponseResult = Response & {
53
+ readonly ready: Promise<void>;
54
+ };
55
+ type ImageResponseFactory = (component: ReactNode, options?: ImageResponseOptions) => ImageResponseResult;
56
+ declare function createImageResponse(defaultOptions?: ImageResponseOptionsWithoutRenderer): ImageResponseFactory;
57
+ declare class ImageResponse extends Response {
58
+ readonly ready: Promise<void>;
59
+ constructor(component: ReactNode, options?: ImageResponseOptions);
60
+ }
61
+
62
+ export { ImageResponse, type ImageResponseFactory, type ImageResponseOptions, type ImageResponseOptionsWithoutRenderer, type ImageResponseResult, createImageResponse, ImageResponse as default };
@@ -0,0 +1,263 @@
1
+ // src/response.tsx
2
+ import { fetchResources } from "@takumi-rs/helpers";
3
+ import { extractEmojis } from "@takumi-rs/helpers/emoji";
4
+ import { fromJsx } from "@takumi-rs/helpers/jsx";
5
+
6
+ // src/cache.ts
7
+ function createResourceCache() {
8
+ return {
9
+ functionDataCache: /* @__PURE__ */ new WeakMap(),
10
+ loadedFontKeys: /* @__PURE__ */ new Set(),
11
+ loadedFontObjects: /* @__PURE__ */ new WeakSet(),
12
+ loadedImageKeys: /* @__PURE__ */ new Set(),
13
+ loadedImageObjects: /* @__PURE__ */ new WeakSet(),
14
+ promiseDataCache: /* @__PURE__ */ new WeakMap(),
15
+ stringDataCache: /* @__PURE__ */ new Map()
16
+ };
17
+ }
18
+ function getOrCreateStringDataCache(cache, key, load) {
19
+ let cached = cache.stringDataCache.get(key);
20
+ if (cached)
21
+ return cached;
22
+ let pending = Promise.resolve(load()).catch((error) => {
23
+ throw cache.stringDataCache.delete(key), error;
24
+ });
25
+ return cache.stringDataCache.set(key, pending), pending;
26
+ }
27
+ function getOrCreateFunctionDataCache(cache, loader) {
28
+ let cached = cache.functionDataCache.get(loader);
29
+ if (cached)
30
+ return cached;
31
+ let pending = Promise.resolve(loader()).catch((error) => {
32
+ throw cache.functionDataCache.delete(loader), error;
33
+ });
34
+ return cache.functionDataCache.set(loader, pending), pending;
35
+ }
36
+ function getOrCreatePromiseDataCache(cache, promise) {
37
+ let cached = cache.promiseDataCache.get(promise);
38
+ if (cached)
39
+ return cached;
40
+ let pending = promise.catch((error) => {
41
+ throw cache.promiseDataCache.delete(promise), error;
42
+ });
43
+ return cache.promiseDataCache.set(promise, pending), pending;
44
+ }
45
+ function hasBinaryData(value) {
46
+ return value instanceof Uint8Array || value instanceof ArrayBuffer;
47
+ }
48
+ function createFontCacheKey(font) {
49
+ return hasBinaryData(font) ? font : font.key ? `font:${font.key}` : font;
50
+ }
51
+ function createImageCacheKey(image) {
52
+ return image.key ? `image:${image.key}` : image;
53
+ }
54
+ async function resolveData(cache, data, cacheKey) {
55
+ return typeof data == "function" ? cacheKey ? getOrCreateStringDataCache(cache, cacheKey, data) : getOrCreateFunctionDataCache(cache, data) : data instanceof Promise ? cacheKey ? getOrCreateStringDataCache(cache, cacheKey, () => data) : getOrCreatePromiseDataCache(cache, data) : data;
56
+ }
57
+ async function resolveFont(cache, font) {
58
+ if (hasBinaryData(font))
59
+ return font;
60
+ let cacheKey = createFontCacheKey(font);
61
+ return {
62
+ ...font,
63
+ data: await resolveData(cache, font.data, typeof cacheKey == "string" ? cacheKey : void 0)
64
+ };
65
+ }
66
+ async function resolvePersistentImage(cache, image) {
67
+ let cacheKey = createImageCacheKey(image);
68
+ return {
69
+ ...image,
70
+ data: await resolveData(cache, image.data, typeof cacheKey == "string" ? cacheKey : void 0)
71
+ };
72
+ }
73
+
74
+ // src/import.ts
75
+ import * as wasm from "@takumi-rs/wasm";
76
+ var importPromise = null;
77
+ function getImports(module) {
78
+ return importPromise ??= getImportsImpl(module), importPromise;
79
+ }
80
+ async function getImportsImpl(module) {
81
+ if (module)
82
+ return initializeWasm(module);
83
+ let importedModule = await importBindings();
84
+ return importedModule && "Renderer" in importedModule ? importedModule : initializeWasm(importedModule);
85
+ }
86
+ async function initializeWasm(module) {
87
+ let wasmModule = module !== void 0 && typeof module == "object" && "default" in module ? module.default : module;
88
+ try {
89
+ return await wasm.default(wasmModule ? { module_or_path: wasmModule } : void 0), wasm;
90
+ } catch (error) {
91
+ throw new Error(
92
+ "Couldn't automatically resolve Takumi native bindings. Please specify the module option with the WASM module.",
93
+ {
94
+ cause: error
95
+ }
96
+ );
97
+ }
98
+ }
99
+ async function importBindings() {
100
+ if (shouldSkipCoreImport())
101
+ return importWasmBindings();
102
+ try {
103
+ return await import("@takumi-rs/core");
104
+ } catch (error) {
105
+ console.warn(
106
+ "Unable to import @takumi-rs/core. Falling back to auto-detection of WASM bindings.",
107
+ {
108
+ cause: error
109
+ }
110
+ );
111
+ }
112
+ return importWasmBindings();
113
+ }
114
+ async function importWasmBindings() {
115
+ let nextPath = "@takumi-rs/wasm/next";
116
+ return typeof process < "u" && process.env.NEXT_RUNTIME ? import(
117
+ /* @vite-ignore */
118
+ nextPath
119
+ ) : import(
120
+ /* turbopackIgnore: true */
121
+ /* webpackIgnore: true */
122
+ "@takumi-rs/wasm/auto"
123
+ );
124
+ }
125
+ function shouldSkipCoreImport() {
126
+ if (typeof process < "u" && process.env.NEXT_RUNTIME === "edge" || typeof window < "u" || typeof navigator < "u" && navigator.userAgent === "Cloudflare-Workers" || "WebSocketPair" in globalThis || "EdgeRuntime" in globalThis)
127
+ return !0;
128
+ let maybeWorkerGlobalScope = globalThis.WorkerGlobalScope;
129
+ return !!(maybeWorkerGlobalScope !== void 0 && maybeWorkerGlobalScope.prototype.isPrototypeOf(globalThis));
130
+ }
131
+
132
+ // src/response.tsx
133
+ var defaultFormat = "webp";
134
+ function hasLoadedResource(key, loadedKeys, loadedObjects) {
135
+ return typeof key == "string" ? loadedKeys.has(key) : loadedObjects.has(key);
136
+ }
137
+ function markLoadedResource(key, loadedKeys, loadedObjects) {
138
+ if (typeof key == "string") {
139
+ loadedKeys.add(key);
140
+ return;
141
+ }
142
+ loadedObjects.add(key);
143
+ }
144
+ function mergeOptions(defaultOptions, options) {
145
+ if (!defaultOptions)
146
+ return options;
147
+ if (!options)
148
+ return defaultOptions;
149
+ let headers = new Headers(defaultOptions.headers);
150
+ return options.headers && new Headers(options.headers).forEach((value, key) => {
151
+ headers.set(key, value);
152
+ }), "renderer" in options ? {
153
+ ...defaultOptions,
154
+ ...options,
155
+ headers
156
+ } : {
157
+ ...defaultOptions,
158
+ ...options,
159
+ fonts: options.fonts ?? defaultOptions.fonts,
160
+ headers,
161
+ persistentImages: options.persistentImages ?? defaultOptions.persistentImages,
162
+ stylesheets: [...defaultOptions.stylesheets ?? [], ...options.stylesheets ?? []]
163
+ };
164
+ }
165
+ var contentTypeMap = {
166
+ png: "image/png",
167
+ jpeg: "image/jpeg",
168
+ webp: "image/webp",
169
+ raw: "application/octet-stream"
170
+ };
171
+ async function renderComponent(component, options, imports, renderer) {
172
+ let { node: originalNode, stylesheets } = await fromJsx(component, options?.jsx), node = options?.emoji && options.emoji !== "from-font" ? extractEmojis(originalNode, options.emoji) : originalNode, fetchedResources = options?.fetchedResources !== void 0 ? options.fetchedResources : await fetchResources(imports.extractResourceUrls(node)), renderOptions = {
173
+ ...options,
174
+ fetchedResources,
175
+ format: options?.format ?? defaultFormat,
176
+ stylesheets: [...options?.stylesheets ?? [], ...stylesheets]
177
+ };
178
+ return renderer.render(node, renderOptions, options?.signal);
179
+ }
180
+ function defaultErrorHandler(error) {
181
+ console.error("Takumi failed to render image."), console.error(error);
182
+ }
183
+ async function loadRendererResources(renderer, options, cache) {
184
+ let tasks = [];
185
+ if (options?.fonts && options.fonts.length > 0) {
186
+ let resolvedFonts = await Promise.all(
187
+ options.fonts.map(async (font) => ({
188
+ cacheKey: createFontCacheKey(font),
189
+ font: await resolveFont(cache, font)
190
+ }))
191
+ );
192
+ if ("loadFonts" in renderer) {
193
+ let filteredFonts = resolvedFonts.filter(({ cacheKey }) => hasLoadedResource(cacheKey, cache.loadedFontKeys, cache.loadedFontObjects) ? !1 : (markLoadedResource(cacheKey, cache.loadedFontKeys, cache.loadedFontObjects), !0));
194
+ filteredFonts.length > 0 && tasks.push(renderer.loadFonts(filteredFonts.map(({ font }) => font)));
195
+ } else
196
+ for (let { cacheKey, font } of resolvedFonts)
197
+ hasLoadedResource(cacheKey, cache.loadedFontKeys, cache.loadedFontObjects) || (markLoadedResource(cacheKey, cache.loadedFontKeys, cache.loadedFontObjects), renderer.loadFont(font));
198
+ }
199
+ if (options?.persistentImages && options.persistentImages.length > 0) {
200
+ let resolvedImages = await Promise.all(
201
+ options.persistentImages.map(async (image) => ({
202
+ cacheKey: createImageCacheKey(image),
203
+ image: await resolvePersistentImage(cache, image)
204
+ }))
205
+ );
206
+ for (let { cacheKey, image } of resolvedImages) {
207
+ if (hasLoadedResource(cacheKey, cache.loadedImageKeys, cache.loadedImageObjects))
208
+ continue;
209
+ markLoadedResource(cacheKey, cache.loadedImageKeys, cache.loadedImageObjects);
210
+ let maybePromise = renderer.putPersistentImage(image, options.signal);
211
+ maybePromise instanceof Promise && tasks.push(maybePromise);
212
+ }
213
+ }
214
+ tasks.length > 0 && await Promise.all(tasks);
215
+ }
216
+ function createImageResponse(defaultOptions) {
217
+ let cache = createResourceCache();
218
+ return function(component, options) {
219
+ let mergedOptions = mergeOptions(defaultOptions, options), {
220
+ promise: ready,
221
+ reject: rejectReady,
222
+ resolve: resolveReady
223
+ } = Promise.withResolvers(), module = mergedOptions && "module" in mergedOptions ? mergedOptions.module : void 0, stream = new ReadableStream({
224
+ type: "bytes",
225
+ async start(controller) {
226
+ try {
227
+ let imports = await getImports(module), isExternalRenderer = mergedOptions && "renderer" in mergedOptions, renderer = isExternalRenderer ? mergedOptions.renderer : cache.renderer ??= new imports.Renderer({
228
+ loadDefaultFonts: mergedOptions?.loadDefaultFonts
229
+ });
230
+ isExternalRenderer || await loadRendererResources(renderer, mergedOptions, cache);
231
+ let image = await renderComponent(component, mergedOptions, imports, renderer);
232
+ controller.enqueue(image), controller.close(), resolveReady();
233
+ } catch (error) {
234
+ controller.error(error), await (mergedOptions?.onError ?? defaultErrorHandler)(error), rejectReady(error);
235
+ }
236
+ }
237
+ }), headers = new Headers(mergedOptions?.headers);
238
+ headers.get("content-type") || headers.set("content-type", contentTypeMap[mergedOptions?.format ?? defaultFormat]);
239
+ let response = new Response(stream, {
240
+ headers,
241
+ status: mergedOptions?.status,
242
+ statusText: mergedOptions?.statusText
243
+ });
244
+ return Object.defineProperty(response, "ready", {
245
+ enumerable: !1,
246
+ value: ready,
247
+ writable: !1
248
+ });
249
+ };
250
+ }
251
+ var defaultImageResponse, ImageResponse = class extends Response {
252
+ ready;
253
+ constructor(component, options) {
254
+ defaultImageResponse ??= createImageResponse();
255
+ let response = defaultImageResponse(component, options);
256
+ super(response.body, response), this.ready = response.ready;
257
+ }
258
+ }, response_default = ImageResponse;
259
+ export {
260
+ ImageResponse,
261
+ createImageResponse,
262
+ response_default as default
263
+ };
package/package.json CHANGED
@@ -1,53 +1,53 @@
1
1
  {
2
2
  "name": "@takumi-rs/image-response",
3
- "type": "module",
4
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.20",
4
+ "license": "(MIT OR Apache-2.0)",
5
5
  "author": {
6
- "email": "me@kane.tw",
7
6
  "name": "Kane Wang",
7
+ "email": "me@kane.tw",
8
8
  "url": "https://kane.tw"
9
9
  },
10
10
  "repository": {
11
11
  "url": "git+https://github.com/kane50613/takumi.git"
12
12
  },
13
- "dependencies": {
14
- "@takumi-rs/core": "1.0.0-beta.2",
15
- "@takumi-rs/helpers": "1.0.0-beta.2",
16
- "@takumi-rs/wasm": "1.0.0-beta.2"
17
- },
18
- "devDependencies": {
19
- "@types/bun": "catalog:",
20
- "@types/react": "catalog:",
21
- "@types/react-dom": "catalog:",
22
- "react": "catalog:",
23
- "react-dom": "catalog:",
24
- "tsup": "^8.5.1",
25
- "type-fest": "^5.4.4",
26
- "typescript": "catalog:"
27
- },
28
- "scripts": {
29
- "build": "tsup-node src/backends/*.ts --clean --minify --dts --format esm,cjs --no-splitting",
30
- "prepublishOnly": "jq '.dependencies[\"@takumi-rs/core\"] = .version | .dependencies[\"@takumi-rs/helpers\"] = .version | .dependencies[\"@takumi-rs/wasm\"] = .version' package.json > tmp.json && mv tmp.json package.json"
31
- },
32
13
  "files": [
33
14
  "dist",
34
15
  "package.json",
35
16
  "README.md",
36
17
  "tsconfig.json"
37
18
  ],
19
+ "type": "module",
38
20
  "exports": {
39
21
  ".": {
40
- "types": "./dist/node.d.ts",
41
- "import": "./dist/node.js",
42
- "require": "./dist/node.cjs",
43
- "default": "./dist/node.js"
22
+ "types": "./dist/response.d.ts",
23
+ "import": "./dist/response.js",
24
+ "require": "./dist/response.cjs",
25
+ "default": "./dist/response.js"
44
26
  },
45
27
  "./wasm": {
46
- "types": "./dist/wasm.d.ts",
47
- "import": "./dist/wasm.js",
48
- "require": "./dist/wasm.cjs",
49
- "default": "./dist/wasm.js"
28
+ "types": "./dist/response.d.ts",
29
+ "import": "./dist/response.js",
30
+ "require": "./dist/response.cjs",
31
+ "default": "./dist/response.js"
50
32
  }
51
33
  },
52
- "license": "(MIT OR Apache-2.0)"
34
+ "scripts": {
35
+ "build": "tsup-node src/response.tsx --clean --minify-syntax --dts --format esm,cjs --no-splitting",
36
+ "prepublishOnly": "jq '.dependencies[\"@takumi-rs/core\"] = .version | .dependencies[\"@takumi-rs/helpers\"] = .version | .dependencies[\"@takumi-rs/wasm\"] = .version' package.json > tmp.json && mv tmp.json package.json"
37
+ },
38
+ "dependencies": {
39
+ "@takumi-rs/core": "1.0.0-beta.20",
40
+ "@takumi-rs/helpers": "1.0.0-beta.20",
41
+ "@takumi-rs/wasm": "1.0.0-beta.20"
42
+ },
43
+ "devDependencies": {
44
+ "@types/bun": "catalog:",
45
+ "@types/react": "catalog:",
46
+ "@types/react-dom": "catalog:",
47
+ "react": "catalog:",
48
+ "react-dom": "catalog:",
49
+ "tsup": "^8.5.1",
50
+ "type-fest": "^5.5.0",
51
+ "typescript": "catalog:"
52
+ }
53
53
  }
package/dist/node.cjs DELETED
@@ -1 +0,0 @@
1
- "use strict";var m=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var I=(t,e)=>{for(var r in e)m(t,r,{get:e[r],enumerable:!0})},b=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of x(e))!j.call(t,n)&&n!==r&&m(t,n,{get:()=>e[n],enumerable:!(s=w(e,n))||s.enumerable});return t};var P=t=>b(m({},"__esModule",{value:!0}),t);var J={};I(J,{ImageResponse:()=>o,default:()=>E});module.exports=P(J);var c=require("@takumi-rs/core"),d=require("@takumi-rs/helpers"),p=require("@takumi-rs/helpers/emoji"),u=require("@takumi-rs/helpers/jsx"),a,T={format:"webp"};async function A(t){if(t&&"renderer"in t)return t.renderer;if(!a)return a=new c.Renderer(t),a;let e=[],r=a;return r?(t?.fonts&&t.fonts.length>0&&e.push(r.loadFonts(t.fonts)),t?.persistentImages&&e.push(...t.persistentImages.map(s=>r.putPersistentImage(s.src,s.data))),e.length>0&&await Promise.all(e),r):a}function F(t,e){if(e?.fetchedResources)return e.fetchedResources;let r=(0,c.extractResourceUrls)(t);return(0,d.fetchResources)(r)}function N(t,e){return new ReadableStream({type:"bytes",async start(r){try{let s=(0,u.fromJsx)(t,e?.jsx).then(async({node:i,stylesheets:l})=>{e?.emoji&&e.emoji!=="from-font"&&(i=(0,p.extractEmojis)(i,e.emoji));let O=await F(i,e);return{node:i,fetchedResources:O,stylesheets:l}}),[n,{node:f,fetchedResources:R,stylesheets:g}]=await Promise.all([A(e),s]),h={width:e?.width,height:e?.height,format:e?.format,quality:e?.quality,dithering:e?.dithering,drawDebugBorder:e?.drawDebugBorder,devicePixelRatio:e?.devicePixelRatio,fetchedResources:R,stylesheets:[...e?.stylesheets??[],...g]},y=await n.render(f,h,e?.signal);r.enqueue(y),r.close()}catch(s){r.error(s)}}})}var B={webp:"image/webp",png:"image/png",jpeg:"image/jpeg",raw:"application/octet-stream"},o=class extends Response{constructor(e,r){let s=N(e,r),n=new Headers(r?.headers);n.get("content-type")||n.set("content-type",B[r?.format??T.format]),super(s,{status:r?.status,statusText:r?.statusText,headers:n})}},E=o;0&&(module.exports={ImageResponse});
package/dist/node.d.cts DELETED
@@ -1,27 +0,0 @@
1
- import { RenderOptions, Renderer, ConstructRendererOptions } from '@takumi-rs/core';
2
- import { EmojiType } from '@takumi-rs/helpers/emoji';
3
- import { FromJsxOptions } from '@takumi-rs/helpers/jsx';
4
- import { ReactNode } from 'react';
5
-
6
- declare module "react" {
7
- interface DOMAttributes<T> {
8
- tw?: string;
9
- }
10
- }
11
- type ImageResponseOptionsWithRenderer = ResponseInit & RenderOptions & {
12
- renderer: Renderer;
13
- signal?: AbortSignal;
14
- jsx?: FromJsxOptions;
15
- emoji?: EmojiType | "from-font";
16
- };
17
- type ImageResponseOptionsWithoutRenderer = ResponseInit & RenderOptions & ConstructRendererOptions & {
18
- signal?: AbortSignal;
19
- jsx?: FromJsxOptions;
20
- emoji?: EmojiType | "from-font";
21
- };
22
- type ImageResponseOptions = ImageResponseOptionsWithRenderer | ImageResponseOptionsWithoutRenderer;
23
- declare class ImageResponse extends Response {
24
- constructor(component: ReactNode, options?: ImageResponseOptions);
25
- }
26
-
27
- export { ImageResponse, type ImageResponseOptions, ImageResponse as default };
package/dist/node.d.ts DELETED
@@ -1,27 +0,0 @@
1
- import { RenderOptions, Renderer, ConstructRendererOptions } from '@takumi-rs/core';
2
- import { EmojiType } from '@takumi-rs/helpers/emoji';
3
- import { FromJsxOptions } from '@takumi-rs/helpers/jsx';
4
- import { ReactNode } from 'react';
5
-
6
- declare module "react" {
7
- interface DOMAttributes<T> {
8
- tw?: string;
9
- }
10
- }
11
- type ImageResponseOptionsWithRenderer = ResponseInit & RenderOptions & {
12
- renderer: Renderer;
13
- signal?: AbortSignal;
14
- jsx?: FromJsxOptions;
15
- emoji?: EmojiType | "from-font";
16
- };
17
- type ImageResponseOptionsWithoutRenderer = ResponseInit & RenderOptions & ConstructRendererOptions & {
18
- signal?: AbortSignal;
19
- jsx?: FromJsxOptions;
20
- emoji?: EmojiType | "from-font";
21
- };
22
- type ImageResponseOptions = ImageResponseOptionsWithRenderer | ImageResponseOptionsWithoutRenderer;
23
- declare class ImageResponse extends Response {
24
- constructor(component: ReactNode, options?: ImageResponseOptions);
25
- }
26
-
27
- export { ImageResponse, type ImageResponseOptions, ImageResponse as default };
package/dist/node.js DELETED
@@ -1 +0,0 @@
1
- import{extractResourceUrls as g,Renderer as h}from"@takumi-rs/core";import{fetchResources as y}from"@takumi-rs/helpers";import{extractEmojis as l}from"@takumi-rs/helpers/emoji";import{fromJsx as O}from"@takumi-rs/helpers/jsx";var a,w={format:"webp"};async function x(r){if(r&&"renderer"in r)return r.renderer;if(!a)return a=new h(r),a;let e=[],t=a;return t?(r?.fonts&&r.fonts.length>0&&e.push(t.loadFonts(r.fonts)),r?.persistentImages&&e.push(...r.persistentImages.map(s=>t.putPersistentImage(s.src,s.data))),e.length>0&&await Promise.all(e),t):a}function j(r,e){if(e?.fetchedResources)return e.fetchedResources;let t=g(r);return y(t)}function I(r,e){return new ReadableStream({type:"bytes",async start(t){try{let s=O(r,e?.jsx).then(async({node:i,stylesheets:f})=>{e?.emoji&&e.emoji!=="from-font"&&(i=l(i,e.emoji));let R=await j(i,e);return{node:i,fetchedResources:R,stylesheets:f}}),[n,{node:c,fetchedResources:m,stylesheets:d}]=await Promise.all([x(e),s]),p={width:e?.width,height:e?.height,format:e?.format,quality:e?.quality,dithering:e?.dithering,drawDebugBorder:e?.drawDebugBorder,devicePixelRatio:e?.devicePixelRatio,fetchedResources:m,stylesheets:[...e?.stylesheets??[],...d]},u=await n.render(c,p,e?.signal);t.enqueue(u),t.close()}catch(s){t.error(s)}}})}var b={webp:"image/webp",png:"image/png",jpeg:"image/jpeg",raw:"application/octet-stream"},o=class extends Response{constructor(e,t){let s=I(e,t),n=new Headers(t?.headers);n.get("content-type")||n.set("content-type",b[t?.format??w.format]),super(s,{status:t?.status,statusText:t?.statusText,headers:n})}},N=o;export{o as ImageResponse,N as default};
package/dist/wasm.cjs DELETED
@@ -1 +0,0 @@
1
- "use strict";var g=Object.create;var p=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var O=Object.getPrototypeOf,h=Object.prototype.hasOwnProperty;var j=(e,t)=>{for(var s in t)p(e,s,{get:t[s],enumerable:!0})},f=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of I(t))!h.call(e,r)&&r!==s&&p(e,r,{get:()=>t[r],enumerable:!(n=y(t,r))||n.enumerable});return e};var w=(e,t,s)=>(s=e!=null?g(O(e)):{},f(t||!e||!e.__esModule?p(s,"default",{value:e,enumerable:!0}):s,e)),x=e=>f(p({},"__esModule",{value:!0}),e);var E={};j(E,{ImageResponse:()=>m,default:()=>A});module.exports=x(E);var u=require("@takumi-rs/helpers"),d=require("@takumi-rs/helpers/emoji"),l=require("@takumi-rs/helpers/jsx"),a=w(require("@takumi-rs/wasm/no-bundler"),1),o;function b(e){if(e&&"renderer"in e)return e.renderer;if(!o)return o=new a.Renderer(e),o;if(e?.fonts)for(let t of e.fonts)o.loadFont(t);if(e?.persistentImages)for(let t of e.persistentImages)o.putPersistentImage(t);return o}var c=null;async function T(e){!("module"in e)||o||(c||(c=(async()=>{let t=await e.module;typeof t=="object"&&"default"in t&&(t=t.default),await(0,a.default)({module_or_path:t})})()),await c)}async function F(e,t){let s={...t},{node:n,stylesheets:r}=await(0,l.fromJsx)(e,s.jsx);if(s.stylesheets=[...s.stylesheets??[],...r],s.emoji&&s.emoji!=="from-font"&&(n=(0,d.extractEmojis)(n,s.emoji)),!s.fetchedResources){let i=(0,a.extractResourceUrls)(n);i.length>0&&(s.fetchedResources=await(0,u.fetchResources)(i))}return{node:n,options:s}}function N(e,t){return new ReadableStream({type:"bytes",async start(s){try{await T(t);let n=b(t),{node:r,options:i}=await F(e,t),R=n.render(r,i);s.enqueue(R),s.close()}catch(n){s.error(n)}}})}var P={png:"image/png",jpeg:"image/jpeg",webp:"image/webp",raw:"application/octet-stream"},W={format:"webp"},m=class extends Response{constructor(t,s){let n=N(t,s),r=new Headers(s.headers);r.get("content-type")||r.set("content-type",P[s.format??W.format]),super(n,{status:s.status,statusText:s.statusText,headers:r})}},A=m;0&&(module.exports={ImageResponse});
package/dist/wasm.d.cts DELETED
@@ -1,58 +0,0 @@
1
- import { EmojiType } from '@takumi-rs/helpers/emoji';
2
- import { FromJsxOptions } from '@takumi-rs/helpers/jsx';
3
- import { RenderOptions, Renderer, InitInput, Font, ImageSource } from '@takumi-rs/wasm/no-bundler';
4
- import { ReactNode } from 'react';
5
-
6
- declare module "react" {
7
- interface DOMAttributes<T> {
8
- tw?: string;
9
- }
10
- }
11
- type ModuleOptions = {
12
- /**
13
- * @description The WebAssembly module to use for the renderer.
14
- *
15
- * @example
16
- * For Cloudflare Workers, you can use the bundled WASM file.
17
- * ```ts
18
- * {
19
- * module: import("@takumi-rs/wasm/takumi_wasm_bg.wasm"),
20
- * }
21
- * ```
22
- *
23
- * For Next.js Turbopack, you can use the nextjs helper.
24
- * ```ts
25
- * {
26
- * module: import("@takumi-rs/wasm/next"),
27
- * }
28
- * ```
29
- *
30
- * For Vite, use `?url` suffix to get the URL of the WASM file.
31
- *
32
- * ```ts
33
- * {
34
- * module: import("@takumi-rs/wasm/takumi_wasm_bg.wasm?url"),
35
- * }
36
- * ```
37
- */
38
- module: InitInput | Promise<InitInput> | {
39
- default: InitInput;
40
- };
41
- };
42
- type ImageResponseOptionsWithRenderer = ResponseInit & RenderOptions & {
43
- renderer: Renderer;
44
- jsx?: FromJsxOptions;
45
- emoji?: EmojiType | "from-font";
46
- };
47
- type ImageResponseOptionsWithoutRenderer = ResponseInit & RenderOptions & ModuleOptions & {
48
- fonts?: Font[];
49
- persistentImages?: ImageSource[];
50
- jsx?: FromJsxOptions;
51
- emoji?: EmojiType | "from-font";
52
- };
53
- type ImageResponseOptions = ImageResponseOptionsWithRenderer | ImageResponseOptionsWithoutRenderer;
54
- declare class ImageResponse extends Response {
55
- constructor(component: ReactNode, options: ImageResponseOptions);
56
- }
57
-
58
- export { ImageResponse, type ImageResponseOptions, ImageResponse as default };
package/dist/wasm.d.ts DELETED
@@ -1,58 +0,0 @@
1
- import { EmojiType } from '@takumi-rs/helpers/emoji';
2
- import { FromJsxOptions } from '@takumi-rs/helpers/jsx';
3
- import { RenderOptions, Renderer, InitInput, Font, ImageSource } from '@takumi-rs/wasm/no-bundler';
4
- import { ReactNode } from 'react';
5
-
6
- declare module "react" {
7
- interface DOMAttributes<T> {
8
- tw?: string;
9
- }
10
- }
11
- type ModuleOptions = {
12
- /**
13
- * @description The WebAssembly module to use for the renderer.
14
- *
15
- * @example
16
- * For Cloudflare Workers, you can use the bundled WASM file.
17
- * ```ts
18
- * {
19
- * module: import("@takumi-rs/wasm/takumi_wasm_bg.wasm"),
20
- * }
21
- * ```
22
- *
23
- * For Next.js Turbopack, you can use the nextjs helper.
24
- * ```ts
25
- * {
26
- * module: import("@takumi-rs/wasm/next"),
27
- * }
28
- * ```
29
- *
30
- * For Vite, use `?url` suffix to get the URL of the WASM file.
31
- *
32
- * ```ts
33
- * {
34
- * module: import("@takumi-rs/wasm/takumi_wasm_bg.wasm?url"),
35
- * }
36
- * ```
37
- */
38
- module: InitInput | Promise<InitInput> | {
39
- default: InitInput;
40
- };
41
- };
42
- type ImageResponseOptionsWithRenderer = ResponseInit & RenderOptions & {
43
- renderer: Renderer;
44
- jsx?: FromJsxOptions;
45
- emoji?: EmojiType | "from-font";
46
- };
47
- type ImageResponseOptionsWithoutRenderer = ResponseInit & RenderOptions & ModuleOptions & {
48
- fonts?: Font[];
49
- persistentImages?: ImageSource[];
50
- jsx?: FromJsxOptions;
51
- emoji?: EmojiType | "from-font";
52
- };
53
- type ImageResponseOptions = ImageResponseOptionsWithRenderer | ImageResponseOptionsWithoutRenderer;
54
- declare class ImageResponse extends Response {
55
- constructor(component: ReactNode, options: ImageResponseOptions);
56
- }
57
-
58
- export { ImageResponse, type ImageResponseOptions, ImageResponse as default };
package/dist/wasm.js DELETED
@@ -1 +0,0 @@
1
- import{fetchResources as c}from"@takumi-rs/helpers";import{extractEmojis as f}from"@takumi-rs/helpers/emoji";import{fromJsx as u}from"@takumi-rs/helpers/jsx";import d,{extractResourceUrls as l,Renderer as R}from"@takumi-rs/wasm/no-bundler";var o;function g(s){if(s&&"renderer"in s)return s.renderer;if(!o)return o=new R(s),o;if(s?.fonts)for(let t of s.fonts)o.loadFont(t);if(s?.persistentImages)for(let t of s.persistentImages)o.putPersistentImage(t);return o}var i=null;async function y(s){!("module"in s)||o||(i||(i=(async()=>{let t=await s.module;typeof t=="object"&&"default"in t&&(t=t.default),await d({module_or_path:t})})()),await i)}async function I(s,t){let e={...t},{node:n,stylesheets:r}=await u(s,e.jsx);if(e.stylesheets=[...e.stylesheets??[],...r],e.emoji&&e.emoji!=="from-font"&&(n=f(n,e.emoji)),!e.fetchedResources){let a=l(n);a.length>0&&(e.fetchedResources=await c(a))}return{node:n,options:e}}function O(s,t){return new ReadableStream({type:"bytes",async start(e){try{await y(t);let n=g(t),{node:r,options:a}=await I(s,t),m=n.render(r,a);e.enqueue(m),e.close()}catch(n){e.error(n)}}})}var h={png:"image/png",jpeg:"image/jpeg",webp:"image/webp",raw:"application/octet-stream"},j={format:"webp"},p=class extends Response{constructor(t,e){let n=O(t,e),r=new Headers(e.headers);r.get("content-type")||r.set("content-type",h[e.format??j.format]),super(n,{status:e.status,statusText:e.statusText,headers:r})}},F=p;export{p as ImageResponse,F as default};