@zumer/snapdom 1.9.5 → 1.9.6-dev.1

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/dist/snapdom.mjs CHANGED
@@ -1,20 +1,36 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.5
4
+ * v.1.9.6-dev.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
8
8
 
9
9
 
10
10
  // src/core/cache.js
11
- var imageCache = /* @__PURE__ */ new Map();
12
- var bgCache = /* @__PURE__ */ new Map();
13
- var resourceCache = /* @__PURE__ */ new Map();
14
- var defaultStylesCache = /* @__PURE__ */ new Map();
15
- var baseCSSCache = /* @__PURE__ */ new Map();
16
- var computedStyleCache = /* @__PURE__ */ new WeakMap();
17
- var processedFontURLs = /* @__PURE__ */ new Set();
11
+ var cache = {
12
+ image: /* @__PURE__ */ new Map(),
13
+ background: /* @__PURE__ */ new Map(),
14
+ resource: /* @__PURE__ */ new Map(),
15
+ defaultStyle: /* @__PURE__ */ new Map(),
16
+ baseStyle: /* @__PURE__ */ new Map(),
17
+ computedStyle: /* @__PURE__ */ new WeakMap(),
18
+ font: /* @__PURE__ */ new Set(),
19
+ snapshot: /* @__PURE__ */ new WeakMap(),
20
+ snapshotKey: /* @__PURE__ */ new Map(),
21
+ preStyleMap: /* @__PURE__ */ new Map(),
22
+ preStyle: /* @__PURE__ */ new WeakMap(),
23
+ preNodeMap: /* @__PURE__ */ new Map(),
24
+ reset: resetCache
25
+ };
26
+ function resetCache() {
27
+ cache.computedStyle = /* @__PURE__ */ new WeakMap();
28
+ cache.snapshot = /* @__PURE__ */ new WeakMap();
29
+ cache.snapshotKey.clear();
30
+ cache.preStyleMap.clear();
31
+ cache.preStyle = /* @__PURE__ */ new WeakMap();
32
+ cache.preNodeMap.clear();
33
+ }
18
34
 
19
35
  // src/utils/cssTools.js
20
36
  var commonTags = [
@@ -62,13 +78,13 @@ function precacheCommonTags() {
62
78
  }
63
79
  }
64
80
  function getDefaultStyleForTag(tagName) {
65
- if (defaultStylesCache.has(tagName)) {
66
- return defaultStylesCache.get(tagName);
81
+ if (cache.defaultStyle.has(tagName)) {
82
+ return cache.defaultStyle.get(tagName);
67
83
  }
68
84
  const skipTags = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "noscript", "template"]);
69
85
  if (skipTags.has(tagName)) {
70
86
  const empty = {};
71
- defaultStylesCache.set(tagName, empty);
87
+ cache.defaultStyle.set(tagName, empty);
72
88
  return empty;
73
89
  }
74
90
  let sandbox = document.getElementById("snapdom-sandbox");
@@ -92,7 +108,7 @@ function getDefaultStyleForTag(tagName) {
92
108
  defaults[prop] = styles.getPropertyValue(prop);
93
109
  }
94
110
  sandbox.removeChild(el);
95
- defaultStylesCache.set(tagName, defaults);
111
+ cache.defaultStyle.set(tagName, defaults);
96
112
  return defaults;
97
113
  }
