wasm-html-to-image 0.1.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -0
  3. package/dist/browser-workers.d.ts +12 -0
  4. package/dist/browser-workers.js +26 -0
  5. package/dist/child-workers.d.ts +11 -0
  6. package/dist/child-workers.js +19 -0
  7. package/dist/cli.d.ts +2 -0
  8. package/dist/cli.js +87 -0
  9. package/dist/core.d.ts +168 -0
  10. package/dist/core.js +824 -0
  11. package/dist/diagnostics.d.ts +93 -0
  12. package/dist/diagnostics.js +28 -0
  13. package/dist/edge-light.d.ts +18 -0
  14. package/dist/edge-light.js +70 -0
  15. package/dist/encode-args.d.ts +10 -0
  16. package/dist/encode-args.js +30 -0
  17. package/dist/html-to-image-single.js +0 -0
  18. package/dist/html-to-image-wasm.js +3 -0
  19. package/dist/html-to-image.js +2 -0
  20. package/dist/html-to-image.wasm +0 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.js +14 -0
  23. package/dist/input.d.ts +23 -0
  24. package/dist/input.js +90 -0
  25. package/dist/loader.d.ts +132 -0
  26. package/dist/loader.js +199 -0
  27. package/dist/preact.d.ts +10 -0
  28. package/dist/preact.js +13 -0
  29. package/dist/react.d.ts +9 -0
  30. package/dist/react.js +11 -0
  31. package/dist/single.d.ts +27 -0
  32. package/dist/single.js +22 -0
  33. package/dist/tailwind.d.ts +1 -0
  34. package/dist/tailwind.js +6 -0
  35. package/dist/wasm-payload.d.ts +2 -0
  36. package/dist/wasm-payload.js +39 -0
  37. package/dist/web-workers.js +4411 -0
  38. package/dist/worker-actions.d.ts +10 -0
  39. package/dist/worker-actions.js +13 -0
  40. package/dist/worker-lib-loader.d.ts +19 -0
  41. package/dist/worker-lib-loader.js +34 -0
  42. package/dist/workerd.d.ts +18 -0
  43. package/dist/workerd.js +65 -0
  44. package/dist/workers-dummy.d.ts +12 -0
  45. package/dist/workers-dummy.js +6 -0
  46. package/dist/workers-parent.js +602 -0
  47. package/dist/workers.d.ts +86 -0
  48. package/dist/workers.js +163 -0
  49. package/package.json +166 -0
