@ahmedrowaihi/pdf-forge-core 1.0.0 → 1.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.
@@ -7,6 +7,25 @@ type Options = {
7
7
  * @see {@link pretty}
8
8
  */
9
9
  pretty?: boolean;
10
+ /**
11
+ * Bundle static assets (fonts, images) as base64 data URIs in the HTML.
12
+ * When enabled, scans the specified static directory and embeds assets.
13
+ * Only available in Node.js environment.
14
+ */
15
+ bundleAssets?: boolean | {
16
+ /**
17
+ * Path to the static directory containing assets (fonts, images, etc.)
18
+ * Can be absolute or relative to process.cwd()
19
+ */
20
+ staticDir: string;
21
+ /**
22
+ * Optional fallback static directory for shared assets.
23
+ * If an asset is not found in staticDir, it will be looked up in fallbackStaticDir.
24
+ * Useful when multiple templates share common assets (fonts, logos, etc.)
25
+ * Can be absolute or relative to process.cwd()
26
+ */
27
+ fallbackStaticDir?: string;
28
+ };
10
29
  } & ({
11
30
  /**
12
31
  * @see {@link toPlainText}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/browser/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ECxFa;AASb;;;;ACVA;;;;;sBHsB0B;;;;cCmEb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCVvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/browser/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ICxFa;AASb;;;;ICVa;;;;;;;;;;;;;;;;;;;;;;;;sBH2Ca;;;;cC8Cb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCVvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
@@ -7,6 +7,25 @@ type Options = {
7
7
  * @see {@link pretty}
8
8
  */
9
9
  pretty?: boolean;
10
+ /**
11
+ * Bundle static assets (fonts, images) as base64 data URIs in the HTML.
12
+ * When enabled, scans the specified static directory and embeds assets.
13
+ * Only available in Node.js environment.
14
+ */
15
+ bundleAssets?: boolean | {
16
+ /**
17
+ * Path to the static directory containing assets (fonts, images, etc.)
18
+ * Can be absolute or relative to process.cwd()
19
+ */
20
+ staticDir: string;
21
+ /**
22
+ * Optional fallback static directory for shared assets.
23
+ * If an asset is not found in staticDir, it will be looked up in fallbackStaticDir.
24
+ * Useful when multiple templates share common assets (fonts, logos, etc.)
25
+ * Can be absolute or relative to process.cwd()
26
+ */
27
+ fallbackStaticDir?: string;
28
+ };
10
29
  } & ({
11
30
  /**
12
31
  * @see {@link toPlainText}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/browser/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ECxFa;AASb;;;;ACVA;;;;;sBHsB0B;;;;cCmEb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCVvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/browser/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ICxFa;AASb;;;;ICVa;;;;;;;;;;;;;;;;;;;;;;;;sBH2Ca;;;;cC8Cb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCVvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
@@ -7,6 +7,25 @@ type Options = {
7
7
  * @see {@link pretty}
8
8
  */
9
9
  pretty?: boolean;
10
+ /**
11
+ * Bundle static assets (fonts, images) as base64 data URIs in the HTML.
12
+ * When enabled, scans the specified static directory and embeds assets.
13
+ * Only available in Node.js environment.
14
+ */
15
+ bundleAssets?: boolean | {
16
+ /**
17
+ * Path to the static directory containing assets (fonts, images, etc.)
18
+ * Can be absolute or relative to process.cwd()
19
+ */
20
+ staticDir: string;
21
+ /**
22
+ * Optional fallback static directory for shared assets.
23
+ * If an asset is not found in staticDir, it will be looked up in fallbackStaticDir.
24
+ * Useful when multiple templates share common assets (fonts, logos, etc.)
25
+ * Can be absolute or relative to process.cwd()
26
+ */
27
+ fallbackStaticDir?: string;
28
+ };
10
29
  } & ({
11
30
  /**
12
31
  * @see {@link toPlainText}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/edge/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ECxFa;AASb;;;;ACRA;;;;;sBHoB0B;;;;cCmEb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCRvC,kBACF,KAAA,CAAM,wBACL,YAAO"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/edge/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ICxFa;AASb;;;;ICRa;;;;;;;;;;;;;;;;;;;;;;;;sBHyCa;;;;cC8Cb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCRvC,kBACF,KAAA,CAAM,wBACL,YAAO"}
@@ -7,6 +7,25 @@ type Options = {
7
7
  * @see {@link pretty}
8
8
  */
9
9
  pretty?: boolean;
10
+ /**
11
+ * Bundle static assets (fonts, images) as base64 data URIs in the HTML.
12
+ * When enabled, scans the specified static directory and embeds assets.
13
+ * Only available in Node.js environment.
14
+ */
15
+ bundleAssets?: boolean | {
16
+ /**
17
+ * Path to the static directory containing assets (fonts, images, etc.)
18
+ * Can be absolute or relative to process.cwd()
19
+ */
20
+ staticDir: string;
21
+ /**
22
+ * Optional fallback static directory for shared assets.
23
+ * If an asset is not found in staticDir, it will be looked up in fallbackStaticDir.
24
+ * Useful when multiple templates share common assets (fonts, logos, etc.)
25
+ * Can be absolute or relative to process.cwd()
26
+ */
27
+ fallbackStaticDir?: string;
28
+ };
10
29
  } & ({
11
30
  /**
12
31
  * @see {@link toPlainText}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/edge/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ECxFa;AASb;;;;ACRA;;;;;sBHoB0B;;;;cCmEb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCRvC,kBACF,KAAA,CAAM,wBACL,YAAO"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/edge/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ICxFa;AASb;;;;ICRa;;;;;;;;;;;;;;;;;;;;;;;;sBHyCa;;;;cC8Cb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCRvC,kBACF,KAAA,CAAM,wBACL,YAAO"}
@@ -7,6 +7,25 @@ type Options = {
7
7
  * @see {@link pretty}
8
8
  */
9
9
  pretty?: boolean;
10
+ /**
11
+ * Bundle static assets (fonts, images) as base64 data URIs in the HTML.
12
+ * When enabled, scans the specified static directory and embeds assets.
13
+ * Only available in Node.js environment.
14
+ */
15
+ bundleAssets?: boolean | {
16
+ /**
17
+ * Path to the static directory containing assets (fonts, images, etc.)
18
+ * Can be absolute or relative to process.cwd()
19
+ */
20
+ staticDir: string;
21
+ /**
22
+ * Optional fallback static directory for shared assets.
23
+ * If an asset is not found in staticDir, it will be looked up in fallbackStaticDir.
24
+ * Useful when multiple templates share common assets (fonts, logos, etc.)
25
+ * Can be absolute or relative to process.cwd()
26
+ */
27
+ fallbackStaticDir?: string;
28
+ };
10
29
  } & ({
11
30
  /**
12
31
  * @see {@link toPlainText}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/node/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ECxFa;AASb;;;;ACTA;;;;;sBHqB0B;;;;cCmEb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCTvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/node/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ICxFa;AASb;;;;ICJa;;;;;;;;;;;;;;;;;;;;;;;;sBHqCa;;;;cC8Cb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCJvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
@@ -7,6 +7,25 @@ type Options = {
7
7
  * @see {@link pretty}
8
8
  */
9
9
  pretty?: boolean;
10
+ /**
11
+ * Bundle static assets (fonts, images) as base64 data URIs in the HTML.
12
+ * When enabled, scans the specified static directory and embeds assets.
13
+ * Only available in Node.js environment.
14
+ */
15
+ bundleAssets?: boolean | {
16
+ /**
17
+ * Path to the static directory containing assets (fonts, images, etc.)
18
+ * Can be absolute or relative to process.cwd()
19
+ */
20
+ staticDir: string;
21
+ /**
22
+ * Optional fallback static directory for shared assets.
23
+ * If an asset is not found in staticDir, it will be looked up in fallbackStaticDir.
24
+ * Useful when multiple templates share common assets (fonts, logos, etc.)
25
+ * Can be absolute or relative to process.cwd()
26
+ */
27
+ fallbackStaticDir?: string;
28
+ };
10
29
  } & ({
11
30
  /**
12
31
  * @see {@link toPlainText}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/node/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ECxFa;AASb;;;;ACTA;;;;;sBHqB0B;;;;cCmEb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCTvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/shared/options.ts","../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/node/render.tsx"],"sourcesContent":[],"mappings":";;;;KAIY,OAAA;;;AAAZ;;;;AC0FA;;;;ICxFa;AASb;;;;ICJa;;;;;;;;;;;;;;;;;;;;;;;;sBHqCa;;;;cC8Cb,gCAAgC,cAAY;;;cCxF5C,oBAAoB;iBASjB,WAAA,yBAAoC;;;cCJvC,eAAsB,KAAA,CAAM,qBAAqB,YAAO"}
@@ -27,8 +27,12 @@ let prettier_standalone = require("prettier/standalone");
27
27
  prettier_standalone = __toESM(prettier_standalone);
28
28
  let html_to_text = require("html-to-text");
29
29
  html_to_text = __toESM(html_to_text);
30
+ let node_path = require("node:path");
31
+ node_path = __toESM(node_path);
30
32
  let react = require("react");
31
33
  react = __toESM(react);
34
+ let node_fs_promises = require("node:fs/promises");
35
+ node_fs_promises = __toESM(node_fs_promises);
32
36
  let node_stream = require("node:stream");
33
37
  node_stream = __toESM(node_stream);
34
38
  let react_jsx_runtime = require("react/jsx-runtime");
@@ -62,9 +66,9 @@ function recursivelyMapDoc(doc, callback) {
62
66
  const modifiedHtml = { ...prettier_plugins_html };
63
67
  if (modifiedHtml.printers) {
64
68
  const previousPrint = modifiedHtml.printers.html.print;
65
- modifiedHtml.printers.html.print = (path, options, print, args) => {
66
- const node = path.getNode();
67
- const rawPrintingResult = previousPrint(path, options, print, args);
69
+ modifiedHtml.printers.html.print = (path$2, options, print, args) => {
70
+ const node = path$2.getNode();
71
+ const rawPrintingResult = previousPrint(path$2, options, print, args);
68
72
  if (node.type === "ieConditionalComment") return recursivelyMapDoc(rawPrintingResult, (doc) => {
69
73
  if (typeof doc === "object" && doc.type === "line") return doc.soft ? "" : " ";
70
74
  return doc;
@@ -113,6 +117,149 @@ function toPlainText(html, options) {
113
117
  });
114
118
  }
115
119
 
120
+ //#endregion
121
+ //#region src/shared/utils/asset-registry.ts
122
+ const templateAssetCache = /* @__PURE__ */ new Map();
123
+ const sharedAssetCache = /* @__PURE__ */ new Map();
124
+ /**
125
+ * Recursively scan a directory and return all files with their relative paths
126
+ */
127
+ async function scanDirectoryRecursive(dir, baseDir = dir) {
128
+ const files = /* @__PURE__ */ new Map();
129
+ try {
130
+ const entries = await node_fs_promises.default.readdir(dir, { withFileTypes: true });
131
+ for (const entry of entries) {
132
+ const fullPath = node_path.default.join(dir, entry.name);
133
+ const relativePath = node_path.default.relative(baseDir, fullPath);
134
+ if (entry.isDirectory()) {
135
+ const subFiles = await scanDirectoryRecursive(fullPath, baseDir);
136
+ for (const [relPath, absPath] of subFiles) files.set(relPath, absPath);
137
+ } else files.set(relativePath, fullPath);
138
+ }
139
+ } catch {
140
+ return files;
141
+ }
142
+ return files;
143
+ }
144
+ /**
145
+ * Load a file and convert it to base64 data URI
146
+ */
147
+ async function loadAssetAsBase64(filePath, url) {
148
+ const stats = await node_fs_promises.default.stat(filePath);
149
+ const base64 = (await node_fs_promises.default.readFile(filePath)).toString("base64");
150
+ const ext = node_path.default.extname(url).toLowerCase();
151
+ return {
152
+ base64: `data:${{
153
+ ".woff2": "font/woff2",
154
+ ".woff": "font/woff",
155
+ ".otf": "font/otf",
156
+ ".ttf": "font/ttf",
157
+ ".eot": "application/vnd.ms-fontobject",
158
+ ".svg": "image/svg+xml",
159
+ ".png": "image/png",
160
+ ".jpg": "image/jpeg",
161
+ ".jpeg": "image/jpeg",
162
+ ".gif": "image/gif",
163
+ ".webp": "image/webp"
164
+ }[ext] || "application/octet-stream"};base64,${base64}`,
165
+ mtime: stats.mtimeMs
166
+ };
167
+ }
168
+ /**
169
+ * Get or build asset registry for a shared/fallback static directory
170
+ * This is cached separately and reused across all templates
171
+ */
172
+ async function getSharedAssetRegistry(fallbackStaticDir) {
173
+ const cached = sharedAssetCache.get(fallbackStaticDir);
174
+ if (cached) return cached;
175
+ const assetMap = /* @__PURE__ */ new Map();
176
+ try {
177
+ await node_fs_promises.default.access(fallbackStaticDir);
178
+ const files = await scanDirectoryRecursive(fallbackStaticDir, fallbackStaticDir);
179
+ for (const [relativePath, absolutePath] of files) {
180
+ const url = `/${relativePath.replace(/\\/g, "/")}`;
181
+ const { base64, mtime } = await loadAssetAsBase64(absolutePath, url);
182
+ assetMap.set(url, {
183
+ base64,
184
+ mtime
185
+ });
186
+ if (url.startsWith("/")) assetMap.set(url.slice(1), {
187
+ base64,
188
+ mtime
189
+ });
190
+ }
191
+ } catch {}
192
+ sharedAssetCache.set(fallbackStaticDir, assetMap);
193
+ return assetMap;
194
+ }
195
+ /**
196
+ * Get or build asset registry for a static directory
197
+ *
198
+ * @param staticDir - Absolute path to the static directory
199
+ * @param fallbackStaticDir - Optional fallback static directory (lower priority)
200
+ * @returns Map of asset URLs to base64 data URIs
201
+ */
202
+ async function getAssetRegistry(staticDir, fallbackStaticDir) {
203
+ const templateCached = templateAssetCache.get(staticDir);
204
+ let templateAssetMap;
205
+ if (templateCached) templateAssetMap = templateCached;
206
+ else {
207
+ templateAssetMap = /* @__PURE__ */ new Map();
208
+ try {
209
+ await node_fs_promises.default.access(staticDir);
210
+ const files = await scanDirectoryRecursive(staticDir, staticDir);
211
+ for (const [relativePath, absolutePath] of files) {
212
+ const url = `/${relativePath.replace(/\\/g, "/")}`;
213
+ const { base64, mtime } = await loadAssetAsBase64(absolutePath, url);
214
+ templateAssetMap.set(url, {
215
+ base64,
216
+ mtime
217
+ });
218
+ if (url.startsWith("/")) templateAssetMap.set(url.slice(1), {
219
+ base64,
220
+ mtime
221
+ });
222
+ }
223
+ } catch {}
224
+ templateAssetCache.set(staticDir, templateAssetMap);
225
+ }
226
+ const result = /* @__PURE__ */ new Map();
227
+ for (const [url, entry] of templateAssetMap) result.set(url, entry.base64);
228
+ if (fallbackStaticDir) {
229
+ const sharedAssetMap = await getSharedAssetRegistry(fallbackStaticDir);
230
+ for (const [url, entry] of sharedAssetMap) if (!result.has(url)) result.set(url, entry.base64);
231
+ }
232
+ return result;
233
+ }
234
+ /**
235
+ * Process HTML to replace relative asset URLs with base64 data URIs
236
+ */
237
+ function embedAssetsInHtml(html, assetRegistry) {
238
+ const getBase64 = (url) => {
239
+ return assetRegistry.get(url) || assetRegistry.get(`/${url}`) || assetRegistry.get(url.slice(1));
240
+ };
241
+ const shouldSkip = (url) => {
242
+ return url.startsWith("data:") || url.startsWith("http://") || url.startsWith("https://") || url.startsWith("#") || url.startsWith("mailto:") || url.startsWith("tel:");
243
+ };
244
+ let processedHtml = html;
245
+ processedHtml = processedHtml.replace(/url\((['"]?)([^'")]+)\1\)/gi, (match, quote, url) => {
246
+ if (shouldSkip(url)) return match;
247
+ const base64 = getBase64(url);
248
+ return base64 ? `url(${quote}${base64}${quote})` : match;
249
+ });
250
+ processedHtml = processedHtml.replace(/src\s*=\s*(['"])([^'"]+)\1/gi, (match, quote, url) => {
251
+ if (shouldSkip(url)) return match;
252
+ const base64 = getBase64(url);
253
+ return base64 ? `src=${quote}${base64}${quote}` : match;
254
+ });
255
+ processedHtml = processedHtml.replace(/href\s*=\s*(['"])([^'"]+)\1/gi, (match, quote, url) => {
256
+ if (shouldSkip(url)) return match;
257
+ const base64 = getBase64(url);
258
+ return base64 ? `href=${quote}${base64}${quote}` : match;
259
+ });
260
+ return processedHtml;
261
+ }
262
+
116
263
  //#endregion
117
264
  //#region src/node/read-stream.ts
118
265
  const readStream = async (stream) => {
@@ -175,6 +322,16 @@ const render = async (node, options) => {
175
322
  });
176
323
  });
177
324
  if (options?.plainText) return toPlainText(html, options.htmlToTextOptions);
325
+ if (options?.bundleAssets) {
326
+ let staticDir;
327
+ let fallbackStaticDir;
328
+ if (typeof options.bundleAssets === "object") {
329
+ staticDir = options.bundleAssets.staticDir;
330
+ fallbackStaticDir = options.bundleAssets.fallbackStaticDir;
331
+ } else staticDir = node_path.default.join(process.cwd(), "static");
332
+ const assetRegistry = await getAssetRegistry(node_path.default.isAbsolute(staticDir) ? staticDir : node_path.default.join(process.cwd(), staticDir), fallbackStaticDir ? node_path.default.isAbsolute(fallbackStaticDir) ? fallbackStaticDir : node_path.default.join(process.cwd(), fallbackStaticDir) : void 0);
333
+ html = embedAssetsInHtml(html, assetRegistry);
334
+ }
178
335
  const document = `<!DOCTYPE html>${html.replace(/<!DOCTYPE.*?>/, "")}`;
179
336
  if (options?.pretty) return pretty(document);
180
337
  return document;
@@ -1,7 +1,9 @@
1
1
  import * as html from "prettier/plugins/html";
2
2
  import { format } from "prettier/standalone";
3
3
  import { convert } from "html-to-text";
4
+ import path from "node:path";
4
5
  import { Suspense } from "react";
6
+ import fs from "node:fs/promises";
5
7
  import { Writable } from "node:stream";
6
8
  import { jsx } from "react/jsx-runtime";
7
9
 
@@ -33,9 +35,9 @@ function recursivelyMapDoc(doc, callback) {
33
35
  const modifiedHtml = { ...html };
34
36
  if (modifiedHtml.printers) {
35
37
  const previousPrint = modifiedHtml.printers.html.print;
36
- modifiedHtml.printers.html.print = (path, options, print, args) => {
37
- const node = path.getNode();
38
- const rawPrintingResult = previousPrint(path, options, print, args);
38
+ modifiedHtml.printers.html.print = (path$1, options, print, args) => {
39
+ const node = path$1.getNode();
40
+ const rawPrintingResult = previousPrint(path$1, options, print, args);
39
41
  if (node.type === "ieConditionalComment") return recursivelyMapDoc(rawPrintingResult, (doc) => {
40
42
  if (typeof doc === "object" && doc.type === "line") return doc.soft ? "" : " ";
41
43
  return doc;
@@ -84,6 +86,149 @@ function toPlainText(html$1, options) {
84
86
  });
85
87
  }
86
88
 
89
+ //#endregion
90
+ //#region src/shared/utils/asset-registry.ts
91
+ const templateAssetCache = /* @__PURE__ */ new Map();
92
+ const sharedAssetCache = /* @__PURE__ */ new Map();
93
+ /**
94
+ * Recursively scan a directory and return all files with their relative paths
95
+ */
96
+ async function scanDirectoryRecursive(dir, baseDir = dir) {
97
+ const files = /* @__PURE__ */ new Map();
98
+ try {
99
+ const entries = await fs.readdir(dir, { withFileTypes: true });
100
+ for (const entry of entries) {
101
+ const fullPath = path.join(dir, entry.name);
102
+ const relativePath = path.relative(baseDir, fullPath);
103
+ if (entry.isDirectory()) {
104
+ const subFiles = await scanDirectoryRecursive(fullPath, baseDir);
105
+ for (const [relPath, absPath] of subFiles) files.set(relPath, absPath);
106
+ } else files.set(relativePath, fullPath);
107
+ }
108
+ } catch {
109
+ return files;
110
+ }
111
+ return files;
112
+ }
113
+ /**
114
+ * Load a file and convert it to base64 data URI
115
+ */
116
+ async function loadAssetAsBase64(filePath, url) {
117
+ const stats = await fs.stat(filePath);
118
+ const base64 = (await fs.readFile(filePath)).toString("base64");
119
+ const ext = path.extname(url).toLowerCase();
120
+ return {
121
+ base64: `data:${{
122
+ ".woff2": "font/woff2",
123
+ ".woff": "font/woff",
124
+ ".otf": "font/otf",
125
+ ".ttf": "font/ttf",
126
+ ".eot": "application/vnd.ms-fontobject",
127
+ ".svg": "image/svg+xml",
128
+ ".png": "image/png",
129
+ ".jpg": "image/jpeg",
130
+ ".jpeg": "image/jpeg",
131
+ ".gif": "image/gif",
132
+ ".webp": "image/webp"
133
+ }[ext] || "application/octet-stream"};base64,${base64}`,
134
+ mtime: stats.mtimeMs
135
+ };
136
+ }
137
+ /**
138
+ * Get or build asset registry for a shared/fallback static directory
139
+ * This is cached separately and reused across all templates
140
+ */
141
+ async function getSharedAssetRegistry(fallbackStaticDir) {
142
+ const cached = sharedAssetCache.get(fallbackStaticDir);
143
+ if (cached) return cached;
144
+ const assetMap = /* @__PURE__ */ new Map();
145
+ try {
146
+ await fs.access(fallbackStaticDir);
147
+ const files = await scanDirectoryRecursive(fallbackStaticDir, fallbackStaticDir);
148
+ for (const [relativePath, absolutePath] of files) {
149
+ const url = `/${relativePath.replace(/\\/g, "/")}`;
150
+ const { base64, mtime } = await loadAssetAsBase64(absolutePath, url);
151
+ assetMap.set(url, {
152
+ base64,
153
+ mtime
154
+ });
155
+ if (url.startsWith("/")) assetMap.set(url.slice(1), {
156
+ base64,
157
+ mtime
158
+ });
159
+ }
160
+ } catch {}
161
+ sharedAssetCache.set(fallbackStaticDir, assetMap);
162
+ return assetMap;
163
+ }
164
+ /**
165
+ * Get or build asset registry for a static directory
166
+ *
167
+ * @param staticDir - Absolute path to the static directory
168
+ * @param fallbackStaticDir - Optional fallback static directory (lower priority)
169
+ * @returns Map of asset URLs to base64 data URIs
170
+ */
171
+ async function getAssetRegistry(staticDir, fallbackStaticDir) {
172
+ const templateCached = templateAssetCache.get(staticDir);
173
+ let templateAssetMap;
174
+ if (templateCached) templateAssetMap = templateCached;
175
+ else {
176
+ templateAssetMap = /* @__PURE__ */ new Map();
177
+ try {
178
+ await fs.access(staticDir);
179
+ const files = await scanDirectoryRecursive(staticDir, staticDir);
180
+ for (const [relativePath, absolutePath] of files) {
181
+ const url = `/${relativePath.replace(/\\/g, "/")}`;
182
+ const { base64, mtime } = await loadAssetAsBase64(absolutePath, url);
183
+ templateAssetMap.set(url, {
184
+ base64,
185
+ mtime
186
+ });
187
+ if (url.startsWith("/")) templateAssetMap.set(url.slice(1), {
188
+ base64,
189
+ mtime
190
+ });
191
+ }
192
+ } catch {}
193
+ templateAssetCache.set(staticDir, templateAssetMap);
194
+ }
195
+ const result = /* @__PURE__ */ new Map();
196
+ for (const [url, entry] of templateAssetMap) result.set(url, entry.base64);
197
+ if (fallbackStaticDir) {
198
+ const sharedAssetMap = await getSharedAssetRegistry(fallbackStaticDir);
199
+ for (const [url, entry] of sharedAssetMap) if (!result.has(url)) result.set(url, entry.base64);
200
+ }
201
+ return result;
202
+ }
203
+ /**
204
+ * Process HTML to replace relative asset URLs with base64 data URIs
205
+ */
206
+ function embedAssetsInHtml(html$1, assetRegistry) {
207
+ const getBase64 = (url) => {
208
+ return assetRegistry.get(url) || assetRegistry.get(`/${url}`) || assetRegistry.get(url.slice(1));
209
+ };
210
+ const shouldSkip = (url) => {
211
+ return url.startsWith("data:") || url.startsWith("http://") || url.startsWith("https://") || url.startsWith("#") || url.startsWith("mailto:") || url.startsWith("tel:");
212
+ };
213
+ let processedHtml = html$1;
214
+ processedHtml = processedHtml.replace(/url\((['"]?)([^'")]+)\1\)/gi, (match, quote, url) => {
215
+ if (shouldSkip(url)) return match;
216
+ const base64 = getBase64(url);
217
+ return base64 ? `url(${quote}${base64}${quote})` : match;
218
+ });
219
+ processedHtml = processedHtml.replace(/src\s*=\s*(['"])([^'"]+)\1/gi, (match, quote, url) => {
220
+ if (shouldSkip(url)) return match;
221
+ const base64 = getBase64(url);
222
+ return base64 ? `src=${quote}${base64}${quote}` : match;
223
+ });
224
+ processedHtml = processedHtml.replace(/href\s*=\s*(['"])([^'"]+)\1/gi, (match, quote, url) => {
225
+ if (shouldSkip(url)) return match;
226
+ const base64 = getBase64(url);
227
+ return base64 ? `href=${quote}${base64}${quote}` : match;
228
+ });
229
+ return processedHtml;
230
+ }
231
+
87
232
  //#endregion
88
233
  //#region src/node/read-stream.ts
89
234
  const readStream = async (stream) => {
@@ -146,6 +291,16 @@ const render = async (node, options) => {
146
291
  });
147
292
  });
148
293
  if (options?.plainText) return toPlainText(html$1, options.htmlToTextOptions);
294
+ if (options?.bundleAssets) {
295
+ let staticDir;
296
+ let fallbackStaticDir;
297
+ if (typeof options.bundleAssets === "object") {
298
+ staticDir = options.bundleAssets.staticDir;
299
+ fallbackStaticDir = options.bundleAssets.fallbackStaticDir;
300
+ } else staticDir = path.join(process.cwd(), "static");
301
+ const assetRegistry = await getAssetRegistry(path.isAbsolute(staticDir) ? staticDir : path.join(process.cwd(), staticDir), fallbackStaticDir ? path.isAbsolute(fallbackStaticDir) ? fallbackStaticDir : path.join(process.cwd(), fallbackStaticDir) : void 0);
302
+ html$1 = embedAssetsInHtml(html$1, assetRegistry);
303
+ }
149
304
  const document = `<!DOCTYPE html>${html$1.replace(/<!DOCTYPE.*?>/, "")}`;
150
305
  if (options?.pretty) return pretty(document);
151
306
  return document;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["defaults: Options","plainTextSelectors: SelectorDefinition[]","html","html!: string","html"],"sources":["../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/node/read-stream.ts","../../src/node/render.tsx"],"sourcesContent":["import type { Options, Plugin } from 'prettier';\nimport type { builders } from 'prettier/doc';\nimport * as html from 'prettier/plugins/html';\nimport { format } from 'prettier/standalone';\n\ninterface HtmlNode {\n type: 'element' | 'text' | 'ieConditionalComment';\n name?: string;\n sourceSpan: {\n start: { file: unknown[]; offset: number; line: number; col: number };\n end: { file: unknown[]; offset: number; line: number; col: number };\n details: null;\n };\n parent?: HtmlNode;\n}\n\nfunction recursivelyMapDoc(\n doc: builders.Doc,\n callback: (innerDoc: string | builders.DocCommand) => builders.Doc,\n): builders.Doc {\n if (Array.isArray(doc)) {\n return doc.map((innerDoc) => recursivelyMapDoc(innerDoc, callback));\n }\n\n if (typeof doc === 'object') {\n if (doc.type === 'group') {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n expandedStates: recursivelyMapDoc(\n doc.expandedStates,\n callback,\n ) as builders.Doc[],\n };\n }\n\n if ('contents' in doc) {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n };\n }\n\n if ('parts' in doc) {\n return {\n ...doc,\n parts: recursivelyMapDoc(doc.parts, callback) as builders.Doc[],\n };\n }\n\n if (doc.type === 'if-break') {\n return {\n ...doc,\n breakContents: recursivelyMapDoc(doc.breakContents, callback),\n flatContents: recursivelyMapDoc(doc.flatContents, callback),\n };\n }\n }\n\n return callback(doc);\n}\n\nconst modifiedHtml = { ...html } as Plugin;\nif (modifiedHtml.printers) {\n const previousPrint = modifiedHtml.printers.html.print;\n modifiedHtml.printers.html.print = (path, options, print, args) => {\n const node = path.getNode() as HtmlNode;\n\n const rawPrintingResult = previousPrint(path, options, print, args);\n\n if (node.type === 'ieConditionalComment') {\n const printingResult = recursivelyMapDoc(rawPrintingResult, (doc) => {\n if (typeof doc === 'object' && doc.type === 'line') {\n return doc.soft ? '' : ' ';\n }\n\n return doc;\n });\n\n return printingResult;\n }\n\n return rawPrintingResult;\n };\n}\n\nconst defaults: Options = {\n endOfLine: 'lf',\n tabWidth: 2,\n plugins: [modifiedHtml],\n bracketSameLine: true,\n parser: 'html',\n};\n\nexport const pretty = (str: string, options: Options = {}) => {\n return format(str.replaceAll('\\0', ''), {\n ...defaults,\n ...options,\n });\n};\n","import {\n convert,\n type HtmlToTextOptions,\n type SelectorDefinition,\n} from 'html-to-text';\n\nexport const plainTextSelectors: SelectorDefinition[] = [\n { selector: 'img', format: 'skip' },\n { selector: '[data-skip-in-text=true]', format: 'skip' },\n {\n selector: 'a',\n options: { linkBrackets: false, hideLinkHrefIfSameAsText: true },\n },\n];\n\nexport function toPlainText(html: string, options?: HtmlToTextOptions) {\n return convert(html, {\n selectors: plainTextSelectors,\n wordwrap: false,\n ...options,\n });\n}\n","import { Writable } from 'node:stream';\nimport type {\n PipeableStream,\n ReactDOMServerReadableStream,\n} from 'react-dom/server.browser';\n\nexport const readStream = async (\n stream: PipeableStream | ReactDOMServerReadableStream,\n) => {\n let result = '';\n // Create a single TextDecoder instance to handle streaming properly\n // This fixes issues with multi-byte characters (e.g., CJK) being split across chunks\n const decoder = new TextDecoder('utf-8');\n\n if ('pipeTo' in stream) {\n // means it's a readable stream\n const writableStream = new WritableStream({\n write(chunk: BufferSource) {\n // Use stream: true to handle multi-byte characters split across chunks\n result += decoder.decode(chunk, { stream: true });\n },\n close() {\n // Flush any remaining bytes\n result += decoder.decode();\n },\n });\n await stream.pipeTo(writableStream);\n } else {\n const writable = new Writable({\n write(chunk: BufferSource, _encoding, callback) {\n // Use stream: true to handle multi-byte characters split across chunks\n result += decoder.decode(chunk, { stream: true });\n\n callback();\n },\n final(callback) {\n // Flush any remaining bytes\n result += decoder.decode();\n callback();\n },\n });\n stream.pipe(writable);\n\n await new Promise<void>((resolve, reject) => {\n writable.on('error', reject);\n writable.on('close', () => {\n resolve();\n });\n });\n }\n\n return result;\n};\n","import { Suspense } from 'react';\nimport type { Options } from '../shared/options';\nimport { pretty } from '../shared/utils/pretty';\nimport { toPlainText } from '../shared/utils/to-plain-text';\nimport { readStream } from './read-stream';\n\nexport const render = async (node: React.ReactNode, options?: Options) => {\n const suspendedElement = <Suspense>{node}</Suspense>;\n const reactDOMServer = await import('react-dom/server').then((m) => {\n if ('default' in m) {\n return m.default;\n }\n return m;\n });\n\n let html!: string;\n if (\n Object.hasOwn(reactDOMServer, 'renderToReadableStream') &&\n typeof WritableStream !== 'undefined'\n ) {\n html = await readStream(\n await reactDOMServer.renderToReadableStream(suspendedElement, {\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n }),\n );\n } else {\n await new Promise<void>((resolve, reject) => {\n const stream = reactDOMServer.renderToPipeableStream(suspendedElement, {\n onAllReady() {\n void readStream(stream).then((dom) => {\n html = dom;\n resolve();\n });\n },\n onError(error) {\n reject(error as Error);\n },\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n });\n });\n }\n\n if (options?.plainText) {\n return toPlainText(html, options.htmlToTextOptions);\n }\n\n const doctype = '<!DOCTYPE html>';\n\n const document = `${doctype}${html.replace(/<!DOCTYPE.*?>/, '')}`;\n\n if (options?.pretty) {\n return pretty(document);\n }\n\n return document;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["path","defaults: Options","plainTextSelectors: SelectorDefinition[]","html","templateAssetMap: Map<string, AssetCacheEntry>","html","html!: string","html","staticDir: string","fallbackStaticDir: string | undefined"],"sources":["../../src/shared/utils/pretty.ts","../../src/shared/utils/to-plain-text.ts","../../src/shared/utils/asset-registry.ts","../../src/node/read-stream.ts","../../src/node/render.tsx"],"sourcesContent":["import type { Options, Plugin } from 'prettier';\nimport type { builders } from 'prettier/doc';\nimport * as html from 'prettier/plugins/html';\nimport { format } from 'prettier/standalone';\n\ninterface HtmlNode {\n type: 'element' | 'text' | 'ieConditionalComment';\n name?: string;\n sourceSpan: {\n start: { file: unknown[]; offset: number; line: number; col: number };\n end: { file: unknown[]; offset: number; line: number; col: number };\n details: null;\n };\n parent?: HtmlNode;\n}\n\nfunction recursivelyMapDoc(\n doc: builders.Doc,\n callback: (innerDoc: string | builders.DocCommand) => builders.Doc,\n): builders.Doc {\n if (Array.isArray(doc)) {\n return doc.map((innerDoc) => recursivelyMapDoc(innerDoc, callback));\n }\n\n if (typeof doc === 'object') {\n if (doc.type === 'group') {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n expandedStates: recursivelyMapDoc(\n doc.expandedStates,\n callback,\n ) as builders.Doc[],\n };\n }\n\n if ('contents' in doc) {\n return {\n ...doc,\n contents: recursivelyMapDoc(doc.contents, callback),\n };\n }\n\n if ('parts' in doc) {\n return {\n ...doc,\n parts: recursivelyMapDoc(doc.parts, callback) as builders.Doc[],\n };\n }\n\n if (doc.type === 'if-break') {\n return {\n ...doc,\n breakContents: recursivelyMapDoc(doc.breakContents, callback),\n flatContents: recursivelyMapDoc(doc.flatContents, callback),\n };\n }\n }\n\n return callback(doc);\n}\n\nconst modifiedHtml = { ...html } as Plugin;\nif (modifiedHtml.printers) {\n const previousPrint = modifiedHtml.printers.html.print;\n modifiedHtml.printers.html.print = (path, options, print, args) => {\n const node = path.getNode() as HtmlNode;\n\n const rawPrintingResult = previousPrint(path, options, print, args);\n\n if (node.type === 'ieConditionalComment') {\n const printingResult = recursivelyMapDoc(rawPrintingResult, (doc) => {\n if (typeof doc === 'object' && doc.type === 'line') {\n return doc.soft ? '' : ' ';\n }\n\n return doc;\n });\n\n return printingResult;\n }\n\n return rawPrintingResult;\n };\n}\n\nconst defaults: Options = {\n endOfLine: 'lf',\n tabWidth: 2,\n plugins: [modifiedHtml],\n bracketSameLine: true,\n parser: 'html',\n};\n\nexport const pretty = (str: string, options: Options = {}) => {\n return format(str.replaceAll('\\0', ''), {\n ...defaults,\n ...options,\n });\n};\n","import {\n convert,\n type HtmlToTextOptions,\n type SelectorDefinition,\n} from 'html-to-text';\n\nexport const plainTextSelectors: SelectorDefinition[] = [\n { selector: 'img', format: 'skip' },\n { selector: '[data-skip-in-text=true]', format: 'skip' },\n {\n selector: 'a',\n options: { linkBrackets: false, hideLinkHrefIfSameAsText: true },\n },\n];\n\nexport function toPlainText(html: string, options?: HtmlToTextOptions) {\n return convert(html, {\n selectors: plainTextSelectors,\n wordwrap: false,\n ...options,\n });\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\n\n/**\n * Asset Registry - Embeds static assets (fonts, images) as base64 data URIs\n */\n\ninterface AssetCacheEntry {\n base64: string;\n mtime: number;\n}\n\n// Separate caches for template-specific and shared assets\nconst templateAssetCache = new Map<string, Map<string, AssetCacheEntry>>();\nconst sharedAssetCache = new Map<string, Map<string, AssetCacheEntry>>();\n\n/**\n * Recursively scan a directory and return all files with their relative paths\n */\nasync function scanDirectoryRecursive(\n dir: string,\n baseDir: string = dir,\n): Promise<Map<string, string>> {\n const files = new Map<string, string>();\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(baseDir, fullPath);\n\n if (entry.isDirectory()) {\n const subFiles = await scanDirectoryRecursive(fullPath, baseDir);\n for (const [relPath, absPath] of subFiles) {\n files.set(relPath, absPath);\n }\n } else {\n files.set(relativePath, fullPath);\n }\n }\n } catch {\n return files;\n }\n\n return files;\n}\n\n/**\n * Load a file and convert it to base64 data URI\n */\nasync function loadAssetAsBase64(\n filePath: string,\n url: string,\n): Promise<{ base64: string; mtime: number }> {\n const stats = await fs.stat(filePath);\n const buffer = await fs.readFile(filePath);\n const base64 = buffer.toString('base64');\n\n const ext = path.extname(url).toLowerCase();\n const mimeTypes: Record<string, string> = {\n '.woff2': 'font/woff2',\n '.woff': 'font/woff',\n '.otf': 'font/otf',\n '.ttf': 'font/ttf',\n '.eot': 'application/vnd.ms-fontobject',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n };\n const mimeType = mimeTypes[ext] || 'application/octet-stream';\n\n const dataUri = `data:${mimeType};base64,${base64}`;\n\n return { base64: dataUri, mtime: stats.mtimeMs };\n}\n\n/**\n * Get or build asset registry for a shared/fallback static directory\n * This is cached separately and reused across all templates\n */\nasync function getSharedAssetRegistry(\n fallbackStaticDir: string,\n): Promise<Map<string, AssetCacheEntry>> {\n const cached = sharedAssetCache.get(fallbackStaticDir);\n if (cached) {\n return cached;\n }\n\n const assetMap = new Map<string, AssetCacheEntry>();\n\n try {\n await fs.access(fallbackStaticDir);\n const files = await scanDirectoryRecursive(\n fallbackStaticDir,\n fallbackStaticDir,\n );\n\n for (const [relativePath, absolutePath] of files) {\n const url = `/${relativePath.replace(/\\\\/g, '/')}`;\n const { base64, mtime } = await loadAssetAsBase64(absolutePath, url);\n\n assetMap.set(url, { base64, mtime });\n if (url.startsWith('/')) {\n assetMap.set(url.slice(1), { base64, mtime });\n }\n }\n } catch {}\n\n sharedAssetCache.set(fallbackStaticDir, assetMap);\n return assetMap;\n}\n\n/**\n * Get or build asset registry for a static directory\n *\n * @param staticDir - Absolute path to the static directory\n * @param fallbackStaticDir - Optional fallback static directory (lower priority)\n * @returns Map of asset URLs to base64 data URIs\n */\nexport async function getAssetRegistry(\n staticDir: string,\n fallbackStaticDir?: string,\n): Promise<Map<string, string>> {\n const templateCached = templateAssetCache.get(staticDir);\n let templateAssetMap: Map<string, AssetCacheEntry>;\n\n if (templateCached) {\n templateAssetMap = templateCached;\n } else {\n templateAssetMap = new Map<string, AssetCacheEntry>();\n\n try {\n await fs.access(staticDir);\n const files = await scanDirectoryRecursive(staticDir, staticDir);\n\n for (const [relativePath, absolutePath] of files) {\n const url = `/${relativePath.replace(/\\\\/g, '/')}`;\n const { base64, mtime } = await loadAssetAsBase64(absolutePath, url);\n\n templateAssetMap.set(url, { base64, mtime });\n if (url.startsWith('/')) {\n templateAssetMap.set(url.slice(1), { base64, mtime });\n }\n }\n } catch {}\n\n templateAssetCache.set(staticDir, templateAssetMap);\n }\n\n const result = new Map<string, string>();\n\n for (const [url, entry] of templateAssetMap) {\n result.set(url, entry.base64);\n }\n\n if (fallbackStaticDir) {\n const sharedAssetMap = await getSharedAssetRegistry(fallbackStaticDir);\n for (const [url, entry] of sharedAssetMap) {\n if (!result.has(url)) {\n result.set(url, entry.base64);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Process HTML to replace relative asset URLs with base64 data URIs\n */\nexport function embedAssetsInHtml(\n html: string,\n assetRegistry: Map<string, string>,\n): string {\n const getBase64 = (url: string): string | undefined => {\n return (\n assetRegistry.get(url) ||\n assetRegistry.get(`/${url}`) ||\n assetRegistry.get(url.slice(1))\n );\n };\n\n const shouldSkip = (url: string): boolean => {\n return (\n url.startsWith('data:') ||\n url.startsWith('http://') ||\n url.startsWith('https://') ||\n url.startsWith('#') ||\n url.startsWith('mailto:') ||\n url.startsWith('tel:')\n );\n };\n\n let processedHtml = html;\n\n // Pattern 1: CSS url() - matches url('/fonts/...') or url(\"/fonts/...\")\n processedHtml = processedHtml.replace(\n /url\\((['\"]?)([^'\")]+)\\1\\)/gi,\n (match: string, quote: string, url: string) => {\n if (shouldSkip(url)) return match;\n const base64 = getBase64(url);\n return base64 ? `url(${quote}${base64}${quote})` : match;\n },\n );\n\n // Pattern 2: src attribute - matches src=\"/fonts/...\" or src='/fonts/...'\n processedHtml = processedHtml.replace(\n /src\\s*=\\s*(['\"])([^'\"]+)\\1/gi,\n (match: string, quote: string, url: string) => {\n if (shouldSkip(url)) return match;\n const base64 = getBase64(url);\n return base64 ? `src=${quote}${base64}${quote}` : match;\n },\n );\n\n // Pattern 3: href attribute - matches href=\"/fonts/...\" or href='/fonts/...'\n processedHtml = processedHtml.replace(\n /href\\s*=\\s*(['\"])([^'\"]+)\\1/gi,\n (match: string, quote: string, url: string) => {\n if (shouldSkip(url)) return match;\n const base64 = getBase64(url);\n return base64 ? `href=${quote}${base64}${quote}` : match;\n },\n );\n\n return processedHtml;\n}\n","import { Writable } from 'node:stream';\nimport type {\n PipeableStream,\n ReactDOMServerReadableStream,\n} from 'react-dom/server.browser';\n\nexport const readStream = async (\n stream: PipeableStream | ReactDOMServerReadableStream,\n) => {\n let result = '';\n // Create a single TextDecoder instance to handle streaming properly\n // This fixes issues with multi-byte characters (e.g., CJK) being split across chunks\n const decoder = new TextDecoder('utf-8');\n\n if ('pipeTo' in stream) {\n // means it's a readable stream\n const writableStream = new WritableStream({\n write(chunk: BufferSource) {\n // Use stream: true to handle multi-byte characters split across chunks\n result += decoder.decode(chunk, { stream: true });\n },\n close() {\n // Flush any remaining bytes\n result += decoder.decode();\n },\n });\n await stream.pipeTo(writableStream);\n } else {\n const writable = new Writable({\n write(chunk: BufferSource, _encoding, callback) {\n // Use stream: true to handle multi-byte characters split across chunks\n result += decoder.decode(chunk, { stream: true });\n\n callback();\n },\n final(callback) {\n // Flush any remaining bytes\n result += decoder.decode();\n callback();\n },\n });\n stream.pipe(writable);\n\n await new Promise<void>((resolve, reject) => {\n writable.on('error', reject);\n writable.on('close', () => {\n resolve();\n });\n });\n }\n\n return result;\n};\n","import path from 'node:path';\nimport { Suspense } from 'react';\nimport type { Options } from '../shared/options';\nimport {\n embedAssetsInHtml,\n getAssetRegistry,\n} from '../shared/utils/asset-registry';\nimport { pretty } from '../shared/utils/pretty';\nimport { toPlainText } from '../shared/utils/to-plain-text';\nimport { readStream } from './read-stream';\n\nexport const render = async (node: React.ReactNode, options?: Options) => {\n const suspendedElement = <Suspense>{node}</Suspense>;\n const reactDOMServer = await import('react-dom/server').then((m) => {\n if ('default' in m) {\n return m.default;\n }\n return m;\n });\n\n let html!: string;\n if (\n Object.hasOwn(reactDOMServer, 'renderToReadableStream') &&\n typeof WritableStream !== 'undefined'\n ) {\n html = await readStream(\n await reactDOMServer.renderToReadableStream(suspendedElement, {\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n }),\n );\n } else {\n await new Promise<void>((resolve, reject) => {\n const stream = reactDOMServer.renderToPipeableStream(suspendedElement, {\n onAllReady() {\n void readStream(stream).then((dom) => {\n html = dom;\n resolve();\n });\n },\n onError(error) {\n reject(error as Error);\n },\n progressiveChunkSize: Number.POSITIVE_INFINITY,\n });\n });\n }\n\n if (options?.plainText) {\n return toPlainText(html, options.htmlToTextOptions);\n }\n\n if (options?.bundleAssets) {\n let staticDir: string;\n let fallbackStaticDir: string | undefined;\n\n if (typeof options.bundleAssets === 'object') {\n staticDir = options.bundleAssets.staticDir;\n fallbackStaticDir = options.bundleAssets.fallbackStaticDir;\n } else {\n staticDir = path.join(process.cwd(), 'static');\n }\n\n const absoluteStaticDir = path.isAbsolute(staticDir)\n ? staticDir\n : path.join(process.cwd(), staticDir);\n\n const absoluteFallbackStaticDir = fallbackStaticDir\n ? path.isAbsolute(fallbackStaticDir)\n ? fallbackStaticDir\n : path.join(process.cwd(), fallbackStaticDir)\n : undefined;\n\n const assetRegistry = await getAssetRegistry(\n absoluteStaticDir,\n absoluteFallbackStaticDir,\n );\n html = embedAssetsInHtml(html, assetRegistry);\n }\n\n const doctype = '<!DOCTYPE html>';\n\n const document = `${doctype}${html.replace(/<!DOCTYPE.*?>/, '')}`;\n\n if (options?.pretty) {\n return pretty(document);\n }\n\n return document;\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahmedrowaihi/pdf-forge-core",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Transform React components into HTML for PDF generation",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/browser/index.js",
@@ -120,9 +120,10 @@
120
120
  "@types/html-to-text": "9.0.4",
121
121
  "@types/react": "npm:types-react@19.0.0-rc.1",
122
122
  "@types/react-dom": "npm:types-react-dom@19.0.0",
123
+ "@types/node": "22.14.1",
123
124
  "jsdom": "26.1.0",
124
125
  "typescript": "5.8.3",
125
- "tsconfig": "1.0.0"
126
+ "tsconfig": "1.1.0"
126
127
  },
127
128
  "publishConfig": {
128
129
  "access": "public"