Skip to content

Commit 44db8d7

Browse files
committed
perf(snapshot): avoid recreate element a everytime
1 parent 8aaca89 commit 44db8d7

File tree

2 files changed

+41
-9
lines changed

2 files changed

+41
-9
lines changed

.changeset/hungry-dodos-taste.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'rrweb-snapshot': patch
3+
---
4+
5+
Avoid recreating the same element every time, instead, we cache and we just update the element.
6+
7+
Before: 779k ops/s
8+
After: 860k ops/s
9+
10+
Benchmark: https://jsbench.me/ktlqztuf95/1

packages/rrweb-snapshot/src/snapshot.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -194,23 +194,45 @@ function getAbsoluteSrcsetString(doc: Document, attributeValue: string) {
194194
return output.join(', ');
195195
}
196196

197+
const cachedDocument = new WeakMap<Document, HTMLAnchorElement>();
198+
197199
export function absoluteToDoc(doc: Document, attributeValue: string): string {
198200
if (!attributeValue || attributeValue.trim() === '') {
199201
return attributeValue;
200202
}
201-
const a: HTMLAnchorElement = doc.createElement('a');
202-
a.href = attributeValue;
203-
return a.href;
203+
204+
return getHref(doc, attributeValue);
204205
}
205206

206207
function isSVGElement(el: Element): boolean {
207208
return Boolean(el.tagName === 'svg' || (el as SVGElement).ownerSVGElement);
208209
}
209210

210-
function getHref() {
211-
// return a href without hash
212-
const a = document.createElement('a');
211+
function getHref(doc: Document, customHref?: string) {
212+
let a = cachedDocument.get(doc);
213+
214+
if (!a) {
215+
a = doc.createElement('a');
216+
cachedDocument.set(doc, a);
217+
}
218+
219+
/**
220+
* This is needed for SPAs.
221+
*
222+
* Basically, the cached a link stores the URL when the element is created.
223+
* SPAs will not update the page but it can change the URL, this makes the cached a link unusable.
224+
*
225+
* In order to prevent that, I need to reset the cache by defining the URL to any value,
226+
* then I need to reset again to set the URL to '' to support relative paths like ./image.png
227+
* to be correctly displayed.
228+
*
229+
* URLs are not updated if you set the same value, that's why I need to reset the value twice.
230+
*/
231+
a.href = './clear-current';
213232
a.href = '';
233+
234+
if (customHref) a.href = customHref;
235+
214236
return a.href;
215237
}
216238

@@ -242,7 +264,7 @@ export function transformAttribute(
242264
} else if (name === 'srcset') {
243265
return getAbsoluteSrcsetString(doc, value);
244266
} else if (name === 'style') {
245-
return absoluteToStylesheet(value, getHref());
267+
return absoluteToStylesheet(value, getHref(doc));
246268
} else if (tagName === 'object' && name === 'data') {
247269
return absoluteToDoc(doc, value);
248270
}
@@ -565,7 +587,7 @@ function serializeTextNode(
565587
n,
566588
);
567589
}
568-
textContent = absoluteToStylesheet(textContent, getHref());
590+
textContent = absoluteToStylesheet(textContent, getHref(document));
569591
}
570592
if (isScript) {
571593
textContent = 'SCRIPT_PLACEHOLDER';
@@ -659,7 +681,7 @@ function serializeElementNode(
659681
(n as HTMLStyleElement).sheet as CSSStyleSheet,
660682
);
661683
if (cssText) {
662-
attributes._cssText = absoluteToStylesheet(cssText, getHref());
684+
attributes._cssText = absoluteToStylesheet(cssText, getHref(doc));
663685
}
664686
}
665687
// form fields

0 commit comments

Comments
 (0)