@@ -0,0 +1,602 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __esmMin = (fn, res, err) => () => {
4
+ if (err) throw err[0];
5
+ try {
6
+ return fn && (res = fn(fn = 0)), res;
7
+ } catch (e) {
8
+ throw err = [e], e;
9
+ }
10
+ };
11
+ var __exportAll = (all, no_symbols) => {
12
+ let target = {};
13
+ for (var name in all) __defProp(target, name, {
14
+ get: all[name],
15
+ enumerable: true
16
+ });
17
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
18
+ return target;
19
+ };
20
+ //#endregion
21
+ //#region src/loader.ts
22
+ /**
23
+ * Runtime-only load of a `node:` builtin (e.g. `importNode("fs/promises")`).
24
+ * Uses `process.getBuiltinModule` (Node 22.3+/20.16+) so there is NO `import`
25
+ * syntax at all: bundlers with static analysis (webpack/vite/rollup, and
26
+ * Turbopack — the Next.js 16 default builder, which rejects both dynamic
27
+ * `` `node:${name}` `` requests and literal `import("node:*")` inside Edge
28
+ * graphs) have nothing to resolve or rewrite. Only the builtins actually
29
+ * used by this package are supported. All call sites are guarded to run on
30
+ * Node only; other runtimes never evaluate these branches. A `new Function`
31
+ * indirect-import fallback keeps older Node working without adding any
32
+ * statically analyzable import.
33
+ */
34
+ function importNode(name) {
35
+ if (name !== "fs/promises" && name !== "path" && name !== "module") throw new Error(`wasm-html-to-image: unsupported node builtin "${name}"`);
36
+ const proc = globalThis.process;
37
+ try {
38
+ const mod = proc?.getBuiltinModule?.(`node:${name}`);
39
+ if (mod) return Promise.resolve(mod);
40
+ } catch {}
41
+ return new Function("s", "return import(s)")(`node:${name}`);
42
+ }
43
+ //#endregion
44
+ //#region ../../node_modules/.pnpm/worker-lib@2.2.1/node_modules/worker-lib/dist/esm/common.js
45
+ var FUNCTION_PLACEHOLDER, isPlaceholder, isPlainObject, getTransferables, createRegistry;
46
+ var init_common = __esmMin((() => {
47
+ FUNCTION_PLACEHOLDER = "__worker_lib_function__";
48
+ isPlaceholder = (v) => v && typeof v === "object" && "__worker_lib_function__" in v;
49
+ isPlainObject = (v) => {
50
+ return v && typeof v === "object" && Object.prototype.toString.call(v) === "[object Object]" && !(v instanceof Uint8Array) && !(v instanceof ArrayBuffer) && !ArrayBuffer.isView(v);
51
+ };
52
+ getTransferables = (v, result = []) => {
53
+ if (v instanceof ArrayBuffer) result.push(v);
54
+ else if (ArrayBuffer.isView(v)) result.push(v.buffer);
55
+ else if (Array.isArray(v)) for (const item of v) getTransferables(item, result);
56
+ else if (v && typeof v === "object") for (const key in v) getTransferables(v[key], result);
57
+ return result;
58
+ };
59
+ createRegistry = () => {
60
+ const callbacks = /* @__PURE__ */ new Map();
61
+ const callbackProxies = /* @__PURE__ */ new Map();
62
+ const registerCallback = (requestId, fn) => {
63
+ const id = `${requestId}:${Math.random().toString(36).slice(2)}`;
64
+ callbacks.set(id, fn);
65
+ return id;
66
+ };
67
+ const clearCallbacks = (requestId) => {
68
+ for (const key of callbacks.keys()) if (key.startsWith(`${requestId}:`)) callbacks.delete(key);
69
+ for (const key of callbackProxies.keys()) if (key.startsWith(`${requestId}:`)) callbackProxies.delete(key);
70
+ };
71
+ const transformArgs = (requestId, args) => {
72
+ if (typeof args === "function") return { [FUNCTION_PLACEHOLDER]: registerCallback(requestId, args) };
73
+ if (ArrayBuffer.isView(args) || args instanceof ArrayBuffer) return args;
74
+ if (Array.isArray(args)) return args.map((v) => transformArgs(requestId, v));
75
+ if (isPlainObject(args)) {
76
+ const result = {};
77
+ for (const key in args) result[key] = transformArgs(requestId, args[key]);
78
+ return result;
79
+ }
80
+ return args;
81
+ };
82
+ const resolveArgs = (requestId, args, createProxy) => {
83
+ if (isPlaceholder(args)) return createProxy(args[FUNCTION_PLACEHOLDER]);
84
+ if (Array.isArray(args)) return args.map((v) => resolveArgs(requestId, v, createProxy));
85
+ if (isPlainObject(args)) {
86
+ const result = {};
87
+ for (const key in args) result[key] = resolveArgs(requestId, args[key], createProxy);
88
+ return result;
89
+ }
90
+ return args;
91
+ };
92
+ return {
93
+ callbacks,
94
+ callbackProxies,
95
+ registerCallback,
96
+ clearCallbacks,
97
+ transformArgs,
98
+ resolveArgs
99
+ };
100
+ };
101
+ }));
102
+ //#endregion
103
+ //#region ../../node_modules/.pnpm/worker-lib@2.2.1/node_modules/worker-lib/dist/esm/pool.js
104
+ var requestIdCounter, exec, createWorkerPool, initUniversalWorker;
105
+ var init_pool = __esmMin((() => {
106
+ init_common();
107
+ requestIdCounter = 0;
108
+ exec = (worker, name, registry, ...args) => {
109
+ const requestId = requestIdCounter++;
110
+ const { callbacks, callbackProxies, clearCallbacks, transformArgs, resolveArgs } = registry;
111
+ return new Promise((resolve, reject) => {
112
+ const createProxy = (callbackId) => {
113
+ const key = `${requestId}:${callbackId}`;
114
+ if (callbackProxies.has(key)) return callbackProxies.get(key);
115
+ const proxy = (...proxyArgs) => {
116
+ const callId = Math.random().toString(36).slice(2);
117
+ return new Promise((res) => {
118
+ const handler = (data) => {
119
+ if (data.type === "callback_result" && data.payload.id === callId) {
120
+ worker.removeEventListener("message", handler);
121
+ res(resolveArgs(requestId, data.payload.result, createProxy));
122
+ }
123
+ };
124
+ worker.addEventListener("message", handler);
125
+ const transformedProxyArgs = transformArgs(requestId, proxyArgs);
126
+ worker.postMessage({
127
+ type: "callback_call",
128
+ payload: {
129
+ id: requestId,
130
+ callbackId,
131
+ args: transformedProxyArgs,
132
+ callId
133
+ }
134
+ }, getTransferables(transformedProxyArgs));
135
+ });
136
+ };
137
+ callbackProxies.set(key, proxy);
138
+ return proxy;
139
+ };
140
+ const messageHandler = async (data) => {
141
+ if (!data || typeof data !== "object") return;
142
+ const payload = data.payload;
143
+ if (payload?.id !== requestId) return;
144
+ if (data.type === "result") {
145
+ worker.removeEventListener("message", messageHandler);
146
+ const result = resolveArgs(requestId, payload.result, createProxy);
147
+ clearCallbacks(requestId);
148
+ resolve(result);
149
+ } else if (data.type === "error") {
150
+ worker.removeEventListener("message", messageHandler);
151
+ clearCallbacks(requestId);
152
+ reject(payload.error);
153
+ } else if (data.type === "callback_call") {
154
+ const { callbackId, args: callArgs, callId } = payload;
155
+ const fn = callbacks.get(callbackId);
156
+ if (fn) try {
157
+ const result = await fn(...resolveArgs(requestId, callArgs, createProxy));
158
+ const transformedResult = transformArgs(requestId, result);
159
+ worker.postMessage({
160
+ type: "callback_result",
161
+ payload: {
162
+ id: callId,
163
+ result: transformedResult
164
+ }
165
+ }, getTransferables(transformedResult));
166
+ } catch (e) {
167
+ console.error("[worker-lib] Callback execution failed:", e);
168
+ }
169
+ }
170
+ };
171
+ worker.addEventListener("message", messageHandler);
172
+ const transformedArgs = transformArgs(requestId, args);
173
+ worker.postMessage({
174
+ type: "function",
175
+ payload: {
176
+ id: requestId,
177
+ name,
178
+ args: transformedArgs
179
+ }
180
+ }, getTransferables(transformedArgs));
181
+ });
182
+ };
183
+ createWorkerPool = (builder, limit = 4) => {
184
+ let workers = Array(limit).fill(void 0).map(() => ({}));
185
+ const emptyWaits = [];
186
+ let isEmptyWait = false;
187
+ const registry = createRegistry();
188
+ const getResolver = async () => {
189
+ while (true) {
190
+ const target = workers.find(({ resultResolver }) => !resultResolver);
191
+ if (target) {
192
+ target.resultResolver = Promise.withResolvers();
193
+ if (!target.worker) target.worker = await builder();
194
+ return target;
195
+ }
196
+ await Promise.race(workers.map(({ resultResolver }) => resultResolver?.promise));
197
+ }
198
+ };
199
+ const execute = async (name, ...value) => {
200
+ const target = await getResolver();
201
+ const { resultResolver } = target;
202
+ if (!resultResolver) throw new Error("Unexpected error");
203
+ exec(target.worker, name, registry, ...value).then(resultResolver.resolve).catch(resultResolver.reject).finally(() => {
204
+ target.resultResolver = void 0;
205
+ });
206
+ return resultResolver.promise;
207
+ };
208
+ const launchWorker = async () => {
209
+ return Promise.all(workers.map(async (target) => {
210
+ if (!target.worker) target.worker = await builder();
211
+ }));
212
+ };
213
+ const waitAll = async () => {
214
+ while (workers.find(({ resultResolver }) => resultResolver)) await Promise.all(workers.flatMap(({ resultResolver }) => resultResolver ? [resultResolver.promise] : []));
215
+ };
216
+ const waitReady = async (retryTime = 1) => {
217
+ const p = Promise.withResolvers();
218
+ emptyWaits.push(p);
219
+ (async () => {
220
+ if (!isEmptyWait) {
221
+ isEmptyWait = true;
222
+ do {
223
+ const actives = workers.flatMap(({ resultResolver }) => resultResolver ? [resultResolver.promise] : []);
224
+ if (workers.length === actives.length) await Promise.race(actives);
225
+ emptyWaits.shift()?.resolve();
226
+ if (retryTime) await new Promise((r) => setTimeout(r, retryTime));
227
+ else await Promise.resolve();
228
+ } while (emptyWaits.length);
229
+ isEmptyWait = false;
230
+ }
231
+ })();
232
+ return p.promise;
233
+ };
234
+ const close = () => {
235
+ for (const target of workers) {
236
+ target.worker?.terminate();
237
+ target.worker = void 0;
238
+ target.resultResolver = void 0;
239
+ }
240
+ };
241
+ const setLimit = (newLimit) => {
242
+ workers.forEach((w) => w.worker?.terminate());
243
+ workers = Array(newLimit).fill(void 0).map(() => ({}));
244
+ };
245
+ return {
246
+ execute,
247
+ waitAll,
248
+ waitReady,
249
+ close,
250
+ setLimit,
251
+ launchWorker
252
+ };
253
+ };
254
+ initUniversalWorker = (worker, WorkerProc) => {
255
+ const { callbacks, callbackProxies, registerCallback, clearCallbacks, transformArgs, resolveArgs } = createRegistry();
256
+ const createProxy = (callbackId, requestId) => {
257
+ const key = `${requestId}:${callbackId}`;
258
+ if (callbackProxies.has(key)) return callbackProxies.get(key);
259
+ const proxy = (...proxyArgs) => {
260
+ const callId = Math.random().toString(36).slice(2);
261
+ return new Promise((res) => {
262
+ const handler = (data) => {
263
+ if (data.type === "callback_result" && data.payload.id === callId) {
264
+ worker.removeEventListener("message", handler);
265
+ res(resolveArgs(requestId, data.payload.result, (cbId) => createProxy(cbId, requestId)));
266
+ }
267
+ };
268
+ worker.addEventListener("message", handler);
269
+ const transformedArgs = transformArgs(requestId, proxyArgs);
270
+ worker.postMessage({
271
+ type: "callback_call",
272
+ payload: {
273
+ id: requestId,
274
+ callbackId,
275
+ args: transformedArgs,
276
+ callId
277
+ }
278
+ }, getTransferables(transformedArgs));
279
+ });
280
+ };
281
+ callbackProxies.set(key, proxy);
282
+ return proxy;
283
+ };
284
+ worker.addEventListener("message", async (data) => {
285
+ if (!data) return;
286
+ if (data.type === "function") {
287
+ const { id, name, args } = data.payload;
288
+ const proc = WorkerProc[name];
289
+ if (proc) try {
290
+ const result = await proc(...resolveArgs(id, args, (cbId) => createProxy(cbId, id)));
291
+ const transformedResult = transformArgs(id, result);
292
+ worker.postMessage({
293
+ type: "result",
294
+ payload: {
295
+ id,
296
+ result: transformedResult
297
+ }
298
+ }, getTransferables(transformedResult));
299
+ clearCallbacks(id);
300
+ } catch (error) {
301
+ worker.postMessage({
302
+ type: "error",
303
+ payload: {
304
+ id,
305
+ error: String(error)
306
+ }
307
+ });
308
+ clearCallbacks(id);
309
+ }
310
+ } else if (data.type === "callback_call") {
311
+ const { id, callbackId, args, callId } = data.payload;
312
+ const fn = callbacks.get(callbackId);
313
+ if (fn) try {
314
+ const result = await fn(...resolveArgs(id, args, (cbId) => createProxy(cbId, id)));
315
+ const transformedResult = transformArgs(id, result);
316
+ worker.postMessage({
317
+ type: "callback_result",
318
+ payload: {
319
+ id: callId,
320
+ result: transformedResult
321
+ }
322
+ }, getTransferables(transformedResult));
323
+ } catch (e) {
324
+ console.error("[worker-lib] Worker-side callback failed:", e);
325
+ }
326
+ }
327
+ });
328
+ worker.postMessage("ready");
329
+ return WorkerProc;
330
+ };
331
+ }));
332
+ //#endregion
333
+ //#region ../../node_modules/.pnpm/worker-lib@2.2.1/node_modules/worker-lib/dist/esm/index.js
334
+ var esm_exports = /* @__PURE__ */ __exportAll({
335
+ Worker: () => w,
336
+ createWorker: () => createWorker$1,
337
+ initWorker: () => initWorker
338
+ });
339
+ var w, wrapWorker, init, createWorker$1, initWorker;
340
+ var init_esm = __esmMin((() => {
341
+ init_pool();
342
+ w = Worker;
343
+ wrapWorker = (worker) => ({
344
+ postMessage: (message, transfer) => worker.postMessage(message, transfer),
345
+ addEventListener: (type, listener) => {
346
+ const handler = (e) => {
347
+ if (e instanceof MessageEvent) listener(e.data);
348
+ };
349
+ listener._handler = handler;
350
+ worker.addEventListener(type, handler);
351
+ },
352
+ removeEventListener: (type, listener) => {
353
+ const handler = listener._handler;
354
+ worker.removeEventListener(type, handler);
355
+ },
356
+ terminate: () => worker.terminate()
357
+ });
358
+ init = (worker) => {
359
+ return new Promise((resolve) => {
360
+ const handler = (e) => {
361
+ if (e.data === "ready") {
362
+ worker.removeEventListener("message", handler);
363
+ resolve(worker);
364
+ }
365
+ };
366
+ worker.addEventListener("message", handler);
367
+ });
368
+ };
369
+ createWorker$1 = (builder, limit = 4) => {
370
+ return createWorkerPool(async () => {
371
+ const result = builder();
372
+ const worker = result instanceof Worker ? result : new Worker(result);
373
+ await init(worker);
374
+ return wrapWorker(worker);
375
+ }, limit);
376
+ };
377
+ initWorker = (WorkerProc) => {
378
+ const worker = self;
379
+ return initUniversalWorker({
380
+ postMessage: (message, transfer) => worker.postMessage(message, transfer),
381
+ addEventListener: (type, listener) => {
382
+ const handler = (e) => {
383
+ if (e instanceof MessageEvent) listener(e.data);
384
+ };
385
+ listener._handler = handler;
386
+ worker.addEventListener(type, handler);
387
+ },
388
+ removeEventListener: (type, listener) => {
389
+ const handler = listener._handler;
390
+ worker.removeEventListener(type, handler);
391
+ },
392
+ terminate: () => {}
393
+ }, WorkerProc);
394
+ };
395
+ }));
396
+ //#endregion
397
+ //#region src/worker-lib-loader.ts
398
+ let cached;
399
+ /**
400
+ * Load worker-lib, tolerating its broken native-ESM build: the published
401
+ * `dist/esm/*.js` uses extensionless relative imports (`./pool`), which
402
+ * throw `ERR_MODULE_NOT_FOUND` under plain Node (2.2.0/2.2.1; upstream
403
+ * dodges this via bundler resolution). Plain Node falls back to the CJS
404
+ * build via `createRequire`, which resolves fine.
405
+ *
406
+ * No `@vite-ignore` on the first import so bundlers still substitute the
407
+ * browser build for browser targets.
408
+ */
409
+ function loadWorkerLib() {
410
+ if (!cached) {
411
+ cached = (async () => {
412
+ try {
413
+ return await Promise.resolve().then(() => (init_esm(), esm_exports));
414
+ } catch {
415
+ return (await importNode("module")).createRequire(import.meta.url)("worker-lib");
416
+ }
417
+ })();
418
+ cached.catch(() => {
419
+ resetWorkerLib();
420
+ });
421
+ }
422
+ return cached;
423
+ }
424
+ /** Drop the cached load (e.g. after a failed load) so it can be retried. */
425
+ function resetWorkerLib() {
426
+ cached = void 0;
427
+ }
428
+ //#endregion
429
+ //#region src/diagnostics.ts
430
+ /**
431
+ * Playground-parity diagnostics (satoru-compatible shapes).
432
+ *
433
+ * `LogLevel` mirrors `satoru/log-level.ts` verbatim (numeric levels shared
434
+ * with the WASM `set_log_level` binding). `RenderDiagnostics` mirrors
435
+ * satoru's `RenderDiagnostics` so playground-style consumers can switch
436
+ * imports without reshaping reports.
437
+ */
438
+ /** Log severity, shared with the WASM `set_log_level` binding. */
439
+ let LogLevel = /* @__PURE__ */ function(LogLevel) {
440
+ LogLevel[LogLevel["None"] = 0] = "None";
441
+ LogLevel[LogLevel["Error"] = 1] = "Error";
442
+ LogLevel[LogLevel["Warning"] = 2] = "Warning";
443
+ LogLevel[LogLevel["Info"] = 3] = "Info";
444
+ LogLevel[LogLevel["Debug"] = 4] = "Debug";
445
+ return LogLevel;
446
+ }({});
447
+ /** Machine-readable diagnostic codes (satoru parity). */
448
+ const DIAGNOSTIC_CODES = {
449
+ LIMIT_TIMEOUT: "LIMIT_TIMEOUT",
450
+ LIMIT_RESOURCE_SIZE: "LIMIT_RESOURCE_SIZE",
451
+ LIMIT_TOTAL_SIZE: "LIMIT_TOTAL_SIZE",
452
+ LIMIT_RESOURCE_COUNT: "LIMIT_RESOURCE_COUNT",
453
+ LIMIT_PROTOCOL_BLOCKED: "LIMIT_PROTOCOL_BLOCKED",
454
+ LIMIT_HOST_BLOCKED: "LIMIT_HOST_BLOCKED",
455
+ /** Extension: a resource fetch (or resolveResource hook) failed. */
456
+ RESOURCE_FETCH_FAILED: "RESOURCE_FETCH_FAILED"
457
+ };
458
+ //#endregion
459
+ //#region src/core.ts
460
+ const EMOJI_URL = "https://cdn.jsdelivr.net/npm/@fontsource/noto-color-emoji/files/noto-color-emoji-emoji-400-normal.woff2";
461
+ /**
462
+ * Upstream default font strategy (verbatim from satoru `DEFAULT_FONT_MAP`):
463
+ * generic families resolve to Google Fonts css2 URLs (fetched with a browser
464
+ * UA so woff2 is served); fetched at render time, nothing bundled.
465
+ */
466
+ const DEFAULT_FONT_MAP = {
467
+ "sans-serif": "https://fonts.googleapis.com/css2?family=Noto+Sans+JP",
468
+ serif: "https://fonts.googleapis.com/css2?family=Noto+Serif+JP",
469
+ monospace: "https://fonts.googleapis.com/css2?family=M+PLUS+1+Code",
470
+ cursive: "https://fonts.googleapis.com/css2?family=Yuji+Syuku",
471
+ fantasy: "https://fonts.googleapis.com/css2?family=Reggae+One",
472
+ "Noto Color Emoji": EMOJI_URL,
473
+ emoji: EMOJI_URL,
474
+ "Noto Emoji": EMOJI_URL,
475
+ notocoloremoji: EMOJI_URL,
476
+ notoemoji: EMOJI_URL
477
+ };
478
+ //#endregion
479
+ //#region src/workers.ts
480
+ const { createWorker, Worker: Worker$1 } = await loadWorkerLib();
481
+ /**
482
+ * Create a wasm-html-to-image worker pool using worker-lib.
483
+ * The pooled action is the unified `render` (HTML and image inputs).
484
+ * Each worker thread loads its own module instance via the loader
485
+ * (modules cannot cross thread boundaries).
486
+ *
487
+ * @param params Initialization parameters
488
+ * @param params.worker Optional: Path to the worker file, a URL, or a factory function.
489
+ * Defaults to the pre-bundled browser worker
490
+ * (`./web-workers.js`, SINGLE_FILE inlined) when
491
+ * `window` exists, else the Node worker
492
+ * (`./child-workers.js`) next to this file.
493
+ * @param params.maxParallel Maximum number of parallel workers (default: 4)
494
+ * @param params.timeoutMs Optional: Default timeout in milliseconds for each render job.
495
+ * If a render job times out, the pool is reset to prevent hung workers.
496
+ */
497
+ const createHtmlToImageWorker = (params) => {
498
+ const { worker, maxParallel = 4, timeoutMs } = params ?? {};
499
+ const factory = () => {
500
+ let w;
501
+ if (worker) w = typeof worker === "function" ? worker() : worker;
502
+ else {
503
+ const workerUrl = typeof window !== "undefined" ? new URL("./web-workers.js", import.meta.url) : new URL("./child-workers.js", import.meta.url);
504
+ if (typeof Worker$1 !== "undefined") w = new Worker$1(workerUrl, { type: "module" });
505
+ }
506
+ if (!w) throw new Error("Worker is not supported in this environment.");
507
+ return w;
508
+ };
509
+ const workerInstance = createWorker(factory, maxParallel);
510
+ let totalPendingJobs = 0;
511
+ let completedJobs = 0;
512
+ let failedJobs = 0;
513
+ let totalJobTimeMs = 0;
514
+ /**
515
+ * Retrieve operational stats of the worker pool.
516
+ */
517
+ const getStats = () => ({
518
+ workerCount: maxParallel,
519
+ activeJobs: Math.min(totalPendingJobs, maxParallel),
520
+ queuedJobs: Math.max(0, totalPendingJobs - maxParallel),
521
+ completedJobs,
522
+ failedJobs,
523
+ avgJobTimeMs: completedJobs > 0 ? totalJobTimeMs / completedJobs : 0
524
+ });
525
+ /**
526
+ * Reset the worker pool by terminating all running workers and recreating them.
527
+ * Useful to clear hung workers or reset statistics.
528
+ */
529
+ const reset = () => {
530
+ workerInstance.setLimit(0);
531
+ workerInstance.setLimit(maxParallel);
532
+ totalPendingJobs = 0;
533
+ completedJobs = 0;
534
+ failedJobs = 0;
535
+ totalJobTimeMs = 0;
536
+ };
537
+ return new Proxy(workerInstance, { get(target, prop, receiver) {
538
+ if (prop === "getStats") return getStats;
539
+ if (prop === "reset") return reset;
540
+ if (prop === "render") return async (options) => {
541
+ totalPendingJobs++;
542
+ const startTime = Date.now();
543
+ let timeoutId;
544
+ let timeoutPromise;
545
+ if (timeoutMs !== void 0 && timeoutMs > 0) timeoutPromise = new Promise((_, reject) => {
546
+ timeoutId = setTimeout(() => {
547
+ reset();
548
+ reject(/* @__PURE__ */ new Error(`Render timed out after ${timeoutMs}ms`));
549
+ }, timeoutMs);
550
+ });
551
+ let execOptions = options;
552
+ if (options && typeof options === "object") {
553
+ if (options.value instanceof Uint8Array) execOptions = {
554
+ ...options,
555
+ value: options.value.slice()
556
+ };
557
+ else if (options.value instanceof ArrayBuffer) execOptions = {
558
+ ...options,
559
+ value: options.value.slice(0)
560
+ };
561
+ }
562
+ try {
563
+ const executePromise = target.execute("render", execOptions);
564
+ const result = await (timeoutPromise ? Promise.race([executePromise, timeoutPromise]) : executePromise);
565
+ completedJobs++;
566
+ totalJobTimeMs += Date.now() - startTime;
567
+ return result;
568
+ } catch (e) {
569
+ failedJobs++;
570
+ throw e;
571
+ } finally {
572
+ if (timeoutId) clearTimeout(timeoutId);
573
+ totalPendingJobs--;
574
+ }
575
+ };
576
+ if (prop in target) return Reflect.get(target, prop, receiver);
577
+ } });
578
+ };
579
+ let defaultWorker;
580
+ /** Lazily create the shared single-slot pool on first use (not at import). */
581
+ function getDefaultWorker() {
582
+ if (!defaultWorker) defaultWorker = createHtmlToImageWorker({ maxParallel: 1 });
583
+ return defaultWorker;
584
+ }
585
+ const close = () => {
586
+ getDefaultWorker().close();
587
+ };
588
+ function render(options) {
589
+ return getDefaultWorker().render(options);
590
+ }
591
+ const launchWorker = () => getDefaultWorker().launchWorker();
592
+ const setLimit = (limit) => {
593
+ getDefaultWorker().setLimit(limit);
594
+ };
595
+ const waitAll = () => getDefaultWorker().waitAll();
596
+ const waitReady = (retryTime) => getDefaultWorker().waitReady(retryTime);
597
+ const reset = () => {
598
+ getDefaultWorker().reset();
599
+ };
600
+ const getStats = () => getDefaultWorker().getStats();
601
+ //#endregion
602
+ export { DEFAULT_FONT_MAP, DIAGNOSTIC_CODES, LogLevel, close, createHtmlToImageWorker, getStats, launchWorker, render, reset, setLimit, waitAll, waitReady };
@@ -0,0 +1,86 @@
1
+ export type { HtmlToImageWorker } from "./child-workers.js";
2
+ import { type HtmlToImageOptions, type RenderResult } from "./core.js";
3
+ export type { HtmlToImageOptions, RenderResult } from "./core.js";
4
+ export { DEFAULT_FONT_MAP, DIAGNOSTIC_CODES, LogLevel } from "./core.js";
5
+ export type { DiagnosticMessage, FontDiagnostic, RenderDiagnostics, RenderLimits, ResolveResourceHook, ResourceDiagnostic, } from "./core.js";
6
+ /** Operational stats of the worker pool. */
7
+ export interface WorkerPoolStats {
8
+ /** Number of workers in the pool */
9
+ workerCount: number;
10
+ /** Jobs currently being executed */
11
+ activeJobs: number;
12
+ /** Jobs waiting for a free worker */
13
+ queuedJobs: number;
14
+ /** Total jobs completed successfully since start */
15
+ completedJobs: number;
16
+ /** Total jobs that failed since start */
17
+ failedJobs: number;
18
+ /** Average time per job in milliseconds */
19
+ avgJobTimeMs: number;
20
+ }
21
+ /**
22
+ * Create a wasm-html-to-image worker pool using worker-lib.
23
+ * The pooled action is the unified `render` (HTML and image inputs).
24
+ * Each worker thread loads its own module instance via the loader
25
+ * (modules cannot cross thread boundaries).
26
+ *
27
+ * @param params Initialization parameters
28
+ * @param params.worker Optional: Path to the worker file, a URL, or a factory function.
29
+ * Defaults to the pre-bundled browser worker
30
+ * (`./web-workers.js`, SINGLE_FILE inlined) when
31
+ * `window` exists, else the Node worker
32
+ * (`./child-workers.js`) next to this file.
33
+ * @param params.maxParallel Maximum number of parallel workers (default: 4)
34
+ * @param params.timeoutMs Optional: Default timeout in milliseconds for each render job.
35
+ * If a render job times out, the pool is reset to prevent hung workers.
36
+ */
37
+ export declare const createHtmlToImageWorker: (params?: {
38
+ worker?: string | URL | (() => Worker | string | URL);
39
+ maxParallel?: number;
40
+ timeoutMs?: number;
41
+ }) => Omit<{
42
+ execute: <K extends "render">(name: K, ...value: Parameters<{
43
+ render(options: HtmlToImageOptions): Promise<RenderResult<Uint8Array | string>>;
44
+ }[K]>) => Promise<Awaited<ReturnType<{
45
+ render(options: HtmlToImageOptions): Promise<RenderResult<Uint8Array | string>>;
46
+ }[K]>>>;
47
+ waitAll: () => Promise<void>;
48
+ waitReady: (retryTime?: number) => Promise<void>;
49
+ close: () => void;
50
+ setLimit: (newLimit: number) => void;
51
+ launchWorker: () => Promise<void[]>;
52
+ }, "execute"> & {
53
+ render(options: HtmlToImageOptions): Promise<RenderResult<Uint8Array | string>>;
54
+ } & {
55
+ /**
56
+ * Retrieve operational stats of the worker pool.
57
+ */
58
+ getStats: () => WorkerPoolStats;
59
+ /**
60
+ * Reset the worker pool by terminating all running workers and recreating them.
61
+ */
62
+ reset: () => void;
63
+ /**
64
+ * Close and terminate all workers in the pool immediately.
65
+ */
66
+ close: () => void;
67
+ /**
68
+ * Wait for all running tasks in the pool to complete.
69
+ */
70
+ waitAll: () => Promise<void>;
71
+ /**
72
+ * Wait until there is an available worker slot in the pool.
73
+ */
74
+ waitReady: (retryTime?: number) => Promise<void>;
75
+ };
76
+ export declare const close: () => void;
77
+ export declare function render(options: HtmlToImageOptions & {
78
+ format: "svg";
79
+ }): Promise<RenderResult<string>>;
80
+ export declare function render(options: HtmlToImageOptions): Promise<RenderResult<Uint8Array | string>>;
81
+ export declare const launchWorker: () => Promise<void[]>;
82
+ export declare const setLimit: (limit: number) => void;
83
+ export declare const waitAll: () => Promise<void>;
84
+ export declare const waitReady: (retryTime?: number) => Promise<void>;
85
+ export declare const reset: () => void;
86
+ export declare const getStats: () => WorkerPoolStats;