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.
- package/LICENSE +21 -0
- package/README.md +326 -0
- package/dist/browser-workers.d.ts +12 -0
- package/dist/browser-workers.js +26 -0
- package/dist/child-workers.d.ts +11 -0
- package/dist/child-workers.js +19 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +87 -0
- package/dist/core.d.ts +168 -0
- package/dist/core.js +824 -0
- package/dist/diagnostics.d.ts +93 -0
- package/dist/diagnostics.js +28 -0
- package/dist/edge-light.d.ts +18 -0
- package/dist/edge-light.js +70 -0
- package/dist/encode-args.d.ts +10 -0
- package/dist/encode-args.js +30 -0
- package/dist/html-to-image-single.js +0 -0
- package/dist/html-to-image-wasm.js +3 -0
- package/dist/html-to-image.js +2 -0
- package/dist/html-to-image.wasm +0 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/input.d.ts +23 -0
- package/dist/input.js +90 -0
- package/dist/loader.d.ts +132 -0
- package/dist/loader.js +199 -0
- package/dist/preact.d.ts +10 -0
- package/dist/preact.js +13 -0
- package/dist/react.d.ts +9 -0
- package/dist/react.js +11 -0
- package/dist/single.d.ts +27 -0
- package/dist/single.js +22 -0
- package/dist/tailwind.d.ts +1 -0
- package/dist/tailwind.js +6 -0
- package/dist/wasm-payload.d.ts +2 -0
- package/dist/wasm-payload.js +39 -0
- package/dist/web-workers.js +4411 -0
- package/dist/worker-actions.d.ts +10 -0
- package/dist/worker-actions.js +13 -0
- package/dist/worker-lib-loader.d.ts +19 -0
- package/dist/worker-lib-loader.js +34 -0
- package/dist/workerd.d.ts +18 -0
- package/dist/workerd.js +65 -0
- package/dist/workers-dummy.d.ts +12 -0
- package/dist/workers-dummy.js +6 -0
- package/dist/workers-parent.js +602 -0
- package/dist/workers.d.ts +86 -0
- package/dist/workers.js +163 -0
- package/package.json +166 -0
package/dist/loader.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-WASM loader for the unified `html-to-image` Emscripten module.
|
|
3
|
+
*
|
|
4
|
+
* Build assumptions (see `src/cpp/main.cpp`, C++ lane owns them):
|
|
5
|
+
* - Emscripten `MODULARIZE` + `EXPORT_ES6` → `dist/html-to-image.js`
|
|
6
|
+
* with `export default createHtmlToImageModule`.
|
|
7
|
+
* - Single `EMSCRIPTEN_BINDINGS(html_to_image)` with `satoru_*` /
|
|
8
|
+
* `converter_*` prefixed JS names (no bare `create_instance` etc.).
|
|
9
|
+
*
|
|
10
|
+
* Registered bindings: instance lifecycle + log level, render/encode
|
|
11
|
+
* (`satoru_render`, `converter_encode`, ...), resource discovery/injection
|
|
12
|
+
* (`satoru_collect_resources`, `satoru_get_pending_resources`,
|
|
13
|
+
* `satoru_add_resource`, `satoru_load_font`, `satoru_load_fallback_font`,
|
|
14
|
+
* `satoru_set_font_map`, ...), and the unified `html_to_image` call.
|
|
15
|
+
* Bindings added over time stay optional in the type below; callers must
|
|
16
|
+
* feature-check at runtime (see `./core.ts`).
|
|
17
|
+
*
|
|
18
|
+
* Node and browser share this minimal shape: dynamic `import()` of the glue
|
|
19
|
+
* file URL, then invoking the factory. No `node:` builtins are used so the
|
|
20
|
+
* same file works in both runtimes.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Process-wide module cache, keyed by factory then moduleArg: different
|
|
24
|
+
* factories, or different WASM payloads (e.g. workerd/edge-light calls
|
|
25
|
+
* with distinct `WebAssembly.Module`s), must not share an instance.
|
|
26
|
+
* Callers with their own scoping (workerd/edge-light per-wasm maps,
|
|
27
|
+
* single.ts default path, `*-workers` lazy vars) are unaffected.
|
|
28
|
+
*/
|
|
29
|
+
const moduleCache = new Map();
|
|
30
|
+
/** Default glue URL: `./html-to-image.js` next to the compiled loader. */
|
|
31
|
+
export function defaultGlueUrl() {
|
|
32
|
+
return new URL("./html-to-image.js", import.meta.url);
|
|
33
|
+
}
|
|
34
|
+
function isModuleNotFound(e) {
|
|
35
|
+
const code = e?.code;
|
|
36
|
+
if (code === "ERR_MODULE_NOT_FOUND")
|
|
37
|
+
return true;
|
|
38
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
39
|
+
return /Cannot find (module|package)/.test(msg);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Import glue by URL, tolerating bundlers that hijack dynamic `import()`.
|
|
43
|
+
* Webpack rewrites even variable `import(url)` into chunk loading, which
|
|
44
|
+
* cannot resolve runtime URLs ("Cannot find module"). The native attempt
|
|
45
|
+
* runs first (unchanged behavior everywhere it already works, including
|
|
46
|
+
* strict-CSP pages); only on failure is an indirect import (invisible to
|
|
47
|
+
* bundlers' static analysis) retried. The original error is rethrown when
|
|
48
|
+
* both attempts fail, preserving existing error semantics (e.g. the
|
|
49
|
+
* src→dist dev fallback branching below).
|
|
50
|
+
*/
|
|
51
|
+
async function importGlueModule(glue) {
|
|
52
|
+
// `webpackIgnore` keeps Turbopack (Next.js 16 default builder) from trying
|
|
53
|
+
// to resolve the runtime URL at build time; webpack/vite honor it too.
|
|
54
|
+
try {
|
|
55
|
+
return (await import(
|
|
56
|
+
/* @vite-ignore, webpackIgnore: true */ glue));
|
|
57
|
+
}
|
|
58
|
+
catch (first) {
|
|
59
|
+
try {
|
|
60
|
+
const indirect = new Function("u", "return import(u)");
|
|
61
|
+
return await indirect(glue);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw first;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** `file://` directory URL to a plain fs path (browser URLs pass through). */
|
|
69
|
+
function glueDirToFsPath(glueDir) {
|
|
70
|
+
if (!glueDir.startsWith("file://"))
|
|
71
|
+
return glueDir;
|
|
72
|
+
let p = decodeURIComponent(glueDir.slice("file://".length));
|
|
73
|
+
if (/^\/[A-Za-z]:\//.test(p))
|
|
74
|
+
p = p.slice(1); // Windows drive letter
|
|
75
|
+
return p;
|
|
76
|
+
}
|
|
77
|
+
function isNodeRuntime() {
|
|
78
|
+
const proc = globalThis
|
|
79
|
+
.process;
|
|
80
|
+
return typeof proc?.versions?.node === "string";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Runtime-only load of a `node:` builtin (e.g. `importNode("fs/promises")`).
|
|
84
|
+
* Uses `process.getBuiltinModule` (Node 22.3+/20.16+) so there is NO `import`
|
|
85
|
+
* syntax at all: bundlers with static analysis (webpack/vite/rollup, and
|
|
86
|
+
* Turbopack — the Next.js 16 default builder, which rejects both dynamic
|
|
87
|
+
* `` `node:${name}` `` requests and literal `import("node:*")` inside Edge
|
|
88
|
+
* graphs) have nothing to resolve or rewrite. Only the builtins actually
|
|
89
|
+
* used by this package are supported. All call sites are guarded to run on
|
|
90
|
+
* Node only; other runtimes never evaluate these branches. A `new Function`
|
|
91
|
+
* indirect-import fallback keeps older Node working without adding any
|
|
92
|
+
* statically analyzable import.
|
|
93
|
+
*/
|
|
94
|
+
export function importNode(name) {
|
|
95
|
+
if (name !== "fs/promises" && name !== "path" && name !== "module") {
|
|
96
|
+
throw new Error(`wasm-html-to-image: unsupported node builtin "${name}"`);
|
|
97
|
+
}
|
|
98
|
+
const proc = globalThis.process;
|
|
99
|
+
try {
|
|
100
|
+
const mod = proc?.getBuiltinModule?.(`node:${name}`);
|
|
101
|
+
if (mod)
|
|
102
|
+
return Promise.resolve(mod);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// fall through to the indirect dynamic import below
|
|
106
|
+
}
|
|
107
|
+
const indirect = new Function("s", "return import(s)");
|
|
108
|
+
return indirect(`node:${name}`);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Node-only: pre-read the sibling `.wasm` bytes. The web/worker-oriented
|
|
112
|
+
* glue has no `fs` reader, so `file://` fetching fails under Node; handing
|
|
113
|
+
* over `wasmBinary` bypasses fetching entirely. Browsers skip this (fetch
|
|
114
|
+
* works there). No static `node:` import keeps browser bundles clean.
|
|
115
|
+
*/
|
|
116
|
+
async function readWasmBinaryNode(glueDir) {
|
|
117
|
+
if (!isNodeRuntime())
|
|
118
|
+
return undefined;
|
|
119
|
+
try {
|
|
120
|
+
const fs = await importNode("fs/promises");
|
|
121
|
+
const data = await fs.readFile(glueDirToFsPath(glueDir) + "html-to-image.wasm");
|
|
122
|
+
return new Uint8Array(data);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Load and instantiate the unified module (cached per process, keyed by
|
|
130
|
+
* factory then moduleArg — see `moduleCache`).
|
|
131
|
+
* Works in Node (file URL import) and browsers (served URL import).
|
|
132
|
+
*/
|
|
133
|
+
export async function loadHtmlToImageModule(options = {}) {
|
|
134
|
+
const cacheKeyFactory = options.factory ?? undefined;
|
|
135
|
+
const cacheKeyModuleArg = options.moduleArg ?? undefined;
|
|
136
|
+
if (!options.noCache) {
|
|
137
|
+
const hit = moduleCache.get(cacheKeyFactory)?.get(cacheKeyModuleArg);
|
|
138
|
+
if (hit)
|
|
139
|
+
return hit;
|
|
140
|
+
}
|
|
141
|
+
let factory = options.factory;
|
|
142
|
+
let glueLabel = "(pre-bundled factory)";
|
|
143
|
+
let glueDir = "";
|
|
144
|
+
if (!factory) {
|
|
145
|
+
let glue = String(options.glueUrl ?? defaultGlueUrl());
|
|
146
|
+
let ns;
|
|
147
|
+
try {
|
|
148
|
+
ns = await importGlueModule(glue);
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
if (options.glueUrl || !isModuleNotFound(e))
|
|
152
|
+
throw e;
|
|
153
|
+
// Dev-layout fallback: running from `src/` (tsx) while the glue
|
|
154
|
+
// lives in `../dist/` next to the compiled loader.
|
|
155
|
+
const fallback = String(new URL("../dist/html-to-image.js", import.meta.url));
|
|
156
|
+
try {
|
|
157
|
+
ns = await importGlueModule(fallback);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
throw e; // report the original error
|
|
161
|
+
}
|
|
162
|
+
glue = fallback;
|
|
163
|
+
}
|
|
164
|
+
glueLabel = glue;
|
|
165
|
+
glueDir = glue.slice(0, glue.lastIndexOf("/") + 1);
|
|
166
|
+
factory = ns.default ?? ns.createHtmlToImageModule;
|
|
167
|
+
}
|
|
168
|
+
if (typeof factory !== "function") {
|
|
169
|
+
throw new Error(`wasm-html-to-image: no MODULARIZE factory in ${glueLabel} ` +
|
|
170
|
+
`(expected default export createHtmlToImageModule)`);
|
|
171
|
+
}
|
|
172
|
+
// Node-only: hand over pre-read `.wasm` bytes (factory path, e.g.
|
|
173
|
+
// workerd with `instantiateWasm`, is left untouched).
|
|
174
|
+
let moduleArg = options.moduleArg;
|
|
175
|
+
if (!options.factory && glueDir && moduleArg?.wasmBinary === undefined) {
|
|
176
|
+
const wasmBinary = await readWasmBinaryNode(glueDir);
|
|
177
|
+
if (wasmBinary)
|
|
178
|
+
moduleArg = { ...moduleArg, wasmBinary };
|
|
179
|
+
}
|
|
180
|
+
const module = await factory({
|
|
181
|
+
...moduleArg,
|
|
182
|
+
locateFile: options.locateFile ??
|
|
183
|
+
moduleArg?.locateFile ??
|
|
184
|
+
((path) => glueDir + path),
|
|
185
|
+
});
|
|
186
|
+
if (!options.noCache) {
|
|
187
|
+
let inner = moduleCache.get(cacheKeyFactory);
|
|
188
|
+
if (!inner) {
|
|
189
|
+
inner = new Map();
|
|
190
|
+
moduleCache.set(cacheKeyFactory, inner);
|
|
191
|
+
}
|
|
192
|
+
inner.set(cacheKeyModuleArg, module);
|
|
193
|
+
}
|
|
194
|
+
return module;
|
|
195
|
+
}
|
|
196
|
+
/** Drop all cached instances (tests / HMR). */
|
|
197
|
+
export function dropCachedModule() {
|
|
198
|
+
moduleCache.clear();
|
|
199
|
+
}
|
package/dist/preact.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { h, type VNode } from "preact";
|
|
2
|
+
export { h };
|
|
3
|
+
/**
|
|
4
|
+
* Converts a React element to a static HTML string.
|
|
5
|
+
* This is a thin wrapper around react-dom/server's renderToStaticMarkup.
|
|
6
|
+
*
|
|
7
|
+
* @param element - The React element to convert.
|
|
8
|
+
* @returns The resulting HTML string.
|
|
9
|
+
*/
|
|
10
|
+
export declare function toHtml(element: VNode): string;
|
package/dist/preact.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import render from "preact-render-to-string";
|
|
3
|
+
export { h };
|
|
4
|
+
/**
|
|
5
|
+
* Converts a React element to a static HTML string.
|
|
6
|
+
* This is a thin wrapper around react-dom/server's renderToStaticMarkup.
|
|
7
|
+
*
|
|
8
|
+
* @param element - The React element to convert.
|
|
9
|
+
* @returns The resulting HTML string.
|
|
10
|
+
*/
|
|
11
|
+
export function toHtml(element) {
|
|
12
|
+
return render(element);
|
|
13
|
+
}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Converts a React element to a static HTML string.
|
|
4
|
+
* This is a thin wrapper around react-dom/server's renderToStaticMarkup.
|
|
5
|
+
*
|
|
6
|
+
* @param element - The React element to convert.
|
|
7
|
+
* @returns The resulting HTML string.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toHtml(element: ReactNode): string;
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
2
|
+
/**
|
|
3
|
+
* Converts a React element to a static HTML string.
|
|
4
|
+
* This is a thin wrapper around react-dom/server's renderToStaticMarkup.
|
|
5
|
+
*
|
|
6
|
+
* @param element - The React element to convert.
|
|
7
|
+
* @returns The resulting HTML string.
|
|
8
|
+
*/
|
|
9
|
+
export function toHtml(element) {
|
|
10
|
+
return renderToStaticMarkup(element);
|
|
11
|
+
}
|
package/dist/single.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-WASM entry point.
|
|
3
|
+
*
|
|
4
|
+
* Loads the unified `html-to-image` module once (local `./loader.js`)
|
|
5
|
+
* and reuses the single connection for all calls.
|
|
6
|
+
* `render()` accepts HTML (`string` / `string[]` / URL) or image input
|
|
7
|
+
* (`Uint8Array` / `ArrayBuffer` / `data:image/` URL); image input skips
|
|
8
|
+
* rendering and goes straight to `converter_encode`.
|
|
9
|
+
*/
|
|
10
|
+
import { type HtmlToImageModule } from "./loader.js";
|
|
11
|
+
import { type HtmlToImageOptions, type RenderResult } from "./core.js";
|
|
12
|
+
import { isImageInput } from "./input.js";
|
|
13
|
+
export type { OutputFormat, RenderOptions, HtmlToImageOptions, RenderResult, DiagnosticMessage, FontDiagnostic, RenderDiagnostics, RenderLimits, ResolveResourceHook, ResourceDiagnostic, } from "./core.js";
|
|
14
|
+
export { DIAGNOSTIC_CODES, LogLevel, htmlToImage } from "./core.js";
|
|
15
|
+
export { isImageInput };
|
|
16
|
+
export type { HtmlToImageModule } from "./loader.js";
|
|
17
|
+
export { dropCachedModule, loadHtmlToImageModule } from "./loader.js";
|
|
18
|
+
/** Default connection: thin delegation to the loader-owned cache. */
|
|
19
|
+
export declare function getDefaultModule(): Promise<HtmlToImageModule>;
|
|
20
|
+
/**
|
|
21
|
+
* Render HTML — or convert an image — with the default single-WASM
|
|
22
|
+
* connection. Returns RenderResult containing data and metadata.
|
|
23
|
+
*/
|
|
24
|
+
export declare function render(options: HtmlToImageOptions & {
|
|
25
|
+
format: "svg";
|
|
26
|
+
}): Promise<RenderResult<string>>;
|
|
27
|
+
export declare function render(options: HtmlToImageOptions): Promise<RenderResult<Uint8Array | string>>;
|
package/dist/single.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-WASM entry point.
|
|
3
|
+
*
|
|
4
|
+
* Loads the unified `html-to-image` module once (local `./loader.js`)
|
|
5
|
+
* and reuses the single connection for all calls.
|
|
6
|
+
* `render()` accepts HTML (`string` / `string[]` / URL) or image input
|
|
7
|
+
* (`Uint8Array` / `ArrayBuffer` / `data:image/` URL); image input skips
|
|
8
|
+
* rendering and goes straight to `converter_encode`.
|
|
9
|
+
*/
|
|
10
|
+
import { loadHtmlToImageModule } from "./loader.js";
|
|
11
|
+
import { htmlToImage, } from "./core.js";
|
|
12
|
+
import { isImageInput } from "./input.js";
|
|
13
|
+
export { DIAGNOSTIC_CODES, LogLevel, htmlToImage } from "./core.js";
|
|
14
|
+
export { isImageInput };
|
|
15
|
+
export { dropCachedModule, loadHtmlToImageModule } from "./loader.js";
|
|
16
|
+
/** Default connection: thin delegation to the loader-owned cache. */
|
|
17
|
+
export function getDefaultModule() {
|
|
18
|
+
return loadHtmlToImageModule();
|
|
19
|
+
}
|
|
20
|
+
export async function render(options) {
|
|
21
|
+
return htmlToImage(await getDefaultModule(), options);
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createCSS: (html: string) => Promise<string>;
|
package/dist/tailwind.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compressed embedded WASM payload.
|
|
3
|
+
*
|
|
4
|
+
* `scripts/compress-wasm.mjs` compresses `dist/html-to-image.wasm`
|
|
5
|
+
* (gzip vs brotli, smaller wins) and writes the base64 payload to
|
|
6
|
+
* `dist/html-to-image-wasm.js`. This loader restores the raw bytes at
|
|
7
|
+
* runtime via `DecompressionStream`, so the browser worker bundle embeds
|
|
8
|
+
* ~4MB instead of the 12MB SINGLE_FILE glue.
|
|
9
|
+
*/
|
|
10
|
+
// @ts-expect-error — dist/html-to-image-wasm.js is generated at build time
|
|
11
|
+
import { wasmBase64, wasmEncoding } from "../dist/html-to-image-wasm.js";
|
|
12
|
+
/** Decompress the embedded WASM payload and return the raw bytes. */
|
|
13
|
+
export async function loadCompressedWasmBinary() {
|
|
14
|
+
const encoding = wasmEncoding;
|
|
15
|
+
try {
|
|
16
|
+
// gzip only: the W3C Compression Standard (DecompressionStream) has no
|
|
17
|
+
// "br" — see scripts/compress-wasm.mjs.
|
|
18
|
+
if (encoding !== "gzip") {
|
|
19
|
+
throw new Error(`unsupported wasmEncoding ${JSON.stringify(encoding)}`);
|
|
20
|
+
}
|
|
21
|
+
const bin = atob(wasmBase64);
|
|
22
|
+
const compressed = new Uint8Array(bin.length);
|
|
23
|
+
for (let i = 0; i < bin.length; i++)
|
|
24
|
+
compressed[i] = bin.charCodeAt(i);
|
|
25
|
+
const stream = new Blob([compressed])
|
|
26
|
+
.stream()
|
|
27
|
+
.pipeThrough(new DecompressionStream(encoding));
|
|
28
|
+
const bytes = new Uint8Array(await new Response(stream).arrayBuffer());
|
|
29
|
+
if (bytes.length === 0) {
|
|
30
|
+
throw new Error("decompressed to 0 bytes");
|
|
31
|
+
}
|
|
32
|
+
return bytes;
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
throw new Error(`wasm-html-to-image: failed to restore embedded WASM ` +
|
|
36
|
+
`(encoding=${JSON.stringify(encoding)}): ` +
|
|
37
|
+
(e instanceof Error ? e.message : String(e)));
|
|
38
|
+
}
|
|
39
|
+
}
|