diff --git a/.vscode/rrweb-monorepo.code-workspace b/.vscode/rrweb-monorepo.code-workspace index 1d100ed367..896a62e5d9 100644 --- a/.vscode/rrweb-monorepo.code-workspace +++ b/.vscode/rrweb-monorepo.code-workspace @@ -8,6 +8,10 @@ "name": "rrdom (package)", "path": "../packages/rrdom" }, + { + "name": "rrdom-nodejs (package)", + "path": "../packages/rrdom-nodejs" + }, { "name": "rrweb (package)", "path": "../packages/rrweb" diff --git a/package.json b/package.json index 61c9b5227d..9e6b495424 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "packages/rrweb", "packages/rrweb-snapshot", "packages/rrweb-player", - "packages/rrdom" + "packages/rrdom", + "packages/rrdom-nodejs" ], "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.25.0", diff --git a/packages/rrdom-nodejs/.gitignore b/packages/rrdom-nodejs/.gitignore new file mode 100644 index 0000000000..fb77b328d4 --- /dev/null +++ b/packages/rrdom-nodejs/.gitignore @@ -0,0 +1,4 @@ +dist +es +lib +typings diff --git a/packages/rrdom/.vscode/extensions.json b/packages/rrdom-nodejs/.vscode/extensions.json similarity index 100% rename from packages/rrdom/.vscode/extensions.json rename to packages/rrdom-nodejs/.vscode/extensions.json diff --git a/packages/rrdom/.vscode/launch.json b/packages/rrdom-nodejs/.vscode/launch.json similarity index 100% rename from packages/rrdom/.vscode/launch.json rename to packages/rrdom-nodejs/.vscode/launch.json diff --git a/packages/rrdom/.vscode/settings.json b/packages/rrdom-nodejs/.vscode/settings.json similarity index 100% rename from packages/rrdom/.vscode/settings.json rename to packages/rrdom-nodejs/.vscode/settings.json diff --git a/packages/rrdom-nodejs/jest.config.js b/packages/rrdom-nodejs/jest.config.js new file mode 100644 index 0000000000..e86e13bab9 --- /dev/null +++ b/packages/rrdom-nodejs/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; diff --git a/packages/rrdom-nodejs/package.json b/packages/rrdom-nodejs/package.json new file mode 100644 index 0000000000..331a66cf23 --- /dev/null +++ b/packages/rrdom-nodejs/package.json @@ -0,0 +1,55 @@ +{ + "name": "rrdom-nodejs", + "version": "0.1.2", + "scripts": { + "dev": "rollup -c -w", + "bundle": "rollup --config", + "bundle:es-only": "cross-env ES_ONLY=true rollup --config", + "check-types": "tsc -noEmit", + "test": "jest", + "prepublish": "npm run bundle", + "lint": "yarn eslint src/**/*.ts" + }, + "keywords": [ + "rrweb", + "rrdom-nodejs" + ], + "license": "MIT", + "main": "lib/rrdom-nodejs.js", + "module": "es/rrdom-nodejs.js", + "typings": "es", + "files": [ + "dist", + "lib", + "es", + "typings" + ], + "devDependencies": { + "@rollup/plugin-commonjs": "^20.0.0", + "@rollup/plugin-node-resolve": "^13.0.4", + "@types/cssom": "^0.4.1", + "@types/cssstyle": "^2.2.1", + "@types/jest": "^27.4.1", + "@types/nwsapi": "^2.2.2", + "@types/puppeteer": "^5.4.4", + "@typescript-eslint/eslint-plugin": "^5.23.0", + "@typescript-eslint/parser": "^5.23.0", + "compare-versions": "^4.1.3", + "eslint": "^8.15.0", + "jest": "^27.5.1", + "puppeteer": "^9.1.1", + "rollup": "^2.56.3", + "rollup-plugin-terser": "^7.0.2", + "rollup-plugin-typescript2": "^0.31.2", + "rollup-plugin-web-worker-loader": "^1.6.1", + "ts-jest": "^27.1.3", + "typescript": "^4.6.2" + }, + "dependencies": { + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "nwsapi": "^2.2.0", + "rrweb-snapshot": "^1.1.14", + "rrdom": "^0.1.2" + } +} diff --git a/packages/rrdom-nodejs/rollup.config.js b/packages/rrdom-nodejs/rollup.config.js new file mode 100644 index 0000000000..233b0f1f3f --- /dev/null +++ b/packages/rrdom-nodejs/rollup.config.js @@ -0,0 +1,84 @@ +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import { terser } from 'rollup-plugin-terser'; +import typescript from 'rollup-plugin-typescript2'; +import webWorkerLoader from 'rollup-plugin-web-worker-loader'; +import pkg from './package.json'; + +function toMinPath(path) { + return path.replace(/\.js$/, '.min.js'); +} + +const basePlugins = [ + resolve({ browser: true }), + commonjs(), + + // supports bundling `web-worker:..filename` from rrweb + webWorkerLoader(), + + typescript({ + tsconfigOverride: { compilerOptions: { module: 'ESNext' } }, + }), +]; + +const baseConfigs = [ + { + input: './src/index.ts', + name: pkg.name, + path: pkg.name, + }, + { + input: './src/document-nodejs.ts', + name: 'RRDocument', + path: 'document-nodejs', + }, +]; + +let configs = []; +let extraConfigs = []; +for (let config of baseConfigs) { + configs.push( + // ES module + { + input: config.input, + plugins: basePlugins, + output: [ + { + format: 'esm', + file: pkg.module.replace(pkg.name, config.path), + }, + ], + }, + ); + extraConfigs.push( + // CommonJS + { + input: config.input, + plugins: basePlugins, + output: [ + { + format: 'cjs', + file: pkg.main.replace(pkg.name, config.path), + }, + ], + }, + // ES module (packed) + { + input: config.input, + plugins: basePlugins.concat(terser()), + output: [ + { + format: 'esm', + file: toMinPath(pkg.module).replace(pkg.name, config.path), + sourcemap: true, + }, + ], + }, + ); +} + +if (!process.env.ES_ONLY) { + configs.push(...extraConfigs); +} + +export default configs; diff --git a/packages/rrdom/src/document-nodejs.ts b/packages/rrdom-nodejs/src/document-nodejs.ts similarity index 98% rename from packages/rrdom/src/document-nodejs.ts rename to packages/rrdom-nodejs/src/document-nodejs.ts index 3eb504fcca..249457227c 100644 --- a/packages/rrdom/src/document-nodejs.ts +++ b/packages/rrdom-nodejs/src/document-nodejs.ts @@ -13,7 +13,7 @@ import { ClassList, IRRDocument, CSSStyleDeclaration, -} from './document'; +} from 'rrdom'; const nwsapi = require('nwsapi'); const cssom = require('cssom'); const cssstyle = require('cssstyle'); @@ -53,22 +53,27 @@ export class RRDocument return this._nwsapi; } + // @ts-ignore get documentElement(): RRElement | null { return super.documentElement as RRElement | null; } + // @ts-ignore get body(): RRElement | null { return super.body as RRElement | null; } + // @ts-ignore get head() { return super.head as RRElement | null; } + // @ts-ignore get implementation(): RRDocument { return this; } + // @ts-ignore get firstElementChild(): RRElement | null { return this.documentElement; } @@ -198,6 +203,7 @@ export class RRElement extends BaseRRElementImpl(RRNode) { }); } + // @ts-ignore get style() { return (this._style as unknown) as CSSStyleDeclaration; } diff --git a/packages/rrdom-nodejs/src/index.ts b/packages/rrdom-nodejs/src/index.ts new file mode 100644 index 0000000000..ac14bf73a4 --- /dev/null +++ b/packages/rrdom-nodejs/src/index.ts @@ -0,0 +1,13 @@ +import { + polyfillPerformance, + polyfillRAF, + polyfillEvent, + polyfillNode, + polyfillDocument, +} from './polyfill'; +polyfillPerformance(); +polyfillRAF(); +polyfillEvent(); +polyfillNode(); +polyfillDocument(); +export * from './document-nodejs'; diff --git a/packages/rrdom/src/polyfill.ts b/packages/rrdom-nodejs/src/polyfill.ts similarity index 100% rename from packages/rrdom/src/polyfill.ts rename to packages/rrdom-nodejs/src/polyfill.ts diff --git a/packages/rrdom/test/document-nodejs.test.ts b/packages/rrdom-nodejs/test/document-nodejs.test.ts similarity index 99% rename from packages/rrdom/test/document-nodejs.test.ts rename to packages/rrdom-nodejs/test/document-nodejs.test.ts index f3b3c7b95b..ba3c6144d8 100644 --- a/packages/rrdom/test/document-nodejs.test.ts +++ b/packages/rrdom-nodejs/test/document-nodejs.test.ts @@ -16,7 +16,7 @@ import { RRStyleElement, RRText, } from '../src/document-nodejs'; -import { buildFromDom } from '../src/virtual-dom'; +import { buildFromDom } from 'rrdom'; describe('RRDocument for nodejs environment', () => { describe('RRDocument API', () => { @@ -542,6 +542,6 @@ describe('RRDocument for nodejs environment', () => { }); function getHtml(fileName: string) { - const filePath = path.resolve(__dirname, `./html/${fileName}`); + const filePath = path.resolve(__dirname, `../../rrdom/test/html/${fileName}`); return fs.readFileSync(filePath, 'utf8'); } diff --git a/packages/rrdom/test/polyfill.test.ts b/packages/rrdom-nodejs/test/polyfill.test.ts similarity index 100% rename from packages/rrdom/test/polyfill.test.ts rename to packages/rrdom-nodejs/test/polyfill.test.ts diff --git a/packages/rrdom-nodejs/tsconfig.json b/packages/rrdom-nodejs/tsconfig.json new file mode 100644 index 0000000000..4a4f18a080 --- /dev/null +++ b/packages/rrdom-nodejs/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "noImplicitAny": true, + "strictNullChecks": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true, + "rootDir": "src", + "outDir": "build", + "lib": ["es6", "dom"], + "skipLibCheck": true, + "declaration": true, + "importsNotUsedAsValues": "error" + }, + "compileOnSave": true, + "exclude": ["test"], + "include": ["src", "test.d.ts", "../rrweb/src/record/workers/workers.d.ts"] +} diff --git a/packages/rrdom/package.json b/packages/rrdom/package.json index 3acef09350..6bc1e1a490 100644 --- a/packages/rrdom/package.json +++ b/packages/rrdom/package.json @@ -1,19 +1,7 @@ { "name": "rrdom", "version": "0.1.2", - "scripts": { - "dev": "rollup -c -w", - "bundle": "rollup --config", - "bundle:es-only": "cross-env ES_ONLY=true rollup --config", - "check-types": "tsc -noEmit", - "test": "jest", - "prepublish": "npm run bundle", - "lint": "yarn eslint src/**/*.ts" - }, - "keywords": [ - "rrweb", - "rrdom" - ], + "homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/rrdom#readme", "license": "MIT", "main": "lib/rrdom.js", "module": "es/rrdom.js", @@ -25,17 +13,28 @@ "es", "typings" ], + "repository": { + "type": "git", + "url": "git+https://github.com/rrweb-io/rrweb.git" + }, + "scripts": { + "dev": "rollup -c -w", + "bundle": "rollup --config", + "bundle:es-only": "cross-env ES_ONLY=true rollup --config", + "check-types": "tsc -noEmit", + "test": "jest", + "prepublish": "npm run bundle", + "lint": "yarn eslint src/**/*.ts" + }, + "bugs": { + "url": "https://github.com/rrweb-io/rrweb/issues" + }, "devDependencies": { "@rollup/plugin-commonjs": "^20.0.0", - "@rollup/plugin-node-resolve": "^13.0.4", - "@types/cssom": "^0.4.1", - "@types/cssstyle": "^2.2.1", "@types/jest": "^27.4.1", - "@types/nwsapi": "^2.2.2", - "@types/puppeteer": "^5.4.4", "@typescript-eslint/eslint-plugin": "^5.23.0", "@typescript-eslint/parser": "^5.23.0", - "compare-versions": "^4.1.3", + "@types/puppeteer": "^5.4.4", "eslint": "^8.15.0", "jest": "^27.5.1", "puppeteer": "^9.1.1", @@ -43,13 +42,10 @@ "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.31.2", "rollup-plugin-web-worker-loader": "^1.6.1", - "rrweb-snapshot": "^1.1.14", "ts-jest": "^27.1.3", "typescript": "^4.6.2" }, "dependencies": { - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "nwsapi": "^2.2.0" + "rrweb-snapshot": "^1.1.14" } } diff --git a/packages/rrdom/rollup.config.js b/packages/rrdom/rollup.config.js index fe1d74a15b..5bd346673f 100644 --- a/packages/rrdom/rollup.config.js +++ b/packages/rrdom/rollup.config.js @@ -27,16 +27,6 @@ const baseConfigs = [ name: pkg.name, path: pkg.name, }, - { - input: './src/document-nodejs.ts', - name: 'RRDocument', - path: 'document-nodejs', - }, - { - input: './src/virtual-dom.ts', - name: 'RRDocument', - path: 'virtual-dom', - }, ]; let configs = []; diff --git a/packages/rrdom/src/diff.ts b/packages/rrdom/src/diff.ts index 8c493bcb7b..73850d6453 100644 --- a/packages/rrdom/src/diff.ts +++ b/packages/rrdom/src/diff.ts @@ -22,7 +22,7 @@ import type { RRStyleElement, RRDocument, Mirror, -} from './virtual-dom'; +} from '.'; const NAMESPACES: Record = { svg: 'http://www.w3.org/2000/svg', @@ -113,7 +113,7 @@ export function diff( break; } case RRNodeType.Element: { - const oldElement = (oldTree ) as HTMLElement; + const oldElement = oldTree as HTMLElement; const newRRElement = newTree as IRRElement; diffProps(oldElement, newRRElement, rrnodeMirror); scrollDataToApply = (newRRElement as RRElement).scrollData; @@ -121,7 +121,7 @@ export function diff( switch (newRRElement.tagName) { case 'AUDIO': case 'VIDEO': { - const oldMediaElement = (oldTree ) as HTMLMediaElement; + const oldMediaElement = oldTree as HTMLMediaElement; const newMediaRRElement = newRRElement as RRMediaElement; if (newMediaRRElement.paused !== undefined) newMediaRRElement.paused @@ -141,7 +141,7 @@ export function diff( replayer.applyCanvas( canvasMutation.event, canvasMutation.mutation, - (oldTree ) as HTMLCanvasElement, + oldTree as HTMLCanvasElement, ), ); break; @@ -191,8 +191,7 @@ export function diff( // IFrame element doesn't have child nodes. if (newTree.nodeName === 'IFRAME') { - const oldContentDocument = ((oldTree ) as HTMLIFrameElement) - .contentDocument; + const oldContentDocument = (oldTree as HTMLIFrameElement).contentDocument; const newIFrameElement = newTree as RRIFrameElement; // If the iframe is cross-origin, the contentDocument will be null. if (oldContentDocument) { @@ -319,11 +318,9 @@ function diffChildren( if ( replayer.mirror.getMeta(parentNode)?.type === RRNodeType.Document && replayer.mirror.getMeta(newNode)?.type === RRNodeType.Element && - ((parentNode ) as Document).documentElement + (parentNode as Document).documentElement ) { - parentNode.removeChild( - ((parentNode ) as Document).documentElement, - ); + parentNode.removeChild((parentNode as Document).documentElement); oldChildren[oldStartIndex] = undefined; oldStartNode = undefined; } @@ -417,8 +414,7 @@ export function getNestedRule( return rule; } else { return getNestedRule( - ((rule ).cssRules[position[1]] as CSSGroupingRule) - .cssRules, + (rule.cssRules[position[1]] as CSSGroupingRule).cssRules, position.slice(2), ); } diff --git a/packages/rrdom/src/index.ts b/packages/rrdom/src/index.ts index ac14bf73a4..c81f6af698 100644 --- a/packages/rrdom/src/index.ts +++ b/packages/rrdom/src/index.ts @@ -1,13 +1,450 @@ import { - polyfillPerformance, - polyfillRAF, - polyfillEvent, - polyfillNode, - polyfillDocument, -} from './polyfill'; -polyfillPerformance(); -polyfillRAF(); -polyfillEvent(); -polyfillNode(); -polyfillDocument(); -export * from './document-nodejs'; + NodeType as RRNodeType, + createMirror as createNodeMirror, +} from 'rrweb-snapshot'; +import type { + Mirror as NodeMirror, + IMirror, + serializedNodeWithId, +} from 'rrweb-snapshot'; +import type { + canvasMutationData, + canvasEventWithTime, + inputData, + scrollData, +} from 'rrweb/src/types'; +import type { VirtualStyleRules } from './diff'; +import { + BaseRRNode as RRNode, + BaseRRCDATASectionImpl, + BaseRRCommentImpl, + BaseRRDocumentImpl, + BaseRRDocumentTypeImpl, + BaseRRElementImpl, + BaseRRMediaElementImpl, + BaseRRTextImpl, + IRRDocument, + IRRElement, + IRRNode, + NodeType, + IRRDocumentType, + IRRText, + IRRComment, +} from './document'; + +export class RRDocument extends BaseRRDocumentImpl(RRNode) { + // In the rrweb replayer, there are some unserialized nodes like the element that stores the injected style rules. + // These unserialized nodes may interfere the execution of the diff algorithm. + // The id of serialized node is larger than 0. So this value ​​less than 0 is used as id for these unserialized nodes. + private _unserializedId = -1; + + /** + * Every time the id is used, it will minus 1 automatically to avoid collisions. + */ + public get unserializedId(): number { + return this._unserializedId--; + } + + public mirror: Mirror = createMirror(); + + public scrollData: scrollData | null = null; + + constructor(mirror?: Mirror) { + super(); + if (mirror) { + this.mirror = mirror; + } + } + + createDocument( + _namespace: string | null, + _qualifiedName: string | null, + _doctype?: DocumentType | null, + ) { + return new RRDocument(); + } + + createDocumentType( + qualifiedName: string, + publicId: string, + systemId: string, + ) { + const documentTypeNode = new RRDocumentType( + qualifiedName, + publicId, + systemId, + ); + documentTypeNode.ownerDocument = this; + return documentTypeNode; + } + + createElement( + tagName: K, + ): RRElementType; + createElement(tagName: string): RRElement; + createElement(tagName: string) { + const upperTagName = tagName.toUpperCase(); + let element; + switch (upperTagName) { + case 'AUDIO': + case 'VIDEO': + element = new RRMediaElement(upperTagName); + break; + case 'IFRAME': + element = new RRIFrameElement(upperTagName, this.mirror); + break; + case 'CANVAS': + element = new RRCanvasElement(upperTagName); + break; + case 'STYLE': + element = new RRStyleElement(upperTagName); + break; + default: + element = new RRElement(upperTagName); + break; + } + element.ownerDocument = this; + return element; + } + + createComment(data: string) { + const commentNode = new RRComment(data); + commentNode.ownerDocument = this; + return commentNode; + } + + createCDATASection(data: string) { + const sectionNode = new RRCDATASection(data); + sectionNode.ownerDocument = this; + return sectionNode; + } + + createTextNode(data: string) { + const textNode = new RRText(data); + textNode.ownerDocument = this; + return textNode; + } + + destroyTree() { + this.childNodes = []; + this.mirror.reset(); + } + + open() { + super.open(); + this._unserializedId = -1; + } +} + +export const RRDocumentType = BaseRRDocumentTypeImpl(RRNode); + +export class RRElement extends BaseRRElementImpl(RRNode) { + inputData: inputData | null = null; + scrollData: scrollData | null = null; +} + +export class RRMediaElement extends BaseRRMediaElementImpl(RRElement) {} + +export class RRCanvasElement extends RRElement implements IRRElement { + public canvasMutations: { + event: canvasEventWithTime; + mutation: canvasMutationData; + }[] = []; + /** + * This is a dummy implementation to distinguish RRCanvasElement from real HTMLCanvasElement. + */ + getContext(): RenderingContext | null { + return null; + } +} + +export class RRStyleElement extends RRElement { + public rules: VirtualStyleRules = []; +} + +export class RRIFrameElement extends RRElement { + contentDocument: RRDocument = new RRDocument(); + constructor(upperTagName: string, mirror: Mirror) { + super(upperTagName); + this.contentDocument.mirror = mirror; + } +} + +export const RRText = BaseRRTextImpl(RRNode); +export type RRText = typeof RRText; + +export const RRComment = BaseRRCommentImpl(RRNode); +export type RRComment = typeof RRComment; + +export const RRCDATASection = BaseRRCDATASectionImpl(RRNode); +export type RRCDATASection = typeof RRCDATASection; + +interface RRElementTagNameMap { + audio: RRMediaElement; + canvas: RRCanvasElement; + iframe: RRIFrameElement; + style: RRStyleElement; + video: RRMediaElement; +} + +type RRElementType< + K extends keyof HTMLElementTagNameMap +> = K extends keyof RRElementTagNameMap ? RRElementTagNameMap[K] : RRElement; + +function getValidTagName(element: HTMLElement): string { + // https://github.com/rrweb-io/rrweb-snapshot/issues/56 + if (element instanceof HTMLFormElement) { + return 'FORM'; + } + return element.tagName.toUpperCase(); +} + +/** + * Build a RRNode from a real Node. + * @param node the real Node + * @param rrdom the RRDocument + * @param domMirror the NodeMirror that records the real document tree + * @returns the built RRNode + */ +export function buildFromNode( + node: Node, + rrdom: IRRDocument, + domMirror: NodeMirror, + parentRRNode?: IRRNode | null, +): IRRNode | null { + let rrNode: IRRNode; + + switch (node.nodeType) { + case NodeType.DOCUMENT_NODE: + if (parentRRNode && parentRRNode.nodeName === 'IFRAME') + rrNode = (parentRRNode as RRIFrameElement).contentDocument; + else { + rrNode = rrdom; + (rrNode as IRRDocument).compatMode = (node as Document).compatMode as + | 'BackCompat' + | 'CSS1Compat'; + } + break; + case NodeType.DOCUMENT_TYPE_NODE: + const documentType = node as DocumentType; + rrNode = rrdom.createDocumentType( + documentType.name, + documentType.publicId, + documentType.systemId, + ); + break; + case NodeType.ELEMENT_NODE: + const elementNode = node as HTMLElement; + const tagName = getValidTagName(elementNode); + rrNode = rrdom.createElement(tagName); + const rrElement = rrNode as IRRElement; + for (const { name, value } of Array.from(elementNode.attributes)) { + rrElement.attributes[name] = value; + } + elementNode.scrollLeft && (rrElement.scrollLeft = elementNode.scrollLeft); + elementNode.scrollTop && (rrElement.scrollTop = elementNode.scrollTop); + /** + * We don't have to record special values of input elements at the beginning. + * Because if these values are changed later, the mutation will be applied through the batched input events on its RRElement after the diff algorithm is executed. + */ + break; + case NodeType.TEXT_NODE: + rrNode = rrdom.createTextNode((node as Text).textContent || ''); + break; + case NodeType.CDATA_SECTION_NODE: + rrNode = rrdom.createCDATASection((node as CDATASection).data); + break; + case NodeType.COMMENT_NODE: + rrNode = rrdom.createComment((node as Comment).textContent || ''); + break; + // if node is a shadow root + case NodeType.DOCUMENT_FRAGMENT_NODE: + rrNode = (parentRRNode as IRRElement).attachShadow({ mode: 'open' }); + break; + default: + return null; + } + + let sn: serializedNodeWithId | null = domMirror.getMeta(node); + + if (rrdom instanceof RRDocument) { + if (!sn) { + sn = getDefaultSN(rrNode, rrdom.unserializedId); + domMirror.add(node, sn); + } + rrdom.mirror.add(rrNode, { ...sn }); + } + + return rrNode; +} + +/** + * Build a RRDocument from a real document tree. + * @param dom the real document tree + * @param domMirror the NodeMirror that records the real document tree + * @param rrdom the rrdom object to be constructed + * @returns the build rrdom + */ +export function buildFromDom( + dom: Document, + domMirror: NodeMirror = createNodeMirror(), + rrdom: IRRDocument = new RRDocument(), +) { + function walk(node: Node, parentRRNode: IRRNode | null) { + const rrNode = buildFromNode(node, rrdom, domMirror, parentRRNode); + if (rrNode === null) return; + if ( + // if the parentRRNode isn't a RRIFrameElement + parentRRNode?.nodeName !== 'IFRAME' && + // if node isn't a shadow root + node.nodeType !== NodeType.DOCUMENT_FRAGMENT_NODE + ) { + parentRRNode?.appendChild(rrNode); + rrNode.parentNode = parentRRNode; + rrNode.parentElement = parentRRNode as RRElement; + } + + if (node.nodeName === 'IFRAME') { + walk((node as HTMLIFrameElement).contentDocument!, rrNode); + } else if ( + node.nodeType === NodeType.DOCUMENT_NODE || + node.nodeType === NodeType.ELEMENT_NODE || + node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE + ) { + // if the node is a shadow dom + if ( + node.nodeType === NodeType.ELEMENT_NODE && + (node as HTMLElement).shadowRoot + ) + walk((node as HTMLElement).shadowRoot!, rrNode); + node.childNodes.forEach((childNode) => walk(childNode, rrNode)); + } + } + walk(dom, null); + return rrdom; +} + +export function createMirror(): Mirror { + return new Mirror(); +} + +// based on Mirror from rrweb-snapshots +export class Mirror implements IMirror { + private idNodeMap: Map = new Map(); + private nodeMetaMap: WeakMap = new WeakMap(); + + getId(n: RRNode | undefined | null): number { + if (!n) return -1; + + const id = this.getMeta(n)?.id; + + // if n is not a serialized Node, use -1 as its id. + return id ?? -1; + } + + getNode(id: number): RRNode | null { + return this.idNodeMap.get(id) || null; + } + + getIds(): number[] { + return Array.from(this.idNodeMap.keys()); + } + + getMeta(n: RRNode): serializedNodeWithId | null { + return this.nodeMetaMap.get(n) || null; + } + + // removes the node from idNodeMap + // doesn't remove the node from nodeMetaMap + removeNodeFromMap(n: RRNode) { + const id = this.getId(n); + this.idNodeMap.delete(id); + + if (n.childNodes) { + n.childNodes.forEach((childNode) => this.removeNodeFromMap(childNode)); + } + } + has(id: number): boolean { + return this.idNodeMap.has(id); + } + + hasNode(node: RRNode): boolean { + return this.nodeMetaMap.has(node); + } + + add(n: RRNode, meta: serializedNodeWithId) { + const id = meta.id; + this.idNodeMap.set(id, n); + this.nodeMetaMap.set(n, meta); + } + + replace(id: number, n: RRNode) { + this.idNodeMap.set(id, n); + } + + reset() { + this.idNodeMap = new Map(); + this.nodeMetaMap = new WeakMap(); + } +} + +/** + * Get a default serializedNodeWithId value for a RRNode. + * @param id the serialized id to assign + */ +export function getDefaultSN(node: IRRNode, id: number): serializedNodeWithId { + switch (node.RRNodeType) { + case RRNodeType.Document: + return { + id, + type: node.RRNodeType, + childNodes: [], + }; + case RRNodeType.DocumentType: + const doctype = node as IRRDocumentType; + return { + id, + type: node.RRNodeType, + name: doctype.name, + publicId: doctype.publicId, + systemId: doctype.systemId, + }; + case RRNodeType.Element: + return { + id, + type: node.RRNodeType, + tagName: (node as IRRElement).tagName.toLowerCase(), // In rrweb data, all tagNames are lowercase. + attributes: {}, + childNodes: [], + }; + case RRNodeType.Text: + return { + id, + type: node.RRNodeType, + textContent: (node as IRRText).textContent || '', + }; + case RRNodeType.Comment: + return { + id, + type: node.RRNodeType, + textContent: (node as IRRComment).textContent || '', + }; + case RRNodeType.CDATA: + return { + id, + type: node.RRNodeType, + textContent: '', + }; + } +} + +export { RRNode }; + +export { + diff, + createOrGetNode, + StyleRuleType, + ReplayerHandler, + VirtualStyleRules, +} from './diff'; +export * from './document'; diff --git a/packages/rrdom/src/virtual-dom.ts b/packages/rrdom/src/virtual-dom.ts deleted file mode 100644 index 78f1dad05e..0000000000 --- a/packages/rrdom/src/virtual-dom.ts +++ /dev/null @@ -1,450 +0,0 @@ -import { - NodeType as RRNodeType, - createMirror as createNodeMirror, -} from 'rrweb-snapshot'; -import type { - Mirror as NodeMirror, - IMirror, - serializedNodeWithId, -} from 'rrweb-snapshot'; -import type { - canvasMutationData, - canvasEventWithTime, - inputData, - scrollData, -} from 'rrweb/src/types'; -import { - BaseRRNode as RRNode, - BaseRRCDATASectionImpl, - BaseRRCommentImpl, - BaseRRDocumentImpl, - BaseRRDocumentTypeImpl, - BaseRRElementImpl, - BaseRRMediaElementImpl, - BaseRRTextImpl, - IRRDocument, - IRRElement, - IRRNode, - NodeType, - IRRDocumentType, - IRRText, - IRRComment, -} from './document'; -import type { VirtualStyleRules } from './diff'; - -export class RRDocument extends BaseRRDocumentImpl(RRNode) { - // In the rrweb replayer, there are some unserialized nodes like the element that stores the injected style rules. - // These unserialized nodes may interfere the execution of the diff algorithm. - // The id of serialized node is larger than 0. So this value ​​less than 0 is used as id for these unserialized nodes. - private _unserializedId = -1; - - /** - * Every time the id is used, it will minus 1 automatically to avoid collisions. - */ - public get unserializedId(): number { - return this._unserializedId--; - } - - public mirror: Mirror = createMirror(); - - public scrollData: scrollData | null = null; - - constructor(mirror?: Mirror) { - super(); - if (mirror) { - this.mirror = mirror; - } - } - - createDocument( - _namespace: string | null, - _qualifiedName: string | null, - _doctype?: DocumentType | null, - ) { - return new RRDocument(); - } - - createDocumentType( - qualifiedName: string, - publicId: string, - systemId: string, - ) { - const documentTypeNode = new RRDocumentType( - qualifiedName, - publicId, - systemId, - ); - documentTypeNode.ownerDocument = this; - return documentTypeNode; - } - - createElement( - tagName: K, - ): RRElementType; - createElement(tagName: string): RRElement; - createElement(tagName: string) { - const upperTagName = tagName.toUpperCase(); - let element; - switch (upperTagName) { - case 'AUDIO': - case 'VIDEO': - element = new RRMediaElement(upperTagName); - break; - case 'IFRAME': - element = new RRIFrameElement(upperTagName, this.mirror); - break; - case 'CANVAS': - element = new RRCanvasElement(upperTagName); - break; - case 'STYLE': - element = new RRStyleElement(upperTagName); - break; - default: - element = new RRElement(upperTagName); - break; - } - element.ownerDocument = this; - return element; - } - - createComment(data: string) { - const commentNode = new RRComment(data); - commentNode.ownerDocument = this; - return commentNode; - } - - createCDATASection(data: string) { - const sectionNode = new RRCDATASection(data); - sectionNode.ownerDocument = this; - return sectionNode; - } - - createTextNode(data: string) { - const textNode = new RRText(data); - textNode.ownerDocument = this; - return textNode; - } - - destroyTree() { - this.childNodes = []; - this.mirror.reset(); - } - - open() { - super.open(); - this._unserializedId = -1; - } -} - -export const RRDocumentType = BaseRRDocumentTypeImpl(RRNode); - -export class RRElement extends BaseRRElementImpl(RRNode) { - inputData: inputData | null = null; - scrollData: scrollData | null = null; -} - -export class RRMediaElement extends BaseRRMediaElementImpl(RRElement) {} - -export class RRCanvasElement extends RRElement implements IRRElement { - public canvasMutations: { - event: canvasEventWithTime; - mutation: canvasMutationData; - }[] = []; - /** - * This is a dummy implementation to distinguish RRCanvasElement from real HTMLCanvasElement. - */ - getContext(): RenderingContext | null { - return null; - } -} - -export class RRStyleElement extends RRElement { - public rules: VirtualStyleRules = []; -} - -export class RRIFrameElement extends RRElement { - contentDocument: RRDocument = new RRDocument(); - constructor(upperTagName: string, mirror: Mirror) { - super(upperTagName); - this.contentDocument.mirror = mirror; - } -} - -export const RRText = BaseRRTextImpl(RRNode); -export type RRText = typeof RRText; - -export const RRComment = BaseRRCommentImpl(RRNode); -export type RRComment = typeof RRComment; - -export const RRCDATASection = BaseRRCDATASectionImpl(RRNode); -export type RRCDATASection = typeof RRCDATASection; - -interface RRElementTagNameMap { - audio: RRMediaElement; - canvas: RRCanvasElement; - iframe: RRIFrameElement; - style: RRStyleElement; - video: RRMediaElement; -} - -type RRElementType< - K extends keyof HTMLElementTagNameMap -> = K extends keyof RRElementTagNameMap ? RRElementTagNameMap[K] : RRElement; - -function getValidTagName(element: HTMLElement): string { - // https://github.com/rrweb-io/rrweb-snapshot/issues/56 - if (element instanceof HTMLFormElement) { - return 'FORM'; - } - return element.tagName.toUpperCase(); -} - -/** - * Build a RRNode from a real Node. - * @param node the real Node - * @param rrdom the RRDocument - * @param domMirror the NodeMirror that records the real document tree - * @returns the built RRNode - */ -export function buildFromNode( - node: Node, - rrdom: IRRDocument, - domMirror: NodeMirror, - parentRRNode?: IRRNode | null, -): IRRNode | null { - let rrNode: IRRNode; - - switch (node.nodeType) { - case NodeType.DOCUMENT_NODE: - if (parentRRNode && parentRRNode.nodeName === 'IFRAME') - rrNode = (parentRRNode as RRIFrameElement).contentDocument; - else { - rrNode = rrdom; - (rrNode as IRRDocument).compatMode = (node as Document).compatMode as - | 'BackCompat' - | 'CSS1Compat'; - } - break; - case NodeType.DOCUMENT_TYPE_NODE: - const documentType = (node ) as DocumentType; - rrNode = rrdom.createDocumentType( - documentType.name, - documentType.publicId, - documentType.systemId, - ); - break; - case NodeType.ELEMENT_NODE: - const elementNode = (node ) as HTMLElement; - const tagName = getValidTagName(elementNode); - rrNode = rrdom.createElement(tagName); - const rrElement = rrNode as IRRElement; - for (const { name, value } of Array.from(elementNode.attributes)) { - rrElement.attributes[name] = value; - } - elementNode.scrollLeft && (rrElement.scrollLeft = elementNode.scrollLeft); - elementNode.scrollTop && (rrElement.scrollTop = elementNode.scrollTop); - /** - * We don't have to record special values of input elements at the beginning. - * Because if these values are changed later, the mutation will be applied through the batched input events on its RRElement after the diff algorithm is executed. - */ - break; - case NodeType.TEXT_NODE: - rrNode = rrdom.createTextNode(((node ) as Text).textContent || ''); - break; - case NodeType.CDATA_SECTION_NODE: - rrNode = rrdom.createCDATASection(((node ) as CDATASection).data); - break; - case NodeType.COMMENT_NODE: - rrNode = rrdom.createComment( - ((node ) as Comment).textContent || '', - ); - break; - // if node is a shadow root - case NodeType.DOCUMENT_FRAGMENT_NODE: - rrNode = (parentRRNode as IRRElement).attachShadow({ mode: 'open' }); - break; - default: - return null; - } - - let sn: serializedNodeWithId | null = domMirror.getMeta(node); - - if (rrdom instanceof RRDocument) { - if (!sn) { - sn = getDefaultSN(rrNode, rrdom.unserializedId); - domMirror.add(node, sn); - } - rrdom.mirror.add(rrNode, { ...sn }); - } - - return rrNode; -} - -/** - * Build a RRDocument from a real document tree. - * @param dom the real document tree - * @param domMirror the NodeMirror that records the real document tree - * @param rrdom the rrdom object to be constructed - * @returns the build rrdom - */ -export function buildFromDom( - dom: Document, - domMirror: NodeMirror = createNodeMirror(), - rrdom: IRRDocument = new RRDocument(), -) { - function walk(node: Node, parentRRNode: IRRNode | null) { - const rrNode = buildFromNode(node, rrdom, domMirror, parentRRNode); - if (rrNode === null) return; - if ( - // if the parentRRNode isn't a RRIFrameElement - parentRRNode?.nodeName !== 'IFRAME' && - // if node isn't a shadow root - node.nodeType !== NodeType.DOCUMENT_FRAGMENT_NODE - ) { - parentRRNode?.appendChild(rrNode); - rrNode.parentNode = parentRRNode; - rrNode.parentElement = parentRRNode as RRElement; - } - - if (node.nodeName === 'IFRAME') { - walk((node as HTMLIFrameElement).contentDocument!, rrNode); - } else if ( - node.nodeType === NodeType.DOCUMENT_NODE || - node.nodeType === NodeType.ELEMENT_NODE || - node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE - ) { - // if the node is a shadow dom - if ( - node.nodeType === NodeType.ELEMENT_NODE && - ((node ) as HTMLElement).shadowRoot - ) - walk(((node ) as HTMLElement).shadowRoot!, rrNode); - node.childNodes.forEach((childNode) => walk(childNode, rrNode)); - } - } - walk(dom, null); - return rrdom; -} - -export function createMirror(): Mirror { - return new Mirror(); -} - -// based on Mirror from rrweb-snapshots -export class Mirror implements IMirror { - private idNodeMap: Map = new Map(); - private nodeMetaMap: WeakMap = new WeakMap(); - - getId(n: RRNode | undefined | null): number { - if (!n) return -1; - - const id = this.getMeta(n)?.id; - - // if n is not a serialized Node, use -1 as its id. - return id ?? -1; - } - - getNode(id: number): RRNode | null { - return this.idNodeMap.get(id) || null; - } - - getIds(): number[] { - return Array.from(this.idNodeMap.keys()); - } - - getMeta(n: RRNode): serializedNodeWithId | null { - return this.nodeMetaMap.get(n) || null; - } - - // removes the node from idNodeMap - // doesn't remove the node from nodeMetaMap - removeNodeFromMap(n: RRNode) { - const id = this.getId(n); - this.idNodeMap.delete(id); - - if (n.childNodes) { - n.childNodes.forEach((childNode) => this.removeNodeFromMap(childNode)); - } - } - has(id: number): boolean { - return this.idNodeMap.has(id); - } - - hasNode(node: RRNode): boolean { - return this.nodeMetaMap.has(node); - } - - add(n: RRNode, meta: serializedNodeWithId) { - const id = meta.id; - this.idNodeMap.set(id, n); - this.nodeMetaMap.set(n, meta); - } - - replace(id: number, n: RRNode) { - this.idNodeMap.set(id, n); - } - - reset() { - this.idNodeMap = new Map(); - this.nodeMetaMap = new WeakMap(); - } -} - -/** - * Get a default serializedNodeWithId value for a RRNode. - * @param id the serialized id to assign - */ -export function getDefaultSN(node: IRRNode, id: number): serializedNodeWithId { - switch (node.RRNodeType) { - case RRNodeType.Document: - return { - id, - type: node.RRNodeType, - childNodes: [], - }; - case RRNodeType.DocumentType: - const doctype = node as IRRDocumentType; - return { - id, - type: node.RRNodeType, - name: doctype.name, - publicId: doctype.publicId, - systemId: doctype.systemId, - }; - case RRNodeType.Element: - return { - id, - type: node.RRNodeType, - tagName: (node as IRRElement).tagName.toLowerCase(), // In rrweb data, all tagNames are lowercase. - attributes: {}, - childNodes: [], - }; - case RRNodeType.Text: - return { - id, - type: node.RRNodeType, - textContent: (node as IRRText).textContent || '', - }; - case RRNodeType.Comment: - return { - id, - type: node.RRNodeType, - textContent: (node as IRRComment).textContent || '', - }; - case RRNodeType.CDATA: - return { - id, - type: node.RRNodeType, - textContent: '', - }; - } -} - -export { RRNode }; -export { - diff, - createOrGetNode, - StyleRuleType, - VirtualStyleRules, - ReplayerHandler, -} from './diff'; diff --git a/packages/rrdom/test/diff.test.ts b/packages/rrdom/test/diff.test.ts index 22feace950..bb4544e888 100644 --- a/packages/rrdom/test/diff.test.ts +++ b/packages/rrdom/test/diff.test.ts @@ -1,7 +1,7 @@ /** * @jest-environment jsdom */ -import { getDefaultSN, RRDocument, RRMediaElement } from '../src/virtual-dom'; +import { getDefaultSN, RRDocument, RRMediaElement } from '../src'; import { applyVirtualStyleRulesToNode, createOrGetNode, diff --git a/packages/rrdom/test/virtual-dom.test.ts b/packages/rrdom/test/virtual-dom.test.ts index 4bded073b2..b99a34325e 100644 --- a/packages/rrdom/test/virtual-dom.test.ts +++ b/packages/rrdom/test/virtual-dom.test.ts @@ -27,8 +27,8 @@ import { RRCanvasElement, RRDocument, RRElement, - RRNode, -} from '../src/virtual-dom'; + BaseRRNode as RRNode, +} from '../src'; const _typescript = (typescript as unknown) as typeof typescript.default; const printRRDomCode = ` @@ -219,9 +219,9 @@ describe('RRDocument for browser environment', () => { beforeAll(async () => { browser = await puppeteer.launch(); const bundle = await rollup.rollup({ - input: path.resolve(__dirname, '../src/virtual-dom.ts'), + input: path.resolve(__dirname, '../src/index.ts'), plugins: [ - resolve(), + (resolve() as unknown) as rollup.Plugin, (_typescript({ tsconfigOverride: { compilerOptions: { module: 'ESNext' } }, }) as unknown) as rollup.Plugin, diff --git a/packages/rrdom/tsconfig.json b/packages/rrdom/tsconfig.json index 4a4f18a080..c5d366adf8 100644 --- a/packages/rrdom/tsconfig.json +++ b/packages/rrdom/tsconfig.json @@ -16,5 +16,5 @@ }, "compileOnSave": true, "exclude": ["test"], - "include": ["src", "test.d.ts", "../rrweb/src/record/workers/workers.d.ts"] + "include": ["src", "../rrweb/src/record/workers/workers.d.ts"] } diff --git a/packages/rrweb/jest.config.js b/packages/rrweb/jest.config.js index c1f271c9b7..29db4e7fa0 100644 --- a/packages/rrweb/jest.config.js +++ b/packages/rrweb/jest.config.js @@ -5,6 +5,5 @@ module.exports = { testMatch: ['**/**.test.ts'], moduleNameMapper: { '\\.css$': 'identity-obj-proxy', - 'rrdom/es/(.*)': 'rrdom/lib/$1', }, }; diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts index e83b82c2e7..99d64b99ca 100644 --- a/packages/rrweb/src/replay/index.ts +++ b/packages/rrweb/src/replay/index.ts @@ -15,7 +15,7 @@ import { buildFromDom, diff, getDefaultSN, -} from 'rrdom/es/virtual-dom'; +} from 'rrdom'; import type { RRNode, RRElement, @@ -26,7 +26,7 @@ import type { ReplayerHandler, Mirror as RRDOMMirror, VirtualStyleRules, -} from 'rrdom/es/virtual-dom'; +} from 'rrdom'; import * as mittProxy from 'mitt'; import { polyfill as smoothscrollPolyfill } from './smoothscroll'; import { Timer } from './timer'; @@ -731,7 +731,7 @@ export class Replayer { ); } if (this.usingVirtualDom) { - const styleEl = this.virtualDom.createElement('style') ; + const styleEl = this.virtualDom.createElement('style'); this.virtualDom.mirror.add( styleEl, getDefaultSN(styleEl, this.virtualDom.unserializedId), @@ -752,10 +752,7 @@ export class Replayer { head as HTMLHeadElement, ); for (let idx = 0; idx < injectStylesRules.length; idx++) { - (styleEl.sheet! ).insertRule( - injectStylesRules[idx], - idx, - ); + styleEl.sheet!.insertRule(injectStylesRules[idx], idx); } } } @@ -1210,7 +1207,7 @@ export class Replayer { if (!target) { return this.debugNodeNotFound(d, d.id); } - const styleSheet = ((target ) as HTMLStyleElement).sheet!; + const styleSheet = (target as HTMLStyleElement).sheet!; d.adds?.forEach(({ rule, index: nestedIndex }) => { try { if (Array.isArray(nestedIndex)) { @@ -1692,7 +1689,7 @@ export class Replayer { } } } else if (attributeName === 'style') { - const styleValues = value ; + const styleValues = value; const targetEl = target as HTMLElement | RRElement; for (const s in styleValues) { if (styleValues[s] === false) { @@ -1772,7 +1769,7 @@ export class Replayer { const previousInMap = previousId && map[previousId]; const nextInMap = nextId && map[nextId]; if (previousInMap) { - const { node, mutation } = previousInMap ; + const { node, mutation } = previousInMap; parent.insertBefore(node as Node & RRNode, target as Node & RRNode); delete map[mutation.node.id]; delete this.legacy_missingNodeRetryMap[mutation.node.id]; @@ -1781,7 +1778,7 @@ export class Replayer { } } if (nextInMap) { - const { node, mutation } = nextInMap ; + const { node, mutation } = nextInMap; parent.insertBefore( node as Node & RRNode, target.nextSibling as Node & RRNode, diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts index a74a091c75..63a10943dd 100644 --- a/packages/rrweb/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -11,7 +11,7 @@ import type { PackFn, UnpackFn } from './packer/base'; import type { IframeManager } from './record/iframe-manager'; import type { ShadowDomManager } from './record/shadow-dom-manager'; import type { Replayer } from './replay'; -import type { RRNode } from 'rrdom/es/virtual-dom'; +import type { RRNode } from 'rrdom'; import type { CanvasManager } from './record/observers/canvas/canvas-manager'; export enum EventType { diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts index ea8cd1f1b8..81108b58b5 100644 --- a/packages/rrweb/src/utils.ts +++ b/packages/rrweb/src/utils.ts @@ -11,7 +11,7 @@ import type { } from './types'; import type { IMirror, Mirror } from 'rrweb-snapshot'; import { isShadowRoot, IGNORED_NODE, classMatchesRegex } from 'rrweb-snapshot'; -import type { RRNode, RRIFrameElement } from 'rrdom/es/virtual-dom'; +import type { RRNode, RRIFrameElement } from 'rrdom'; export function on( type: string, diff --git a/packages/rrweb/typings/replay/index.d.ts b/packages/rrweb/typings/replay/index.d.ts index 46b84e58c4..68dbc5bc8c 100644 --- a/packages/rrweb/typings/replay/index.d.ts +++ b/packages/rrweb/typings/replay/index.d.ts @@ -1,5 +1,5 @@ import { Mirror } from 'rrweb-snapshot'; -import { RRDocument } from 'rrdom/es/virtual-dom'; +import { RRDocument } from 'rrdom'; import { Timer } from './timer'; import { createPlayerService, createSpeedService } from './machine'; import { eventWithTime, playerConfig, playerMetaData, Handler } from '../types'; diff --git a/packages/rrweb/typings/types.d.ts b/packages/rrweb/typings/types.d.ts index 61f3d8b84c..50a7109a75 100644 --- a/packages/rrweb/typings/types.d.ts +++ b/packages/rrweb/typings/types.d.ts @@ -3,7 +3,7 @@ import type { PackFn, UnpackFn } from './packer/base'; import type { IframeManager } from './record/iframe-manager'; import type { ShadowDomManager } from './record/shadow-dom-manager'; import type { Replayer } from './replay'; -import type { RRNode } from 'rrdom/es/virtual-dom'; +import type { RRNode } from 'rrdom'; import type { CanvasManager } from './record/observers/canvas/canvas-manager'; export declare enum EventType { DomContentLoaded = 0, diff --git a/packages/rrweb/typings/utils.d.ts b/packages/rrweb/typings/utils.d.ts index 9491527c8e..0fabf6f199 100644 --- a/packages/rrweb/typings/utils.d.ts +++ b/packages/rrweb/typings/utils.d.ts @@ -1,6 +1,6 @@ import type { throttleOptions, listenerHandler, hookResetter, blockClass, addedNodeMutation, DocumentDimension, IWindow, DeprecatedMirror, textMutation } from './types'; import type { IMirror, Mirror } from 'rrweb-snapshot'; -import type { RRNode, RRIFrameElement } from 'rrdom/es/virtual-dom'; +import type { RRNode, RRIFrameElement } from 'rrdom'; export declare function on(type: string, fn: EventListenerOrEventListenerObject, target?: Document | IWindow): listenerHandler; export declare let _mirror: DeprecatedMirror; export declare function throttle(func: (arg: T) => void, wait: number, options?: throttleOptions): (arg: T) => void; diff --git a/yarn.lock b/yarn.lock index 5f85e0dff5..9f82432e29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1791,10 +1791,10 @@ magic-string "^0.25.7" resolve "^1.17.0" -"@rollup/plugin-node-resolve@^13.0.4": - version "13.0.6" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.6.tgz" - integrity sha512-sFsPDMPd4gMqnh2gS0uIxELnoRUp5kBl5knxD2EO0778G1oOJv4G1vyT2cpWz75OU2jDVcXhjVUuTAczGyFNKA== +"@rollup/plugin-node-resolve@^13.0.4", "@rollup/plugin-node-resolve@^13.2.1": + version "13.2.1" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.2.1.tgz" + integrity sha512-btX7kzGvp1JwShQI9V6IM841YKNPYjKCvUbNrQ2EcVYbULtUd/GH6wZ/qdqH13j9pOHBER+EZXNN2L8RSJhVRA== dependencies: "@rollup/pluginutils" "^3.1.0" "@types/resolve" "1.17.1" @@ -1815,18 +1815,6 @@ is-module "^1.0.0" resolve "^1.19.0" -"@rollup/plugin-node-resolve@^13.2.1": - version "13.2.1" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.2.1.tgz" - integrity sha512-btX7kzGvp1JwShQI9V6IM841YKNPYjKCvUbNrQ2EcVYbULtUd/GH6wZ/qdqH13j9pOHBER+EZXNN2L8RSJhVRA== - dependencies: - "@rollup/pluginutils" "^3.1.0" - "@types/resolve" "1.17.1" - builtin-modules "^3.1.0" - deepmerge "^4.2.2" - is-module "^1.0.0" - resolve "^1.19.0" - "@rollup/plugin-typescript@^8.2.5": version "8.2.5" resolved "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz" @@ -1965,12 +1953,12 @@ "@types/cssom@^0.4.1": version "0.4.1" - resolved "https://registry.npmjs.org/@types/cssom/-/cssom-0.4.1.tgz" + resolved "https://registry.npmjs.org/@types/cssom/-/cssom-0.4.1.tgz#fb64e145b425bd6c1b0ed78ebd66ba43b6e088ab" integrity sha512-hHGVfUuGZe5FpgCxpTJccH0gD1bui5gWceW0We0TyAzUr6wBaqDnSLG9Yr3xqS4AkGhnclNOwRSXH/LIfki3fQ== "@types/cssstyle@^2.2.1": version "2.2.1" - resolved "https://registry.npmjs.org/@types/cssstyle/-/cssstyle-2.2.1.tgz" + resolved "https://registry.npmjs.org/@types/cssstyle/-/cssstyle-2.2.1.tgz#fa010824006ff47af94a6b9baf9759e031815347" integrity sha512-CSQFKdZc3dmWoZXLAM0pPL6XiYLG8hMGzImM2MwQ9kavB5LnbeMGan94CCj4oxY65xMl5mRMwrFUfKPOWO4WpQ== "@types/estree@*": @@ -2088,7 +2076,7 @@ "@types/nwsapi@^2.2.2": version "2.2.2" - resolved "https://registry.npmjs.org/@types/nwsapi/-/nwsapi-2.2.2.tgz" + resolved "https://registry.npmjs.org/@types/nwsapi/-/nwsapi-2.2.2.tgz#1b1dccfc38b2b7e1b9ea71d5285796878375e862" integrity sha512-C4G47l3cAra4729xbhL9y3PjTpO7LJwXd47Fn1mbnZ6WcTkFPo8iDJPyMGCIudxpc7aeM8K1Fmw+lZfOb5ya9g== "@types/offscreencanvas@^2019.6.4": @@ -3499,7 +3487,7 @@ compare-func@^2.0.0: compare-versions@^4.1.3: version "4.1.3" - resolved "https://registry.npmjs.org/compare-versions/-/compare-versions-4.1.3.tgz" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== concat-map@0.0.1: @@ -9676,6 +9664,14 @@ resolve@^1.1.7, resolve@^1.10.0, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.19 is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@~1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz"