move browser-only rrdom features to the new rrdom package (#913)

This commit is contained in:
yz-yu
2026-04-01 12:00:00 +08:00
committed by GitHub
parent eec8d6f717
commit 7662d4e0fb
31 changed files with 707 additions and 554 deletions

View File

@@ -8,6 +8,10 @@
"name": "rrdom (package)", "name": "rrdom (package)",
"path": "../packages/rrdom" "path": "../packages/rrdom"
}, },
{
"name": "rrdom-nodejs (package)",
"path": "../packages/rrdom-nodejs"
},
{ {
"name": "rrweb (package)", "name": "rrweb (package)",
"path": "../packages/rrweb" "path": "../packages/rrweb"

View File

@@ -18,7 +18,8 @@
"packages/rrweb", "packages/rrweb",
"packages/rrweb-snapshot", "packages/rrweb-snapshot",
"packages/rrweb-player", "packages/rrweb-player",
"packages/rrdom" "packages/rrdom",
"packages/rrdom-nodejs"
], ],
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.25.0", "@typescript-eslint/eslint-plugin": "^5.25.0",

4
packages/rrdom-nodejs/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
dist
es
lib
typings

View File

@@ -0,0 +1,5 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

View File

@@ -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"
}
}

View File

@@ -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;

View File

@@ -13,7 +13,7 @@ import {
ClassList, ClassList,
IRRDocument, IRRDocument,
CSSStyleDeclaration, CSSStyleDeclaration,
} from './document'; } from 'rrdom';
const nwsapi = require('nwsapi'); const nwsapi = require('nwsapi');
const cssom = require('cssom'); const cssom = require('cssom');
const cssstyle = require('cssstyle'); const cssstyle = require('cssstyle');
@@ -53,22 +53,27 @@ export class RRDocument
return this._nwsapi; return this._nwsapi;
} }
// @ts-ignore
get documentElement(): RRElement | null { get documentElement(): RRElement | null {
return super.documentElement as RRElement | null; return super.documentElement as RRElement | null;
} }
// @ts-ignore
get body(): RRElement | null { get body(): RRElement | null {
return super.body as RRElement | null; return super.body as RRElement | null;
} }
// @ts-ignore
get head() { get head() {
return super.head as RRElement | null; return super.head as RRElement | null;
} }
// @ts-ignore
get implementation(): RRDocument { get implementation(): RRDocument {
return this; return this;
} }
// @ts-ignore
get firstElementChild(): RRElement | null { get firstElementChild(): RRElement | null {
return this.documentElement; return this.documentElement;
} }
@@ -198,6 +203,7 @@ export class RRElement extends BaseRRElementImpl(RRNode) {
}); });
} }
// @ts-ignore
get style() { get style() {
return (this._style as unknown) as CSSStyleDeclaration; return (this._style as unknown) as CSSStyleDeclaration;
} }

View File

@@ -0,0 +1,13 @@
import {
polyfillPerformance,
polyfillRAF,
polyfillEvent,
polyfillNode,
polyfillDocument,
} from './polyfill';
polyfillPerformance();
polyfillRAF();
polyfillEvent();
polyfillNode();
polyfillDocument();
export * from './document-nodejs';

View File