98
114
  function getStyleKey(snapshot, tagName, compress = false) {
@@ -128,7 +144,7 @@ function collectUsedTagNames(root) {
128
144
  function generateDedupedBaseCSS(usedTagNames) {
129
145
  const groups = /* @__PURE__ */ new Map();
130
146
  for (let tagName of usedTagNames) {
131
- const styles = defaultStylesCache.get(tagName);
147
+ const styles = cache.defaultStyle.get(tagName);
132
148
  if (!styles) continue;
133
149
  const key = Object.entries(styles).map(([k, v]) => `${k}:${v};`).sort().join("");
134
150
  if (!groups.has(key)) {
@@ -143,8 +159,8 @@ function generateDedupedBaseCSS(usedTagNames) {
143
159
  }
144
160
  return css;
145
161
  }
146
- function generateCSSClasses(styleMap) {
147
- const keySet = new Set(styleMap.values());
162
+ function generateCSSClasses() {
163
+ const keySet = new Set(cache.preStyleMap.values());
148
164
  const classMap = /* @__PURE__ */ new Map();
149
165
  let counter = 1;
150
166
  for (const key of keySet) {
@@ -155,16 +171,15 @@ function generateCSSClasses(styleMap) {
155
171
 
156
172
  // src/utils/helpers.js
157
173
  async function inlineSingleBackgroundEntry(entry, options = {}) {
158
- const match = entry.match(/url\(["']?(.*?)["']?\)/);
159
- const rawUrl = match?.[1];
174
+ const rawUrl = extractURL(entry);
160
175
  const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
161
176
  if (rawUrl) {
162
177
  const encodedUrl = safeEncodeURI(rawUrl);
163
- if (bgCache.has(encodedUrl)) {
164
- return options.skipInline ? void 0 : `url(${bgCache.get(encodedUrl)})`;
178
+ if (cache.background.has(encodedUrl)) {
179
+ return options.skipInline ? void 0 : `url(${cache.background.get(encodedUrl)})`;
165
180
  } else {
166
181
  const dataUrl = await fetchImage(encodedUrl, { useProxy: options.useProxy });
167
- bgCache.set(encodedUrl, dataUrl);
182
+ cache.background.set(encodedUrl, dataUrl);
168
183
  return options.skipInline ? void 0 : `url("${dataUrl}")`;
169
184
  }
170
185
  }
@@ -185,10 +200,10 @@ function getStyle(el, pseudo = null) {
185
200
  if (!(el instanceof Element)) {
186
201
  return window.getComputedStyle(el, pseudo);
187
202
  }
188
- let map = computedStyleCache.get(el);
203
+ let map = cache.computedStyle.get(el);
189
204
  if (!map) {
190
205
  map = /* @__PURE__ */ new Map();
191
- computedStyleCache.set(el, map);
206
+ cache.computedStyle.set(el, map);
192
207
  }
193
208
  if (!map.has(pseudo)) {
194
209
  const st = window.getComputedStyle(el, pseudo);
@@ -262,13 +277,13 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
262
277
  }
263
278
  const crossOriginValue = getCrossOriginMode(src);
264
279
  console.log(`[SnapDOM - fetchImage] Start loading image: ${src} with crossOrigin=${crossOriginValue}`);
265
- if (imageCache.has(src)) {
280
+ if (cache.image.has(src)) {
266
281
  console.log(`[SnapDOM - fetchImage] Cache hit for: ${src}`);
267
- return Promise.resolve(imageCache.get(src));
282
+ return Promise.resolve(cache.image.get(src));
268
283
  }
269
284
  const isDataURI = src.startsWith("data:image/");
270
285
  if (isDataURI) {
271
- imageCache.set(src, src);
286
+ cache.image.set(src, src);
272
287
  return Promise.resolve(src);
273
288
  }
274
289
  const isSVG = /\.svg(\?.*)?$/i.test(src);
@@ -281,7 +296,7 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
281
296
  });
282
297
  const svgText = await response.text();
283
298
  const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
284
- imageCache.set(src, encoded);
299
+ cache.image.set(src, encoded);
285
300
  return encoded;
286
301
  } catch {
287
302
  return fetchWithFallback(src);
@@ -305,12 +320,12 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
305
320
  const ctx = canvas.getContext("2d");
306
321
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
307
322
  const dataURL = canvas.toDataURL("image/png");
308
- imageCache.set(src, dataURL);
323
+ cache.image.set(src, dataURL);
309
324
  resolve(dataURL);
310
325
  } catch {
311
326
  try {
312
327
  const fallbackDataURL = await fetchWithFallback(src);
313
- imageCache.set(src, fallbackDataURL);
328
+ cache.image.set(src, fallbackDataURL);
314
329
  resolve(fallbackDataURL);
315
330
  } catch (e) {
316
331
  reject(e);
@@ -322,7 +337,7 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
322
337
  console.error(`[SnapDOM - fetchImage] Image failed to load: ${src}`);
323
338
  try {
324
339
  const fallbackDataURL = await fetchWithFallback(src);
325
- imageCache.set(src, fallbackDataURL);
340
+ cache.image.set(src, fallbackDataURL);
326
341
  resolve(fallbackDataURL);
327
342
  } catch (e) {
328
343
  reject(e);
@@ -386,8 +401,6 @@ function splitBackgroundImage(bg) {
386
401
  }
387
402
 
388
403
  // src/modules/styles.js
389
- var snapshotCache = /* @__PURE__ */ new WeakMap();
390
- var snapshotKeyCache = /* @__PURE__ */ new Map();
391
404
  function snapshotComputedStyleFull(style) {
392
405
  const result = {};
393
406
  for (let i = 0; i < style.length; i++) {
@@ -400,147 +413,182 @@ function snapshotComputedStyleFull(style) {
400
413
  }
401
414
  return result;
402
415
  }
403
- function inlineAllStyles(source, clone, styleMap, cache, compress) {
416
+ function inlineAllStyles(source, clone, compress) {
404
417
  if (source.tagName === "STYLE") return;
405
- if (!cache.has(source)) {
406
- cache.set(source, getStyle(source));
418
+ if (!cache.preStyle.has(source)) {
419
+ cache.preStyle.set(source, getStyle(source));
407
420
  }
408
- const style = cache.get(source);
409
- if (!snapshotCache.has(source)) {
421
+ const style = cache.preStyle.get(source);
422
+ if (!cache.snapshot.has(source)) {
410
423
  const snapshot2 = snapshotComputedStyleFull(style);
411
- snapshotCache.set(source, snapshot2);
424
+ cache.snapshot.set(source, snapshot2);
412
425
  }
413
- const snapshot = snapshotCache.get(source);
426
+ const snapshot = cache.snapshot.get(source);
414
427
  const hash = Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)).map(([prop, val]) => `${prop}:${val}`).join(";");
415
- if (snapshotKeyCache.has(hash)) {
416
- styleMap.set(clone, snapshotKeyCache.get(hash));
428
+ if (cache.snapshotKey.has(hash)) {
429
+ cache.preStyleMap.set(clone, cache.snapshotKey.get(hash));
417
430
  return;
418
431
  }
419
432
  const tagName = source.tagName?.toLowerCase() || "div";
420
433
  const key = getStyleKey(snapshot, tagName, compress);
421
- snapshotKeyCache.set(hash, key);
422
- styleMap.set(clone, key);
434
+ cache.snapshotKey.set(hash, key);
435
+ cache.preStyleMap.set(clone, key);
423
436
  }
424
437
 
425
438
  // src/core/clone.js
426
439
  function isSlotElement(node) {
427
440
  return node.nodeType === Node.ELEMENT_NODE && node.tagName === "SLOT";
428
441
  }
429
- function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
430
- if (node.nodeType === Node.TEXT_NODE) {
431
- return node.cloneNode(true);
432
- }
433
- if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
434
- if (node.getAttribute("data-capture") === "exclude") {
435
- const spacer = document.createElement("div");
436
- const rect = node.getBoundingClientRect();
437
- spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
438
- return spacer;
439
- }
440
- if (options.exclude && Array.isArray(options.exclude)) {
441
- for (const selector of options.exclude) {
442
+ function deepClone(node, compress, options = {}, originalRoot) {
443
+ if (!node) {
444
+ throw new Error("Invalid node: node is null or undefined");
445
+ }
446
+ try {
447
+ if (node.nodeType === Node.TEXT_NODE) {
448
+ return node.cloneNode(true);
449
+ }
450
+ if (node.nodeType !== Node.ELEMENT_NODE) {
451
+ return node.cloneNode(true);
452
+ }
453
+ if (node.getAttribute("data-capture") === "exclude") {
454
+ const spacer = document.createElement("div");
455
+ const rect = node.getBoundingClientRect();
456
+ spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
457
+ return spacer;
458
+ }
459
+ if (options.exclude && Array.isArray(options.exclude)) {
460
+ for (const selector of options.exclude) {
461
+ try {
462
+ if (node.matches?.(selector)) {
463
+ const spacer = document.createElement("div");
464
+ const rect = node.getBoundingClientRect();
465
+ spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
466
+ return spacer;
467
+ }
468
+ } catch (err) {
469
+ console.warn(`Invalid selector in exclude option: ${selector}`, err);
470
+ }
471
+ }
472
+ }
473
+ if (typeof options.filter === "function") {
442
474
  try {
443
- if (node.matches?.(selector)) {
475
+ if (!options.filter(node, originalRoot || node)) {
444
476
  const spacer = document.createElement("div");
445
477
  const rect = node.getBoundingClientRect();
446
478
  spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
447
479
  return spacer;
448
480
  }
449
481
  } catch (err) {
450
- console.warn(`Invalid selector in exclude option: ${selector}`, err);
482
+ console.warn("Error in filter function:", err);
451
483
  }
452
484
  }
453
- }
454
- if (typeof options.filter === "function") {
485
+ if (node.tagName === "IFRAME") {
486
+ const fallback = document.createElement("div");
487
+ fallback.textContent = "";
488
+ fallback.style.cssText = `width: ${node.offsetWidth}px; height: ${node.offsetHeight}px; background-image: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`;
489
+ return fallback;
490
+ }
491
+ if (node.getAttribute("data-capture") === "placeholder") {
492
+ const clone2 = node.cloneNode(false);
493
+ cache.preNodeMap.set(clone2, node);
494
+ inlineAllStyles(node, clone2, compress);
495
+ const placeholder = document.createElement("div");
496
+ placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
497
+ placeholder.style.cssText = `color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;`;
498
+ clone2.appendChild(placeholder);
499
+ return clone2;
500
+ }
501
+ if (node.tagName === "CANVAS") {
502
+ const dataURL = node.toDataURL();
503
+ const img = document.createElement("img");
504
+ img.src = dataURL;
505
+ img.width = node.width;
506
+ img.height = node.height;
507
+ img.style.display = "inline-block";
508
+ img.style.width = node.style.width || `${node.width}px`;
509
+ img.style.height = node.style.height || `${node.height}px`;
510
+ return img;
511
+ }
512
+ let clone;
455
513
  try {
456
- if (!options.filter(node, originalRoot || node)) {
457
- const spacer = document.createElement("div");
458
- const rect = node.getBoundingClientRect();
459
- spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
460
- return spacer;
514
+ clone = node.cloneNode(false);
515
+ if (!clone) {
516
+ throw new Error("Failed to clone node: cloneNode returned null");
461
517
  }
518
+ cache.preNodeMap.set(clone, node);
462
519
  } catch (err) {
463
- console.warn("Error in filter function:", err);
520
+ console.error("[Snapdom] Failed to clone node:", node, err);
521
+ throw err;
464
522
  }
465
- }
466
- if (node.tagName === "IFRAME") {
467
- const fallback = document.createElement("div");
468
- fallback.textContent = "";
469
- fallback.style.cssText = `width: ${node.offsetWidth}px; height: ${node.offsetHeight}px; background-image: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`;
470
- return fallback;
471
- }
472
- if (node.getAttribute("data-capture") === "placeholder") {
473
- const clone2 = node.cloneNode(false);
474
- nodeMap.set(clone2, node);
475
- inlineAllStyles(node, clone2, styleMap, styleCache, compress);
476
- const placeholder = document.createElement("div");
477
- placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
478
- placeholder.style.cssText = `color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;`;
479
- clone2.appendChild(placeholder);
480
- return clone2;
481
- }
482
- if (node.tagName === "CANVAS") {
483
- const dataURL = node.toDataURL();
484
- const img = document.createElement("img");
485
- img.src = dataURL;
486
- img.width = node.width;
487
- img.height = node.height;
488
- img.style.display = "inline-block";
489
- img.style.width = node.style.width || `${node.width}px`;
490
- img.style.height = node.style.height || `${node.height}px`;
491
- return img;
492
- }
493
- const clone = node.cloneNode(false);
494
- nodeMap.set(clone, node);
495
- if (node instanceof HTMLInputElement) {
496
- clone.value = node.value;
497
- clone.setAttribute("value", node.value);
498
- if (node.checked !== void 0) {
499
- clone.checked = node.checked;
500
- if (node.checked) clone.setAttribute("checked", "");
501
- }
502
- } else if (node instanceof HTMLTextAreaElement) {
503
- const rect = node.getBoundingClientRect();
504
- clone.textContent = node.value;
505
- clone.style.width = `${rect.width}px`;
506
- clone.style.height = `${rect.height}px`;
507
- } else if (node instanceof HTMLSelectElement) {
508
- clone.value = node.value;
509
- Array.from(clone.options).forEach((opt) => {
510
- if (opt.value === node.value) {
511
- opt.setAttribute("selected", "");
512
- } else {
513
- opt.removeAttribute("selected");
523
+ if (node instanceof HTMLInputElement) {
524
+ clone.value = node.value;
525
+ clone.setAttribute("value", node.value);
526
+ if (node.checked !== void 0) {
527
+ clone.checked = node.checked;
528
+ if (node.checked) clone.setAttribute("checked", "");
514
529
  }
515
- });
516
- }
517
- inlineAllStyles(node, clone, styleMap, styleCache, compress);
518
- if (isSlotElement(node)) {
519
- const assigned = node.assignedNodes?.({ flatten: true }) || [];
520
- const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
521
- const fragment = document.createDocumentFragment();
522
- for (const child of nodesToClone) {
523
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
524
- if (clonedChild) fragment.appendChild(clonedChild);
525
- }
526
- return fragment;
527
- } else if (node instanceof HTMLTextAreaElement) {
528
- } else {
529
- const baseChildren = node.shadowRoot ? node.shadowRoot.childNodes : node.childNodes;
530
- for (const child of baseChildren) {
531
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
532
- if (clonedChild) clone.appendChild(clonedChild);
530
+ } else if (node instanceof HTMLTextAreaElement) {
531
+ const rect = node.getBoundingClientRect();
532
+ clone.textContent = node.value;
533
+ clone.style.width = `${rect.width}px`;
534
+ clone.style.height = `${rect.height}px`;
535
+ } else if (node instanceof HTMLSelectElement) {
536
+ clone.value = node.value;
537
+ Array.from(clone.options).forEach((opt) => {
538
+ if (opt.value === node.value) {
539
+ opt.setAttribute("selected", "");
540
+ } else {
541
+ opt.removeAttribute("selected");
542
+ }
543
+ });
533
544
  }
534
- if (node.shadowRoot && node.childNodes.length > 0 && !node.shadowRoot.querySelector("slot")) {
535
- const lightDomContent = document.createDocumentFragment();
536
- for (const child of node.childNodes) {
537
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
538
- if (clonedChild) lightDomContent.appendChild(clonedChild);
545
+ inlineAllStyles(node, clone, compress);
546
+ try {
547
+ if (isSlotElement(node)) {
548
+ const assigned = node.assignedNodes?.({ flatten: true }) || [];
549
+ const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
550
+ const fragment = document.createDocumentFragment();
551
+ for (const child of nodesToClone) {
552
+ try {
553
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
554
+ if (clonedChild) fragment.appendChild(clonedChild);
555
+ } catch (err) {
556
+ console.warn("[Snapdom] Failed to clone slot content:", child, err);
557
+ }
558
+ }
559
+ return fragment;
560
+ } else {
561
+ const baseChildren = node.shadowRoot ? node.shadowRoot.childNodes : node.childNodes;
562
+ for (const child of baseChildren) {
563
+ try {
564
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
565
+ if (clonedChild) clone.appendChild(clonedChild);
566
+ } catch (err) {
567
+ console.warn("[Snapdom] Failed to clone child node:", child, err);
568
+ }
569
+ }
570
+ if (node.shadowRoot && node.childNodes.length > 0 && !node.shadowRoot.querySelector("slot")) {
571
+ const lightDomContent = document.createDocumentFragment();
572
+ for (const child of node.childNodes) {
573
+ try {
574
+ const clonedChild = deepClone(child, compress, options, originalRoot || node);
575
+ if (clonedChild) lightDomContent.appendChild(clonedChild);
576
+ } catch (err) {
577
+ console.warn("[Snapdom] Failed to clone light DOM child:", child, err);
578
+ }
579
+ }
580
+ clone.appendChild(lightDomContent);
581
+ }
539
582
  }
540
- clone.appendChild(lightDomContent);
583
+ } catch (err) {
584
+ console.error("[Snapdom] Error cloning children:", err);
585
+ throw err;
541
586
  }
587
+ return clone;
588
+ } catch (error) {
589
+ console.error("[Snapdom] Error in deepClone:", error);
590
+ throw error;
542
591
  }
543
- return clone;
544
592
  }
545
593
 
546
594
  // src/modules/iconFonts.js
@@ -620,14 +668,14 @@ function injectLinkIfMissing(href) {
620
668
  });
621
669
  }
622
670
  async function embedCustomFonts({ preCached = false } = {}) {
623
- if (resourceCache.has("fonts-embed-css")) {
671
+ if (cache.resource.has("fonts-embed-css")) {
624
672
  if (preCached) {
625
673
  const style = document.createElement("style");
626
674
  style.setAttribute("data-snapdom", "embedFonts");
627
- style.textContent = resourceCache.get("fonts-embed-css");
675
+ style.textContent = cache.resource.get("fonts-embed-css");
628
676
  document.head.appendChild(style);
629
677
  }
630
- return resourceCache.get("fonts-embed-css");
678
+ return cache.resource.get("fonts-embed-css");
631
679
  }
632
680
  const importRegex = /@import\s+url\(["']?([^"')]+)["']?\)/g;
633
681
  const styleImports = [];
@@ -660,11 +708,11 @@ async function embedCustomFonts({ preCached = false } = {}) {
660
708
  url = new URL(url, link.href).href;
661
709
  }
662
710
  if (isIconFont(url)) return null;
663
- if (resourceCache.has(url)) {
664
- processedFontURLs.add(url);
665
- return { original: match[0], inlined: `url(${resourceCache.get(url)})` };
711
+ if (cache.resource.has(url)) {
712
+ cache.font.add(url);
713
+ return { original: match[0], inlined: `url(${cache.resource.get(url)})` };
666
714
  }
667
- if (processedFontURLs.has(url)) return null;
715
+ if (cache.font.has(url)) return null;
668
716
  try {
669
717
  const fontRes = await fetch(url);
670
718
  const blob = await fontRes.blob();
@@ -673,8 +721,8 @@ async function embedCustomFonts({ preCached = false } = {}) {
673
721
  reader.onload = () => resolve(reader.result);
674
722
  reader.readAsDataURL(blob);
675
723
  });
676
- resourceCache.set(url, b64);
677
- processedFontURLs.add(url);
724
+ cache.resource.set(url, b64);
725
+ cache.font.add(url);
678
726
  return { original: match[0], inlined: `url(${b64})` };
679
727
  } catch (e) {
680
728
  console.warn("[snapdom] Failed to fetch font resource:", url);
@@ -710,12 +758,12 @@ async function embedCustomFonts({ preCached = false } = {}) {
710
758
  url = new URL(url, sheet.href || location.href).href;
711
759
  }
712
760
  if (isIconFont(url)) continue;
713
- if (resourceCache.has(url)) {
714
- processedFontURLs.add(url);
715
- inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
761
+ if (cache.resource.has(url)) {
762
+ cache.font.add(url);
763
+ inlinedSrc = inlinedSrc.replace(match[0], `url(${cache.resource.get(url)})`);
716
764
  continue;
717
765
  }
718
- if (processedFontURLs.has(url)) continue;
766
+ if (cache.font.has(url)) continue;
719
767
  try {
720
768
  const res = await fetch(url);
721
769
  const blob = await res.blob();
@@ -724,8 +772,8 @@ async function embedCustomFonts({ preCached = false } = {}) {
724
772
  reader.onload = () => resolve(reader.result);
725
773
  reader.readAsDataURL(blob);
726
774
  });
727
- resourceCache.set(url, b64);
728
- processedFontURLs.add(url);
775
+ cache.resource.set(url, b64);
776
+ cache.font.add(url);
729
777
  inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
730
778
  } catch (e) {
731
779
  console.warn("[snapdom] Failed to fetch font URL:", url);
@@ -750,10 +798,10 @@ async function embedCustomFonts({ preCached = false } = {}) {
750
798
  if (isIconFont(font.family)) continue;
751
799
  let b64 = font._snapdomSrc;
752
800
  if (!b64.startsWith("data:")) {
753
- if (resourceCache.has(font._snapdomSrc)) {
754
- b64 = resourceCache.get(font._snapdomSrc);
755
- processedFontURLs.add(font._snapdomSrc);
756
- } else if (!processedFontURLs.has(font._snapdomSrc)) {
801
+ if (cache.resource.has(font._snapdomSrc)) {
802
+ b64 = cache.resource.get(font._snapdomSrc);
803
+ cache.font.add(font._snapdomSrc);
804
+ } else if (!cache.font.has(font._snapdomSrc)) {
757
805
  try {
758
806
  const res = await fetch(font._snapdomSrc);
759
807
  const blob = await res.blob();
@@ -762,8 +810,8 @@ async function embedCustomFonts({ preCached = false } = {}) {
762
810
  reader.onload = () => resolve(reader.result);
763
811
  reader.readAsDataURL(blob);
764
812
  });
765
- resourceCache.set(font._snapdomSrc, b64);
766
- processedFontURLs.add(font._snapdomSrc);
813
+ cache.resource.set(font._snapdomSrc, b64);
814
+ cache.font.add(font._snapdomSrc);
767
815
  } catch (e) {
768
816
  console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
769
817
  continue;
@@ -780,7 +828,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
780
828
  }
781
829
  }
782
830
  if (finalCSS) {
783
- resourceCache.set("fonts-embed-css", finalCSS);
831
+ cache.resource.set("fonts-embed-css", finalCSS);
784
832
  if (preCached) {
785
833
  const style = document.createElement("style");
786
834
  style.setAttribute("data-snapdom", "embedFonts");
@@ -792,7 +840,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
792
840
  }
793
841
 
794
842
  // src/modules/pseudo.js
795
- async function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false, useProxy) {
843
+ async function inlinePseudoElements(source, clone, compress, embedFonts = false, useProxy) {
796
844
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
797
845
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
798
846
  try {
@@ -816,7 +864,7 @@ async function inlinePseudoElements(source, clone, styleMap, styleCache, compres
816
864
  span.dataset.snapdomPseudo = "::first-letter";
817
865
  const snapshot = snapshotComputedStyle(style);
818
866
  const key = getStyleKey(snapshot, "span", compress);
819
- styleMap.set(span, key);
867
+ cache.preStyleMap.set(span, key);
820
868
  const restNode = document.createTextNode(rest);
821
869
  clone.replaceChild(restNode, textNode);
822
870
  clone.insertBefore(span, restNode);
@@ -837,7 +885,7 @@ async function inlinePseudoElements(source, clone, styleMap, styleCache, compres
837
885
  pseudoEl.dataset.snapdomPseudo = pseudo;
838
886
  const snapshot = snapshotComputedStyle(style);
839
887
  const key = getStyleKey(snapshot, "span", compress);
840
- styleMap.set(pseudoEl, key);
888
+ cache.preStyleMap.set(pseudoEl, key);
841
889
  const isIconFont2 = isIconFont(fontFamily);
842
890
  const cleanContent = parseContent(content);
843
891
  if (isIconFont2 && cleanContent.length === 1) {
@@ -888,8 +936,6 @@ async function inlinePseudoElements(source, clone, styleMap, styleCache, compres
888
936
  await inlinePseudoElements(
889
937
  sChildren[i],
890
938
  cChildren[i],
891
- styleMap,
892
- styleCache,
893
939
  compress,
894
940
  embedFonts,
895
941
  useProxy
@@ -929,18 +975,16 @@ function inlineExternalDef(root) {
929
975
 
930
976
  // src/core/prepare.js
931
977
  async function prepareClone(element, compress = false, embedFonts = false, options = {}) {
932
- const styleMap = /* @__PURE__ */ new Map();
933
- const styleCache = /* @__PURE__ */ new WeakMap();
934
- const nodeMap = /* @__PURE__ */ new Map();
935
978
  let clone;
979
+ let classCSS = "";
936
980
  try {
937
- clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
981
+ clone = deepClone(element, compress, options, element);
938
982
  } catch (e) {
939
983
  console.warn("deepClone failed:", e);
940
984
  throw e;
941
985
  }
942
986
  try {
943
- await inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
987
+ await inlinePseudoElements(element, clone, compress, embedFonts, options.useProxy);
944
988
  } catch (e) {
945
989
  console.warn("inlinePseudoElements failed:", e);
946
990
  }
@@ -949,11 +993,10 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
949
993
  } catch (e) {
950
994
  console.warn("inlineExternalDef failed:", e);
951
995
  }
952
- let classCSS = "";
953
996
  if (compress) {
954
- const keyToClass = generateCSSClasses(styleMap);
997
+ const keyToClass = generateCSSClasses();
955
998
  classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
956
- for (const [node, key] of styleMap.entries()) {
999
+ for (const [node, key] of cache.preStyleMap.entries()) {
957
1000
  if (node.tagName === "STYLE") continue;
958
1001
  const className = keyToClass.get(key);
959
1002
  if (className) node.classList.add(className);
@@ -962,12 +1005,12 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
962
1005
  if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
963
1006
  }
964
1007
  } else {
965
- for (const [node, key] of styleMap.entries()) {
1008
+ for (const [node, key] of cache.preStyleMap.entries()) {
966
1009
  if (node.tagName === "STYLE") continue;
967
1010
  node.setAttribute("style", key.replace(/;/g, "; "));
968
1011
  }
969
1012
  }
970
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
1013
+ for (const [cloneNode, originalNode] of cache.preNodeMap.entries()) {
971
1014
  const scrollX = originalNode.scrollLeft;
972
1015
  const scrollY = originalNode.scrollTop;
973
1016
  const hasScroll = scrollX || scrollY;
@@ -986,9 +1029,9 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
986
1029
  cloneNode.appendChild(inner);
987
1030
  }
988
1031
  }
989
- if (element === nodeMap.get(clone)) {
990
- const computed = styleCache.get(element) || window.getComputedStyle(element);
991
- styleCache.set(element, computed);
1032
+ if (element === cache.preNodeMap.get(clone)) {
1033
+ const computed = cache.preStyle.get(element) || window.getComputedStyle(element);
1034
+ cache.preStyle.set(element, computed);
992
1035
  const transform = stripTranslate(computed.transform);
993
1036
  clone.style.margin = "0";
994
1037
  clone.style.position = "static";
@@ -1001,13 +1044,13 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
1001
1044
  clone.style.clear = "none";
1002
1045
  clone.style.transform = transform || "";
1003
1046
  }
1004
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
1047
+ for (const [cloneNode, originalNode] of cache.preNodeMap.entries()) {
1005
1048
  if (originalNode.tagName === "PRE") {
1006
1049
  cloneNode.style.marginTop = "0";
1007
1050
  cloneNode.style.marginBlockStart = "0";
1008
1051
  }
1009
1052
  }
1010
- return { clone, classCSS, styleCache };
1053
+ return { clone, classCSS };
1011
1054
  }
1012
1055
 
1013
1056
  // src/modules/images.js
@@ -1034,7 +1077,7 @@ async function inlineImages(clone, options = {}) {
1034
1077
  }
1035
1078
 
1036
1079
  // src/modules/background.js
1037
- async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
1080
+ async function inlineBackgroundImages(source, clone, options = {}) {
1038
1081
  const queue = [[source, clone]];
1039
1082
  const imageProps = [
1040
1083
  "background-image",
@@ -1048,8 +1091,8 @@ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
1048
1091
  ];
1049
1092
  while (queue.length) {
1050
1093
  const [srcNode, cloneNode] = queue.shift();
1051
- const style = styleCache.get(srcNode) || getStyle(srcNode);
1052
- if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
1094
+ const style = cache.preStyle.get(srcNode) || getStyle(srcNode);
1095
+ if (!cache.preStyle.has(srcNode)) cache.preStyle.set(srcNode, style);
1053
1096
  for (const prop of imageProps) {
1054
1097
  const val = style.getPropertyValue(prop);
1055
1098
  if (!val || val === "none") continue;
@@ -1076,13 +1119,14 @@ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
1076
1119
  // src/core/capture.js
1077
1120
  async function captureDOM(element, options = {}) {
1078
1121
  if (!element) throw new Error("Element cannot be null or undefined");
1122
+ cache.reset();
1079
1123
  const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "" } = options;
1080
- let clone, classCSS, styleCache;
1124
+ let clone, classCSS;
1081
1125
  let fontsCSS = "";
1082
1126
  let baseCSS = "";
1083
1127
  let dataURL;
1084
1128
  let svgString;
1085
- ({ clone, classCSS, styleCache } = await prepareClone(element, compress, embedFonts, options));
1129
+ ({ clone, classCSS } = await prepareClone(element, compress, embedFonts, options));
1086
1130
  await new Promise((resolve) => {
1087
1131
  idle(async () => {
1088
1132
  await inlineImages(clone, options);
@@ -1091,7 +1135,7 @@ async function captureDOM(element, options = {}) {
1091
1135
  });
1092
1136
  await new Promise((resolve) => {
1093
1137
  idle(async () => {
1094
- await inlineBackgroundImages(element, clone, styleCache, options);
1138
+ await inlineBackgroundImages(element, clone, options);
1095
1139
  resolve();
1096
1140
  }, { fast });
1097
1141
  });
@@ -1106,13 +1150,13 @@ async function captureDOM(element, options = {}) {
1106
1150
  if (compress) {
1107
1151
  const usedTags = collectUsedTagNames(clone).sort();
1108
1152
  const tagKey = usedTags.join(",");
1109
- if (baseCSSCache.has(tagKey)) {
1110
- baseCSS = baseCSSCache.get(tagKey);
1153
+ if (cache.baseStyle.has(tagKey)) {
1154
+ baseCSS = cache.baseStyle.get(tagKey);
1111
1155
  } else {
1112
1156
  await new Promise((resolve) => {
1113
1157
  idle(() => {
1114
1158
  baseCSS = generateDedupedBaseCSS(usedTags);
1115
- baseCSSCache.set(tagKey, baseCSS);
1159
+ cache.baseStyle.set(tagKey, baseCSS);
1116
1160
  resolve();
1117
1161
  }, { fast });
1118
1162
  });
@@ -1239,8 +1283,10 @@ async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
1239
1283
  ctx.drawImage(baseCanvas, 0, 0);
1240
1284
  return temp;
1241
1285
  }
1242
- async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
1243
- const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
1286
+ async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor, quality }, format = "png") {
1287
+ const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1288
+ const finalBg = backgroundColor ?? defaultBg;
1289
+ const canvas = await createBackground(url, { dpr, scale }, finalBg);
1244
1290
  const img = new Image();
1245
1291
  img.src = canvas.toDataURL(`image/${format}`, quality);
1246
1292
  await img.decode();
@@ -1321,10 +1367,7 @@ snapdom.download = async (el, options = {}) => {
1321
1367
  async function preCache(root = document, options = {}) {
1322
1368
  const { embedFonts = true, reset = false } = options;
1323
1369
  if (reset) {
1324
- imageCache.clear();
1325
- bgCache.clear();
1326
- resourceCache.clear();
1327
- baseCSSCache.clear();
1370
+ cache.reset();
1328
1371
  return;
1329
1372
  }
1330
1373
  await document.fonts.ready;
@@ -1337,9 +1380,9 @@ async function preCache(root = document, options = {}) {
1337
1380
  const promises = [];
1338
1381
  for (const img of imgEls) {
1339
1382
  const src = img.src;
1340
- if (!imageCache.has(src)) {
1383
+ if (!cache.image.has(src)) {
1341
1384
  promises.push(
1342
- fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
1385
+ fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => cache.image.set(src, dataURL)).catch(() => {
1343
1386
  })
1344
1387
  );
1345
1388
  }