Skip to content

Commit 5e7943d

Browse files
H4adeoghanmurray
andauthored
perf(snapshot): avoid recreate element a every time (#1387)
perf(snapshot): avoid costly generation of <a> element on each call to `getHref`, instead cache an anchor element and reuse it's href attributed --------- Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
1 parent f1e6a51 commit 5e7943d

File tree

2 files changed

+30
-10
lines changed

2 files changed

+30
-10
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: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,23 +196,31 @@ function getAbsoluteSrcsetString(doc: Document, attributeValue: string) {
196196
return output.join(', ');
197197
}
198198

199+
const cachedDocument = new WeakMap<Document, HTMLAnchorElement>();
200+
199201
export function absoluteToDoc(doc: Document, attributeValue: string): string {
200202
if (!attributeValue || attributeValue.trim() === '') {
201203
return attributeValue;
202204
}
203-
const a: HTMLAnchorElement = doc.createElement('a');
204-
a.href = attributeValue;
205-
return a.href;
205+
206+
return getHref(doc, attributeValue);
206207
}
207208

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

212-
function getHref() {
213-
// return a href without hash
214-
const a = document.createElement('a');
215-
a.href = '';
213+
function getHref(doc: Document, customHref?: string) {
214+
let a = cachedDocument.get(doc);
215+
if (!a) {
216+
a = doc.createElement('a');
217+
cachedDocument.set(doc, a);
218+
}
219+
if (!customHref) {
220+
customHref = '';
221+
}
222+
// note: using `new URL` is slower. See #1434 or https://jsbench.me/uqlud17rxo/1
223+
a.setAttribute('href', customHref);
216224
return a.href;
217225
}
218226

@@ -244,7 +252,7 @@ export function transformAttribute(
244252
} else if (name === 'srcset') {
245253
return getAbsoluteSrcsetString(doc, value);
246254
} else if (name === 'style') {
247-
return absoluteToStylesheet(value, getHref());
255+
return absoluteToStylesheet(value, getHref(doc));
248256
} else if (tagName === 'object' && name === 'data') {
249257
return absoluteToDoc(doc, value);
250258
}
@@ -506,6 +514,7 @@ function serializeNode(
506514
});
507515
case n.TEXT_NODE:
508516
return serializeTextNode(n as Text, {
517+
doc,
509518
needsMask,
510519
maskTextFn,
511520
rootId,
@@ -536,6 +545,7 @@ function getRootId(doc: Document, mirror: Mirror): number | undefined {
536545
function serializeTextNode(
537546
n: Text,
538547
options: {
548+
doc: Document;
539549
needsMask: boolean | undefined;
540550
maskTextFn: MaskTextFn | undefined;
541551
rootId: number | undefined;
@@ -567,7 +577,7 @@ function serializeTextNode(
567577
n,
568578
);
569579
}
570-
textContent = absoluteToStylesheet(textContent, getHref());
580+
textContent = absoluteToStylesheet(textContent, getHref(options.doc));
571581
}
572582
if (isScript) {
573583
textContent = 'SCRIPT_PLACEHOLDER';
@@ -661,7 +671,7 @@ function serializeElementNode(
661671
(n as HTMLStyleElement).sheet as CSSStyleSheet,
662672
);
663673
if (cssText) {
664-
attributes._cssText = absoluteToStylesheet(cssText, getHref());
674+
attributes._cssText = absoluteToStylesheet(cssText, getHref(doc));
665675
}
666676
}
667677
// form fields

0 commit comments

Comments
 (0)