From 92c99b6efb87ec106d32744446a666b71b641b26 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Tue, 9 Apr 2024 12:49:32 +0100 Subject: [PATCH 1/2] See #1339 - perf: remove a nested function call and an object clone per event --- .changeset/event-single-wrap.md | 5 + packages/rrweb/src/record/iframe-manager.ts | 10 +- packages/rrweb/src/record/index.ts | 299 +++++++++----------- packages/rrweb/test/utils.ts | 4 +- packages/types/src/index.ts | 4 +- 5 files changed, 145 insertions(+), 177 deletions(-) create mode 100644 .changeset/event-single-wrap.md diff --git a/.changeset/event-single-wrap.md b/.changeset/event-single-wrap.md new file mode 100644 index 0000000000..c659c015ef --- /dev/null +++ b/.changeset/event-single-wrap.md @@ -0,0 +1,5 @@ +--- +'rrweb': patch +--- + +perf: Avoid an extra function call and object clone during event emission diff --git a/packages/rrweb/src/record/iframe-manager.ts b/packages/rrweb/src/record/iframe-manager.ts index 26985cc49a..2d9205c0cb 100644 --- a/packages/rrweb/src/record/iframe-manager.ts +++ b/packages/rrweb/src/record/iframe-manager.ts @@ -3,7 +3,11 @@ import { genId, NodeType } from 'rrweb-snapshot'; import type { CrossOriginIframeMessageEvent } from '../types'; import CrossOriginIframeMirror from './cross-origin-iframe-mirror'; import { EventType, IncrementalSource } from '@rrweb/types'; -import type { eventWithTime, mutationCallBack } from '@rrweb/types'; +import type { + eventWithTime, + eventWithoutTime, + mutationCallBack, +} from '@rrweb/types'; import type { StylesheetManager } from './stylesheet-manager'; export class IframeManager { @@ -16,7 +20,7 @@ export class IframeManager { new WeakMap(); private mirror: Mirror; private mutationCb: mutationCallBack; - private wrappedEmit: (e: eventWithTime, isCheckout?: boolean) => void; + private wrappedEmit: (e: eventWithoutTime, isCheckout?: boolean) => void; private loadListener?: (iframeEl: HTMLIFrameElement) => unknown; private stylesheetManager: StylesheetManager; private recordCrossOriginIframes: boolean; @@ -26,7 +30,7 @@ export class IframeManager { mutationCb: mutationCallBack; stylesheetManager: StylesheetManager; recordCrossOriginIframes: boolean; - wrappedEmit: (e: eventWithTime, isCheckout?: boolean) => void; + wrappedEmit: (e: eventWithoutTime, isCheckout?: boolean) => void; }) { this.mutationCb = options.mutationCb; this.wrappedEmit = options.wrappedEmit; diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 3b4475cfc9..7be978199d 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -19,7 +19,7 @@ import { import type { recordOptions } from '../types'; import { EventType, - event, + eventWithoutTime, eventWithTime, IncrementalSource, listenerHandler, @@ -40,14 +40,7 @@ import { unregisterErrorHandler, } from './error-handler'; -function wrapEvent(e: event): eventWithTime { - return { - ...e, - timestamp: nowTimestamp(), - }; -} - -let wrappedEmit!: (e: eventWithTime, isCheckout?: boolean) => void; +let wrappedEmit!: (e: eventWithoutTime, isCheckout?: boolean) => void; let takeFullSnapshot!: (isCheckout?: boolean) => void; let canvasManager!: CanvasManager; @@ -187,7 +180,9 @@ function record( } return e as unknown as T; }; - wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => { + wrappedEmit = (r: eventWithoutTime, isCheckout?: boolean) => { + const e = r as eventWithTime; + e.timestamp = nowTimestamp(); if ( mutationBuffers[0]?.isFrozen() && e.type !== EventType.FullSnapshot && @@ -238,47 +233,39 @@ function record( }; const wrappedMutationEmit = (m: mutationCallbackParam) => { - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.Mutation, - ...m, - }, - }), - ); + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Mutation, + ...m, + }, + }); }; const wrappedScrollEmit: scrollCallback = (p) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.Scroll, - ...p, - }, - }), - ); + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Scroll, + ...p, + }, + }); const wrappedCanvasMutationEmit = (p: canvasMutationParam) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.CanvasMutation, - ...p, - }, - }), - ); + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.CanvasMutation, + ...p, + }, + }); const wrappedAdoptedStyleSheetEmit = (a: adoptedStyleSheetParam) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.AdoptedStyleSheet, - ...a, - }, - }), - ); + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.AdoptedStyleSheet, + ...a, + }, + }); const stylesheetManager = new StylesheetManager({ mutationCb: wrappedMutationEmit, @@ -350,14 +337,14 @@ function record( return; } wrappedEmit( - wrapEvent({ + { type: EventType.Meta, data: { href: window.location.href, width: getWindowWidth(), height: getWindowHeight(), }, - }), + }, isCheckout, ); @@ -406,13 +393,13 @@ function record( } wrappedEmit( - wrapEvent({ + { type: EventType.FullSnapshot, data: { node, initialOffset: getWindowScroll(window), }, - }), + }, isCheckout, ); mutationBuffers.forEach((buf) => buf.unlock()); // generate & emit any mutations that happened during snapshotting, as can now apply against the newly built mirror @@ -433,108 +420,88 @@ function record( { mutationCb: wrappedMutationEmit, mousemoveCb: (positions, source) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source, - positions, - }, - }), - ), + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source, + positions, + }, + }), mouseInteractionCb: (d) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.MouseInteraction, - ...d, - }, - }), - ), + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.MouseInteraction, + ...d, + }, + }), scrollCb: wrappedScrollEmit, viewportResizeCb: (d) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.ViewportResize, - ...d, - }, - }), - ), + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.ViewportResize, + ...d, + }, + }), inputCb: (v) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.Input, - ...v, - }, - }), - ), + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Input, + ...v, + }, + }), mediaInteractionCb: (p) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.MediaInteraction, - ...p, - }, - }), - ), + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.MediaInteraction, + ...p, + }, + }), styleSheetRuleCb: (r) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.StyleSheetRule, - ...r, - }, - }), - ), + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.StyleSheetRule, + ...r, + }, + }), styleDeclarationCb: (r) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.StyleDeclaration, - ...r, - }, - }), - ), + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.StyleDeclaration, + ...r, + }, + }), canvasMutationCb: wrappedCanvasMutationEmit, fontCb: (p) => - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.Font, - ...p, - }, - }), - ), + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Font, + ...p, + }, + }), selectionCb: (p) => { - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.Selection, - ...p, - }, - }), - ); + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Selection, + ...p, + }, + }); }, customElementCb: (c) => { - wrappedEmit( - wrapEvent({ - type: EventType.IncrementalSnapshot, - data: { - source: IncrementalSource.CustomElement, - ...c, - }, - }), - ); + wrappedEmit({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.CustomElement, + ...c, + }, + }); }, blockClass, ignoreClass, @@ -570,15 +537,13 @@ function record( observer: p.observer!, options: p.options, callback: (payload: object) => - wrappedEmit( - wrapEvent({ - type: EventType.Plugin, - data: { - plugin: p.name, - payload, - }, - }), - ), + wrappedEmit({ + type: EventType.Plugin, + data: { + plugin: p.name, + payload, + }, + }), })) || [], }, hooks, @@ -607,12 +572,10 @@ function record( } else { handlers.push( on('DOMContentLoaded', () => { - wrappedEmit( - wrapEvent({ - type: EventType.DomContentLoaded, - data: {}, - }), - ); + wrappedEmit({ + type: EventType.DomContentLoaded, + data: {}, + }); if (recordAfter === 'DOMContentLoaded') init(); }), ); @@ -620,12 +583,10 @@ function record( on( 'load', () => { - wrappedEmit( - wrapEvent({ - type: EventType.Load, - data: {}, - }), - ); + wrappedEmit({ + type: EventType.Load, + data: {}, + }); if (recordAfter === 'load') init(); }, window, @@ -648,15 +609,13 @@ record.addCustomEvent = (tag: string, payload: T) => { if (!recording) { throw new Error('please add custom event after start recording'); } - wrappedEmit( - wrapEvent({ - type: EventType.Custom, - data: { - tag, - payload, - }, - }), - ); + wrappedEmit({ + type: EventType.Custom, + data: { + tag, + payload, + }, + }); }; record.freezePage = () => { diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts index 6cd93281f9..4905ca5452 100644 --- a/packages/rrweb/test/utils.ts +++ b/packages/rrweb/test/utils.ts @@ -3,10 +3,10 @@ import { EventType, IncrementalSource, eventWithTime, + eventWithoutTime, MouseInteractions, Optional, mouseInteractionData, - event, pluginEvent, } from '@rrweb/types'; import type { recordOptions } from '../src/types'; @@ -226,7 +226,7 @@ function stringifySnapshots(snapshots: eventWithTime[]): string { } } delete (s as Optional).timestamp; - return s as event; + return s as eventWithoutTime; }), null, 2, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index c41d3e97ff..cc00c55f1a 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -163,7 +163,7 @@ export type incrementalData = | adoptedStyleSheetData | customElementData; -export type event = +export type eventWithoutTime = | domContentLoadedEvent | loadedEvent | fullSnapshotEvent @@ -172,7 +172,7 @@ export type event = | customEvent | pluginEvent; -export type eventWithTime = event & { +export type eventWithTime = eventWithoutTime & { timestamp: number; delay?: number; }; From 1071c360a1d24148c06099cfd14c4f25d9e98d66 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Thu, 11 Apr 2024 09:42:39 +0100 Subject: [PATCH 2/2] Maintain backward compatibility in case someone is importing `event` --- packages/types/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index cc00c55f1a..fc8b814332 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -172,6 +172,12 @@ export type eventWithoutTime = | customEvent | pluginEvent; +/** + * @deprecated intended for internal use + * a synonym for eventWithoutTime + */ +export type event = eventWithoutTime; + export type eventWithTime = eventWithoutTime & { timestamp: number; delay?: number;