@@ -16,7 +16,7 @@ import {
RRStyleElement, RRStyleElement,
RRText, RRText,
} from '../src/document-nodejs'; } from '../src/document-nodejs';
import { buildFromDom } from '../src/virtual-dom'; import { buildFromDom } from 'rrdom';
describe('RRDocument for nodejs environment', () => { describe('RRDocument for nodejs environment', () => {
describe('RRDocument API', () => { describe('RRDocument API', () => {
@@ -542,6 +542,6 @@ describe('RRDocument for nodejs environment', () => {
}); });
function getHtml(fileName: string) { 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'); return fs.readFileSync(filePath, 'utf8');
} }

View File

@@ -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"]
}

View File

@@ -1,19 +1,7 @@
{ {
"name": "rrdom", "name": "rrdom",
"version": "0.1.2", "version": "0.1.2",
"scripts": { "homepage": "https://github.com/rrweb-io/rrweb/tree/main/packages/rrdom#readme",
"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"
],
"license": "MIT", "license": "MIT",
"main": "lib/rrdom.js", "main": "lib/rrdom.js",
"module": "es/rrdom.js", "module": "es/rrdom.js",
@@ -25,17 +13,28 @@
"es", "es",
"typings" "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": { "devDependencies": {
"@rollup/plugin-commonjs": "^20.0.0", "@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/jest": "^27.4.1",
"@types/nwsapi": "^2.2.2",
"@types/puppeteer": "^5.4.4",
"@typescript-eslint/eslint-plugin": "^5.23.0", "@typescript-eslint/eslint-plugin": "^5.23.0",
"@typescript-eslint/parser": "^5.23.0", "@typescript-eslint/parser": "^5.23.0",
"compare-versions": "^4.1.3", "@types/puppeteer": "^5.4.4",
"eslint": "^8.15.0", "eslint": "^8.15.0",
"jest": "^27.5.1", "jest": "^27.5.1",
"puppeteer": "^9.1.1", "puppeteer": "^9.1.1",
@@ -43,13 +42,10 @@
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.2", "rollup-plugin-typescript2": "^0.31.2",
"rollup-plugin-web-worker-loader": "^1.6.1", "rollup-plugin-web-worker-loader": "^1.6.1",
"rrweb-snapshot": "^1.1.14",
"ts-jest": "^27.1.3", "ts-jest": "^27.1.3",
"typescript": "^4.6.2" "typescript": "^4.6.2"
}, },
"dependencies": { "dependencies": {
"cssom": "^0.5.0", "rrweb-snapshot": "^1.1.14"
"cssstyle": "^2.3.0",
"nwsapi": "^2.2.0"
} }
} }

View File

@@ -27,16 +27,6 @@ const baseConfigs = [
name: pkg.name, name: pkg.name,
path: 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 = []; let configs = [];

View File

@@ -22,7 +22,7 @@ import type {
RRStyleElement, RRStyleElement,
RRDocument, RRDocument,
Mirror, Mirror,
} from './virtual-dom'; } from '.';
const NAMESPACES: Record<string, string> = { const NAMESPACES: Record<string, string> = {
svg: 'http://www.w3.org/2000/svg', svg: 'http://www.w3.org/2000/svg',
@@ -113,7 +113,7 @@ export function diff(
break; break;
} }
case RRNodeType.Element: { case RRNodeType.Element: {
const oldElement = (oldTree ) as HTMLElement; const oldElement = oldTree as HTMLElement;
const newRRElement = newTree as IRRElement; const newRRElement = newTree as IRRElement;
diffProps(oldElement, newRRElement, rrnodeMirror); diffProps(oldElement, newRRElement, rrnodeMirror);
scrollDataToApply = (newRRElement as RRElement).scrollData; scrollDataToApply = (newRRElement as RRElement).scrollData;
@@ -121,7 +121,7 @@ export function diff(
switch (newRRElement.tagName) { switch (newRRElement.tagName) {
case 'AUDIO': case 'AUDIO':
case 'VIDEO': { case 'VIDEO': {
const oldMediaElement = (oldTree ) as HTMLMediaElement; const oldMediaElement = oldTree as HTMLMediaElement;
const newMediaRRElement = newRRElement as RRMediaElement; const newMediaRRElement = newRRElement as RRMediaElement;
if (newMediaRRElement.paused !== undefined) if (newMediaRRElement.paused !== undefined)
newMediaRRElement.paused newMediaRRElement.paused
@@ -141,7 +141,7 @@ export function diff(
replayer.applyCanvas( replayer.applyCanvas(
canvasMutation.event, canvasMutation.event,
canvasMutation.mutation, canvasMutation.mutation,
(oldTree ) as HTMLCanvasElement, oldTree as HTMLCanvasElement,
), ),
); );
break; break;
@@ -191,8 +191,7 @@ export function diff(
// IFrame element doesn't have child nodes. // IFrame element doesn't have child nodes.
if (newTree.nodeName === 'IFRAME') { if (newTree.nodeName === 'IFRAME') {
const oldContentDocument = ((oldTree ) as HTMLIFrameElement) const oldContentDocument = (oldTree as HTMLIFrameElement).contentDocument;
.contentDocument;
const newIFrameElement = newTree as RRIFrameElement; const newIFrameElement = newTree as RRIFrameElement;
// If the iframe is cross-origin, the contentDocument will be null. // If the iframe is cross-origin, the contentDocument will be null.
if (oldContentDocument) { if (oldContentDocument) {
@@ -319,11 +318,9 @@ function diffChildren(
if ( if (
replayer.mirror.getMeta(parentNode)?.type === RRNodeType.Document && replayer.mirror.getMeta(parentNode)?.type === RRNodeType.Document &&
replayer.mirror.getMeta(newNode)?.type === RRNodeType.Element && replayer.mirror.getMeta(newNode)?.type === RRNodeType.Element &&
((parentNode ) as Document).documentElement (parentNode as Document).documentElement
) { ) {
parentNode.removeChild( parentNode.removeChild((parentNode as Document).documentElement);
((parentNode ) as Document).documentElement,
);
oldChildren[oldStartIndex] = undefined; oldChildren[oldStartIndex] = undefined;
oldStartNode = undefined; oldStartNode = undefined;
} }
@@ -417,8 +414,7 @@ export function getNestedRule(
return rule; return rule;
} else { } else {
return getNestedRule( return getNestedRule(
((rule ).cssRules[position[1]] as CSSGroupingRule) (rule.cssRules[position[1]] as CSSGroupingRule).cssRules,
.cssRules,
position.slice(2), position.slice(2),
); );
} }

View File

@@ -1,13 +1,450 @@
import { import {
polyfillPerformance, NodeType as RRNodeType,
polyfillRAF, createMirror as createNodeMirror,
polyfillEvent, } from 'rrweb-snapshot';
polyfillNode, import type {
polyfillDocument, Mirror as NodeMirror,
} from './polyfill'; IMirror,
polyfillPerformance(); serializedNodeWithId,
polyfillRAF(); } from 'rrweb-snapshot';
polyfillEvent(); import type {
polyfillNode(); canvasMutationData,
polyfillDocument(); canvasEventWithTime,
export * from './document-nodejs'; 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<K extends keyof HTMLElementTagNameMap>(
tagName: K,
): RRElementType<K>;
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<RRNode> {
private idNodeMap: Map<number, RRNode> = new Map();
private nodeMetaMap: WeakMap<RRNode, serializedNodeWithId> = 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';

View File

@@ -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<K extends keyof HTMLElementTagNameMap>(
tagName: K,
): RRElementType<K>;
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<RRNode> {
private idNodeMap: Map<number, RRNode> = new Map();
private nodeMetaMap: WeakMap<RRNode, serializedNodeWithId> = 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';

View File

@@ -1,7 +1,7 @@
/** /**
* @jest-environment jsdom * @jest-environment jsdom
*/ */
import { getDefaultSN, RRDocument, RRMediaElement } from '../src/virtual-dom'; import { getDefaultSN, RRDocument, RRMediaElement } from '../src';
import { import {
applyVirtualStyleRulesToNode, applyVirtualStyleRulesToNode,
createOrGetNode, createOrGetNode,

View File

@@ -27,8 +27,8 @@ import {
RRCanvasElement, RRCanvasElement,
RRDocument, RRDocument,
RRElement, RRElement,
RRNode, BaseRRNode as RRNode,
} from '../src/virtual-dom'; } from '../src';
const _typescript = (typescript as unknown) as typeof typescript.default; const _typescript = (typescript as unknown) as typeof typescript.default;
const printRRDomCode = ` const printRRDomCode = `
@@ -219,9 +219,9 @@ describe('RRDocument for browser environment', () => {
beforeAll(async () => { beforeAll(async () => {
browser = await puppeteer.launch(); browser = await puppeteer.launch();
const bundle = await rollup.rollup({ const bundle = await rollup.rollup({
input: path.resolve(__dirname, '../src/virtual-dom.ts'), input: path.resolve(__dirname, '../src/index.ts'),
plugins: [ plugins: [
resolve(), (resolve() as unknown) as rollup.Plugin,
(_typescript({ (_typescript({
tsconfigOverride: { compilerOptions: { module: 'ESNext' } }, tsconfigOverride: { compilerOptions: { module: 'ESNext' } },
}) as unknown) as rollup.Plugin, }) as unknown) as rollup.Plugin,

View File

@@ -16,5 +16,5 @@
}, },
"compileOnSave": true, "compileOnSave": true,
"exclude": ["test"], "exclude": ["test"],
"include": ["src", "test.d.ts", "../rrweb/src/record/workers/workers.d.ts"] "include": ["src", "../rrweb/src/record/workers/workers.d.ts"]
} }

View File

@@ -5,6 +5,5 @@ module.exports = {
testMatch: ['**/**.test.ts'], testMatch: ['**/**.test.ts'],
moduleNameMapper: { moduleNameMapper: {
'\\.css$': 'identity-obj-proxy', '\\.css$': 'identity-obj-proxy',
'rrdom/es/(.*)': 'rrdom/lib/$1',
}, },
}; };

View File

@@ -15,7 +15,7 @@ import {
buildFromDom, buildFromDom,
diff, diff,
getDefaultSN, getDefaultSN,
} from 'rrdom/es/virtual-dom'; } from 'rrdom';
import type { import type {
RRNode, RRNode,
RRElement, RRElement,
@@ -26,7 +26,7 @@ import type {
ReplayerHandler, ReplayerHandler,
Mirror as RRDOMMirror, Mirror as RRDOMMirror,
VirtualStyleRules, VirtualStyleRules,
} from 'rrdom/es/virtual-dom'; } from 'rrdom';
import * as mittProxy from 'mitt'; import * as mittProxy from 'mitt';
import { polyfill as smoothscrollPolyfill } from './smoothscroll'; import { polyfill as smoothscrollPolyfill } from './smoothscroll';
import { Timer } from './timer'; import { Timer } from './timer';
@@ -731,7 +731,7 @@ export class Replayer {
); );
} }
if (this.usingVirtualDom) { if (this.usingVirtualDom) {
const styleEl = this.virtualDom.createElement('style') ; const styleEl = this.virtualDom.createElement('style');
this.virtualDom.mirror.add( this.virtualDom.mirror.add(
styleEl, styleEl,
getDefaultSN(styleEl, this.virtualDom.unserializedId), getDefaultSN(styleEl, this.virtualDom.unserializedId),
@@ -752,10 +752,7 @@ export class Replayer {
head as HTMLHeadElement, head as HTMLHeadElement,
); );
for (let idx = 0; idx < injectStylesRules.length; idx++) { for (let idx = 0; idx < injectStylesRules.length; idx++) {
(styleEl.sheet! ).insertRule( styleEl.sheet!.insertRule(injectStylesRules[idx], idx);
injectStylesRules[idx],
idx,
);
} }
} }
} }
@@ -1210,7 +1207,7 @@ export class Replayer {
if (!target) { if (!target) {
return this.debugNodeNotFound(d, d.id); return this.debugNodeNotFound(d, d.id);
} }
const styleSheet = ((target ) as HTMLStyleElement).sheet!; const styleSheet = (target as HTMLStyleElement).sheet!;
d.adds?.forEach(({ rule, index: nestedIndex }) => { d.adds?.forEach(({ rule, index: nestedIndex }) => {
try { try {
if (Array.isArray(nestedIndex)) { if (Array.isArray(nestedIndex)) {
@@ -1692,7 +1689,7 @@ export class Replayer {
} }
} }
} else if (attributeName === 'style') { } else if (attributeName === 'style') {
const styleValues = value ; const styleValues = value;
const targetEl = target as HTMLElement | RRElement; const targetEl = target as HTMLElement | RRElement;
for (const s in styleValues) { for (const s in styleValues) {
if (styleValues[s] === false) { if (styleValues[s] === false) {
@@ -1772,7 +1769,7 @@ export class Replayer {
const previousInMap = previousId && map[previousId]; const previousInMap = previousId && map[previousId];
const nextInMap = nextId && map[nextId]; const nextInMap = nextId && map[nextId];
if (previousInMap) { if (previousInMap) {
const { node, mutation } = previousInMap ; const { node, mutation } = previousInMap;
parent.insertBefore(node as Node & RRNode, target as Node & RRNode); parent.insertBefore(node as Node & RRNode, target as Node & RRNode);
delete map[mutation.node.id]; delete map[mutation.node.id];
delete this.legacy_missingNodeRetryMap[mutation.node.id]; delete this.legacy_missingNodeRetryMap[mutation.node.id];
@@ -1781,7 +1778,7 @@ export class Replayer {
} }
} }
if (nextInMap) { if (nextInMap) {
const { node, mutation } = nextInMap ; const { node, mutation } = nextInMap;
parent.insertBefore( parent.insertBefore(
node as Node & RRNode, node as Node & RRNode,
target.nextSibling as Node & RRNode, target.nextSibling as Node & RRNode,

View File

@@ -11,7 +11,7 @@ import type { PackFn, UnpackFn } from './packer/base';
import type { IframeManager } from './record/iframe-manager'; import type { IframeManager } from './record/iframe-manager';
import type { ShadowDomManager } from './record/shadow-dom-manager'; import type { ShadowDomManager } from './record/shadow-dom-manager';
import type { Replayer } from './replay'; 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'; import type { CanvasManager } from './record/observers/canvas/canvas-manager';
export enum EventType { export enum EventType {

View File

@@ -11,7 +11,7 @@ import type {
} from './types'; } from './types';
import type { IMirror, Mirror } from 'rrweb-snapshot'; import type { IMirror, Mirror } from 'rrweb-snapshot';
import { isShadowRoot, IGNORED_NODE, classMatchesRegex } 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( export function on(
type: string, type: string,

View File

@@ -1,5 +1,5 @@
import { Mirror } from 'rrweb-snapshot'; import { Mirror } from 'rrweb-snapshot';
import { RRDocument } from 'rrdom/es/virtual-dom'; import { RRDocument } from 'rrdom';
import { Timer } from './timer'; import { Timer } from './timer';
import { createPlayerService, createSpeedService } from './machine'; import { createPlayerService, createSpeedService } from './machine';
import { eventWithTime, playerConfig, playerMetaData, Handler } from '../types'; import { eventWithTime, playerConfig, playerMetaData, Handler } from '../types';

View File

@@ -3,7 +3,7 @@ import type { PackFn, UnpackFn } from './packer/base';
import type { IframeManager } from './record/iframe-manager'; import type { IframeManager } from './record/iframe-manager';
import type { ShadowDomManager } from './record/shadow-dom-manager'; import type { ShadowDomManager } from './record/shadow-dom-manager';
import type { Replayer } from './replay'; 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'; import type { CanvasManager } from './record/observers/canvas/canvas-manager';
export declare enum EventType { export declare enum EventType {
DomContentLoaded = 0, DomContentLoaded = 0,

View File

@@ -1,6 +1,6 @@
import type { throttleOptions, listenerHandler, hookResetter, blockClass, addedNodeMutation, DocumentDimension, IWindow, DeprecatedMirror, textMutation } from './types'; import type { throttleOptions, listenerHandler, hookResetter, blockClass, addedNodeMutation, DocumentDimension, IWindow, DeprecatedMirror, textMutation } from './types';
import type { IMirror, Mirror } from 'rrweb-snapshot'; 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 function on(type: string, fn: EventListenerOrEventListenerObject, target?: Document | IWindow): listenerHandler;
export declare let _mirror: DeprecatedMirror; export declare let _mirror: DeprecatedMirror;
export declare function throttle<T>(func: (arg: T) => void, wait: number, options?: throttleOptions): (arg: T) => void; export declare function throttle<T>(func: (arg: T) => void, wait: number, options?: throttleOptions): (arg: T) => void;

View File

@@ -1791,10 +1791,10 @@
magic-string "^0.25.7" magic-string "^0.25.7"
resolve "^1.17.0" resolve "^1.17.0"
"@rollup/plugin-node-resolve@^13.0.4": "@rollup/plugin-node-resolve@^13.0.4", "@rollup/plugin-node-resolve@^13.2.1":
version "13.0.6" version "13.2.1"
resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.6.tgz" resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.2.1.tgz"
integrity sha512-sFsPDMPd4gMqnh2gS0uIxELnoRUp5kBl5knxD2EO0778G1oOJv4G1vyT2cpWz75OU2jDVcXhjVUuTAczGyFNKA== integrity sha512-btX7kzGvp1JwShQI9V6IM841YKNPYjKCvUbNrQ2EcVYbULtUd/GH6wZ/qdqH13j9pOHBER+EZXNN2L8RSJhVRA==
dependencies: dependencies:
"@rollup/pluginutils" "^3.1.0" "@rollup/pluginutils" "^3.1.0"
"@types/resolve" "1.17.1" "@types/resolve" "1.17.1"
@@ -1815,18 +1815,6 @@
is-module "^1.0.0" is-module "^1.0.0"
resolve "^1.19.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": "@rollup/plugin-typescript@^8.2.5":
version "8.2.5" version "8.2.5"
resolved "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz" resolved "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz"
@@ -1965,12 +1953,12 @@
"@types/cssom@^0.4.1": "@types/cssom@^0.4.1":
version "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== integrity sha512-hHGVfUuGZe5FpgCxpTJccH0gD1bui5gWceW0We0TyAzUr6wBaqDnSLG9Yr3xqS4AkGhnclNOwRSXH/LIfki3fQ==
"@types/cssstyle@^2.2.1": "@types/cssstyle@^2.2.1":
version "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== integrity sha512-CSQFKdZc3dmWoZXLAM0pPL6XiYLG8hMGzImM2MwQ9kavB5LnbeMGan94CCj4oxY65xMl5mRMwrFUfKPOWO4WpQ==
"@types/estree@*": "@types/estree@*":
@@ -2088,7 +2076,7 @@
"@types/nwsapi@^2.2.2": "@types/nwsapi@^2.2.2":
version "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== integrity sha512-C4G47l3cAra4729xbhL9y3PjTpO7LJwXd47Fn1mbnZ6WcTkFPo8iDJPyMGCIudxpc7aeM8K1Fmw+lZfOb5ya9g==
"@types/offscreencanvas@^2019.6.4": "@types/offscreencanvas@^2019.6.4":
@@ -3499,7 +3487,7 @@ compare-func@^2.0.0:
compare-versions@^4.1.3: compare-versions@^4.1.3:
version "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== integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg==
concat-map@0.0.1: 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" is-core-module "^2.2.0"
path-parse "^1.0.6" 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: restore-cursor@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz"