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,163 @@
1
+ import { loadWorkerLib } from "./worker-lib-loader.js";
2
+ // Playground parity: diagnostics surface is usable through the pool —
3
+ // function-valued options (`onLog`/`onDiagnostics`/`resolveResource`) are
4
+ // forwarded untouched by the `render` proxy below (worker-lib RPC).
5
+ export { DEFAULT_FONT_MAP, DIAGNOSTIC_CODES, LogLevel } from "./core.js";
6
+ // worker-lib's published ESM is broken under plain Node (see
7
+ // worker-lib-loader.ts); top-level await keeps call sites sync-shaped.
8
+ const { createWorker, Worker } = await loadWorkerLib();
9
+ /**
10
+ * Create a wasm-html-to-image worker pool using worker-lib.
11
+ * The pooled action is the unified `render` (HTML and image inputs).
12
+ * Each worker thread loads its own module instance via the loader
13
+ * (modules cannot cross thread boundaries).
14
+ *
15
+ * @param params Initialization parameters
16
+ * @param params.worker Optional: Path to the worker file, a URL, or a factory function.
17
+ * Defaults to the pre-bundled browser worker
18
+ * (`./web-workers.js`, SINGLE_FILE inlined) when
19
+ * `window` exists, else the Node worker
20
+ * (`./child-workers.js`) next to this file.
21
+ * @param params.maxParallel Maximum number of parallel workers (default: 4)
22
+ * @param params.timeoutMs Optional: Default timeout in milliseconds for each render job.
23
+ * If a render job times out, the pool is reset to prevent hung workers.
24
+ */
25
+ export const createHtmlToImageWorker = (params) => {
26
+ const { worker, maxParallel = 4, timeoutMs } = params ?? {};
27
+ const factory = () => {
28
+ let w;
29
+ if (worker) {
30
+ w = typeof worker === "function" ? worker() : worker;
31
+ }
32
+ else {
33
+ // child-workers.js = Node worker; web-workers.js = pre-bundled
34
+ // browser worker (SINGLE_FILE inlined, no .wasm fetch needed).
35
+ const workerUrl = typeof window !== "undefined"
36
+ ? new URL("./web-workers.js", import.meta.url)
37
+ : new URL("./child-workers.js", import.meta.url);
38
+ if (typeof Worker !== "undefined") {
39
+ w = new Worker(workerUrl, { type: "module" });
40
+ }
41
+ }
42
+ if (!w)
43
+ throw new Error("Worker is not supported in this environment.");
44
+ return w;
45
+ };
46
+ const workerInstance = createWorker(factory, maxParallel);
47
+ let totalPendingJobs = 0;
48
+ let completedJobs = 0;
49
+ let failedJobs = 0;
50
+ let totalJobTimeMs = 0;
51
+ /**
52
+ * Retrieve operational stats of the worker pool.
53
+ */
54
+ const getStats = () => ({
55
+ workerCount: maxParallel,
56
+ activeJobs: Math.min(totalPendingJobs, maxParallel),
57
+ queuedJobs: Math.max(0, totalPendingJobs - maxParallel),
58
+ completedJobs,
59
+ failedJobs,
60
+ avgJobTimeMs: completedJobs > 0 ? totalJobTimeMs / completedJobs : 0,
61
+ });
62
+ /**
63
+ * Reset the worker pool by terminating all running workers and recreating them.
64
+ * Useful to clear hung workers or reset statistics.
65
+ */
66
+ const reset = () => {
67
+ workerInstance.setLimit(0);
68
+ workerInstance.setLimit(maxParallel);
69
+ totalPendingJobs = 0;
70
+ completedJobs = 0;
71
+ failedJobs = 0;
72
+ totalJobTimeMs = 0;
73
+ };
74
+ const proxy = new Proxy(workerInstance, {
75
+ get(target, prop, receiver) {
76
+ if (prop === "getStats") {
77
+ return getStats;
78
+ }
79
+ if (prop === "reset") {
80
+ return reset;
81
+ }
82
+ if (prop === "render") {
83
+ return async (options) => {
84
+ totalPendingJobs++;
85
+ const startTime = Date.now();
86
+ let timeoutId;
87
+ let timeoutPromise;
88
+ if (timeoutMs !== undefined && timeoutMs > 0) {
89
+ timeoutPromise = new Promise((_, reject) => {
90
+ timeoutId = setTimeout(() => {
91
+ // If a job times out, terminate and recreate workers to recover from a potential hang
92
+ reset();
93
+ reject(new Error(`Render timed out after ${timeoutMs}ms`));
94
+ }, timeoutMs);
95
+ });
96
+ }
97
+ // Protect caller's buffer from being detached if worker-lib transfers TypedArrays
98
+ let execOptions = options;
99
+ if (options && typeof options === "object") {
100
+ if (options.value instanceof Uint8Array) {
101
+ execOptions = { ...options, value: options.value.slice() };
102
+ }
103
+ else if (options.value instanceof ArrayBuffer) {
104
+ execOptions = { ...options, value: options.value.slice(0) };
105
+ }
106
+ }
107
+ try {
108
+ const executePromise = target.execute("render", execOptions);
109
+ const result = await (timeoutPromise
110
+ ? Promise.race([executePromise, timeoutPromise])
111
+ : executePromise);
112
+ completedJobs++;
113
+ totalJobTimeMs += Date.now() - startTime;
114
+ return result;
115
+ }
116
+ catch (e) {
117
+ failedJobs++;
118
+ throw e;
119
+ }
120
+ finally {
121
+ if (timeoutId) {
122
+ clearTimeout(timeoutId);
123
+ }
124
+ totalPendingJobs--;
125
+ }
126
+ };
127
+ }
128
+ if (prop in target) {
129
+ return Reflect.get(target, prop, receiver);
130
+ }
131
+ // No generic `execute` forwarding: only the explicit pool API above
132
+ // (render/getStats/reset) plus worker-lib's own methods (close,
133
+ // launchWorker, setLimit, waitAll, waitReady) are available.
134
+ // Returning undefined (instead of throwing) keeps `await pool` working.
135
+ return undefined;
136
+ },
137
+ });
138
+ return proxy;
139
+ };
140
+ let defaultWorker;
141
+ /** Lazily create the shared single-slot pool on first use (not at import). */
142
+ function getDefaultWorker() {
143
+ if (!defaultWorker) {
144
+ defaultWorker = createHtmlToImageWorker({ maxParallel: 1 });
145
+ }
146
+ return defaultWorker;
147
+ }
148
+ export const close = () => {
149
+ getDefaultWorker().close();
150
+ };
151
+ export function render(options) {
152
+ return getDefaultWorker().render(options);
153
+ }
154
+ export const launchWorker = () => getDefaultWorker().launchWorker();
155
+ export const setLimit = (limit) => {
156
+ getDefaultWorker().setLimit(limit);
157
+ };
158
+ export const waitAll = () => getDefaultWorker().waitAll();
159
+ export const waitReady = (retryTime) => getDefaultWorker().waitReady(retryTime);
160
+ export const reset = () => {
161
+ getDefaultWorker().reset();
162
+ };
163
+ export const getStats = () => getDefaultWorker().getStats();
package/package.json ADDED
@@ -0,0 +1,166 @@
1
+ {
2
+ "name": "wasm-html-to-image",
3
+ "version": "0.1.0",
4
+ "description": "HTML to image converter on a single unified WASM module",
5
+ "type": "module",
6
+ "main": "./dist/single.js",
7
+ "types": "./dist/single.d.ts",
8
+ "bin": {
9
+ "wasm-html-to-image": "./dist/cli.js"
10
+ },
11
+ "author": "SoraKumo <info@croud.jp>",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/SoraKumo001/wasm-html-to-image.git",
15
+ "directory": "packages/html-to-image"
16
+ },
17
+ "homepage": "https://sorakumo001.github.io/wasm-html-to-image/",
18
+ "bugs": {
19
+ "url": "https://github.com/SoraKumo001/wasm-html-to-image/issues"
20
+ },
21
+ "keywords": [
22
+ "wasm",
23
+ "webassembly",
24
+ "html-to-image",
25
+ "html2image",
26
+ "skia",
27
+ "litehtml",
28
+ "image-optimization",
29
+ "screenshot",
30
+ "ogp",
31
+ "avif",
32
+ "webp",
33
+ "pdf",
34
+ "thumbhash",
35
+ "cloudflare-workers",
36
+ "edge-runtime"
37
+ ],
38
+ "engines": {
39
+ "node": ">=20.0.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -b && node scripts/compress-wasm.mjs && rolldown -c rolldown.config.js",
43
+ "prepublishOnly": "npm run build",
44
+ "test": "vitest run",
45
+ "example:cli": "tsx src/cli.ts --help"
46
+ },
47
+ "exports": {
48
+ ".": {
49
+ "workerd": {
50
+ "types": "./dist/workerd.d.ts",
51
+ "import": "./dist/workerd.js"
52
+ },
53
+ "deno": {
54
+ "types": "./dist/single.d.ts",
55
+ "import": "./dist/single.js"
56
+ },
57
+ "single": {
58
+ "types": "./dist/single.d.ts",
59
+ "import": "./dist/single.js"
60
+ },
61
+ "types": "./dist/single.d.ts",
62
+ "import": "./dist/single.js",
63
+ "default": "./dist/single.js"
64
+ },
65
+ "./index": {
66
+ "types": "./dist/index.d.ts",
67
+ "import": "./dist/index.js",
68
+ "default": "./dist/index.js"
69
+ },
70
+ "./single": {
71
+ "types": "./dist/single.d.ts",
72
+ "import": "./dist/single.js",
73
+ "default": "./dist/single.js"
74
+ },
75
+ "./workerd": {
76
+ "workerd": {
77
+ "types": "./dist/workerd.d.ts",
78
+ "import": "./dist/workerd.js",
79
+ "default": "./dist/workerd.js"
80
+ },
81
+ "edge-light": {
82
+ "types": "./dist/edge-light.d.ts",
83
+ "import": "./dist/edge-light.js",
84
+ "default": "./dist/edge-light.js"
85
+ },
86
+ "types": "./dist/workerd.d.ts",
87
+ "import": "./dist/workerd.js",
88
+ "default": "./dist/workerd.js"
89
+ },
90
+ "./edge-light": {
91
+ "types": "./dist/edge-light.d.ts",
92
+ "import": "./dist/edge-light.js",
93
+ "default": "./dist/edge-light.js"
94
+ },
95
+ "./workers": {
96
+ "types": "./dist/workers.d.ts",
97
+ "workerd": "./dist/workers-dummy.js",
98
+ "node": "./dist/workers.js",
99
+ "browser": "./dist/workers-parent.js",
100
+ "default": "./dist/workers-dummy.js"
101
+ },
102
+ "./html-to-image.js": {
103
+ "import": "./dist/html-to-image.js",
104
+ "default": "./dist/html-to-image.js"
105
+ },
106
+ "./react": {
107
+ "types": "./dist/react.d.ts",
108
+ "import": "./dist/react.js"
109
+ },
110
+ "./preact": {
111
+ "types": "./dist/preact.d.ts",
112
+ "import": "./dist/preact.js"
113
+ },
114
+ "./tailwind": {
115
+ "types": "./dist/tailwind.d.ts",
116
+ "import": "./dist/tailwind.js"
117
+ }
118
+ },
119
+ "files": [
120
+ "dist",
121
+ "README.md",
122
+ "LICENSE"
123
+ ],
124
+ "license": "MIT",
125
+ "dependencies": {
126
+ "commander": "15.0.0",
127
+ "worker-lib": "2.2.1"
128
+ },
129
+ "peerDependencies": {
130
+ "@unocss/core": "^66.6.5",
131
+ "@unocss/preset-wind4": "^66.6.5",
132
+ "preact": "^10.28.0",
133
+ "preact-render-to-string": "^6.6.0",
134
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
135
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
136
+ },
137
+ "peerDependenciesMeta": {
138
+ "@unocss/core": {
139
+ "optional": true
140
+ },
141
+ "@unocss/preset-wind4": {
142
+ "optional": true
143
+ },
144
+ "preact": {
145
+ "optional": true
146
+ },
147
+ "preact-render-to-string": {
148
+ "optional": true
149
+ },
150
+ "react": {
151
+ "optional": true
152
+ },
153
+ "react-dom": {
154
+ "optional": true
155
+ }
156
+ },
157
+ "devDependencies": {
158
+ "@types/node": "26.4.1",
159
+ "@types/react": "^19.2.18",
160
+ "@types/react-dom": "^19.2.7",
161
+ "rolldown": "^1.2.7",
162
+ "tsx": "^4.23.13",
163
+ "typescript": "^7.0.2",
164
+ "vitest": "^5.0.0"
165
+ }
166
+ }