refactoring public API
This commit is contained in:
@@ -77,9 +77,12 @@ export function addHoverClass(cssText: string): string {
|
|||||||
|
|
||||||
function buildNode(
|
function buildNode(
|
||||||
n: serializedNodeWithId,
|
n: serializedNodeWithId,
|
||||||
doc: Document,
|
options: {
|
||||||
HACK_CSS: boolean,
|
doc: Document;
|
||||||
|
hackCss: boolean;
|
||||||
|
},
|
||||||
): Node | null {
|
): Node | null {
|
||||||
|
const { doc, hackCss } = options;
|
||||||
switch (n.type) {
|
switch (n.type) {
|
||||||
case NodeType.Document:
|
case NodeType.Document:
|
||||||
return doc.implementation.createDocument(null, '', null);
|
return doc.implementation.createDocument(null, '', null);
|
||||||
@@ -109,7 +112,7 @@ function buildNode(
|
|||||||
const isTextarea = tagName === 'textarea' && name === 'value';
|
const isTextarea = tagName === 'textarea' && name === 'value';
|
||||||
const isRemoteOrDynamicCss =
|
const isRemoteOrDynamicCss =
|
||||||
tagName === 'style' && name === '_cssText';
|
tagName === 'style' && name === '_cssText';
|
||||||
if (isRemoteOrDynamicCss && HACK_CSS) {
|
if (isRemoteOrDynamicCss && hackCss) {
|
||||||
value = addHoverClass(value);
|
value = addHoverClass(value);
|
||||||
}
|
}
|
||||||
if (isTextarea || isRemoteOrDynamicCss) {
|
if (isTextarea || isRemoteOrDynamicCss) {
|
||||||
@@ -177,7 +180,7 @@ function buildNode(
|
|||||||
return node;
|
return node;
|
||||||
case NodeType.Text:
|
case NodeType.Text:
|
||||||
return doc.createTextNode(
|
return doc.createTextNode(
|
||||||
n.isStyle && HACK_CSS ? addHoverClass(n.textContent) : n.textContent,
|
n.isStyle && hackCss ? addHoverClass(n.textContent) : n.textContent,
|
||||||
);
|
);
|
||||||
case NodeType.CDATA:
|
case NodeType.CDATA:
|
||||||
return doc.createCDATASection(n.textContent);
|
return doc.createCDATASection(n.textContent);
|
||||||
@@ -190,12 +193,15 @@ function buildNode(
|
|||||||
|
|
||||||
export function buildNodeWithSN(
|
export function buildNodeWithSN(
|
||||||
n: serializedNodeWithId,
|
n: serializedNodeWithId,
|
||||||
doc: Document,
|
options: {
|
||||||
map: idNodeMap,
|
doc: Document;
|
||||||
skipChild = false,
|
map: idNodeMap;
|
||||||
HACK_CSS = true,
|
skipChild?: boolean;
|
||||||
|
hackCss: boolean;
|
||||||
|
},
|
||||||
): INode | null {
|
): INode | null {
|
||||||
let node = buildNode(n, doc, HACK_CSS);
|
const { doc, map, skipChild = false, hackCss = true } = options;
|
||||||
|
let node = buildNode(n, { doc, hackCss });
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -214,7 +220,12 @@ export function buildNodeWithSN(
|
|||||||
!skipChild
|
!skipChild
|
||||||
) {
|
) {
|
||||||
for (const childN of n.childNodes) {
|
for (const childN of n.childNodes) {
|
||||||
const childNode = buildNodeWithSN(childN, doc, map, false, HACK_CSS);
|
const childNode = buildNodeWithSN(childN, {
|
||||||
|
doc,
|
||||||
|
map,
|
||||||
|
skipChild: false,
|
||||||
|
hackCss,
|
||||||
|
});
|
||||||
if (!childNode) {
|
if (!childNode) {
|
||||||
console.warn('Failed to rebuild', childN);
|
console.warn('Failed to rebuild', childN);
|
||||||
} else {
|
} else {
|
||||||
@@ -259,15 +270,20 @@ function handleScroll(node: INode) {
|
|||||||
|
|
||||||
function rebuild(
|
function rebuild(
|
||||||
n: serializedNodeWithId,
|
n: serializedNodeWithId,
|
||||||
doc: Document,
|
options: {
|
||||||
onVisit?: (node: INode) => unknown,
|
doc: Document;
|
||||||
/**
|
onVisit?: (node: INode) => unknown;
|
||||||
* This is not a public API yet, just for POC
|
hackCss?: boolean;
|
||||||
*/
|
},
|
||||||
HACK_CSS: boolean = true,
|
|
||||||
): [Node | null, idNodeMap] {
|
): [Node | null, idNodeMap] {
|
||||||
|
const { doc, onVisit, hackCss = true } = options;
|
||||||
const idNodeMap: idNodeMap = {};
|
const idNodeMap: idNodeMap = {};
|
||||||
const node = buildNodeWithSN(n, doc, idNodeMap, false, HACK_CSS);
|
const node = buildNodeWithSN(n, {
|
||||||
|
doc,
|
||||||
|
map: idNodeMap,
|
||||||
|
skipChild: false,
|
||||||
|
hackCss,
|
||||||
|
});
|
||||||
visit(idNodeMap, (visitedNode) => {
|
visit(idNodeMap, (visitedNode) => {
|
||||||
if (onVisit) {
|
if (onVisit) {
|
||||||
onVisit(visitedNode);
|
onVisit(visitedNode);
|
||||||
|
|||||||
115
src/snapshot.ts
115
src/snapshot.ts
@@ -187,13 +187,23 @@ export function _isBlockedElement(
|
|||||||
|
|
||||||
function serializeNode(
|
function serializeNode(
|
||||||
n: Node,
|
n: Node,
|
||||||
doc: Document,
|
options: {
|
||||||
blockClass: string | RegExp,
|
doc: Document;
|
||||||
blockSelector: string | null,
|
blockClass: string | RegExp;
|
||||||
inlineStylesheet: boolean,
|
blockSelector: string | null;
|
||||||
maskInputOptions: MaskInputOptions = {},
|
inlineStylesheet: boolean;
|
||||||
recordCanvas: boolean,
|
maskInputOptions: MaskInputOptions;
|
||||||
|
recordCanvas: boolean;
|
||||||
|
},
|
||||||
): serializedNode | false {
|
): serializedNode | false {
|
||||||
|
const {
|
||||||
|
doc,
|
||||||
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
|
inlineStylesheet,
|
||||||
|
maskInputOptions = {},
|
||||||
|
recordCanvas,
|
||||||
|
} = options;
|
||||||
switch (n.nodeType) {
|
switch (n.nodeType) {
|
||||||
case n.DOCUMENT_NODE:
|
case n.DOCUMENT_NODE:
|
||||||
return {
|
return {
|
||||||
@@ -437,26 +447,39 @@ function slimDOMExcluded(
|
|||||||
|
|
||||||
export function serializeNodeWithId(
|
export function serializeNodeWithId(
|
||||||
n: Node | INode,
|
n: Node | INode,
|
||||||
doc: Document,
|
options: {
|
||||||
map: idNodeMap,
|
doc: Document;
|
||||||
blockClass: string | RegExp,
|
map: idNodeMap;
|
||||||
blockSelector: string | null,
|
blockClass: string | RegExp;
|
||||||
skipChild = false,
|
blockSelector: string | null;
|
||||||
inlineStylesheet = true,
|
skipChild: boolean;
|
||||||
maskInputOptions?: MaskInputOptions,
|
inlineStylesheet: boolean;
|
||||||
slimDOMOptions: SlimDOMOptions = {},
|
maskInputOptions?: MaskInputOptions;
|
||||||
recordCanvas?: boolean,
|
slimDOMOptions: SlimDOMOptions;
|
||||||
preserveWhiteSpace = true,
|
recordCanvas?: boolean;
|
||||||
|
preserveWhiteSpace?: boolean;
|
||||||
|
},
|
||||||
): serializedNodeWithId | null {
|
): serializedNodeWithId | null {
|
||||||
const _serializedNode = serializeNode(
|
const {
|
||||||
n,
|
doc,
|
||||||
|
map,
|
||||||
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
|
skipChild = false,
|
||||||
|
inlineStylesheet = true,
|
||||||
|
maskInputOptions = {},
|
||||||
|
slimDOMOptions,
|
||||||
|
recordCanvas = false,
|
||||||
|
} = options;
|
||||||
|
let { preserveWhiteSpace = true } = options;
|
||||||
|
const _serializedNode = serializeNode(n, {
|
||||||
doc,
|
doc,
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
recordCanvas || false,
|
recordCanvas,
|
||||||
);
|
});
|
||||||
if (!_serializedNode) {
|
if (!_serializedNode) {
|
||||||
// TODO: dev only
|
// TODO: dev only
|
||||||
console.warn(n, 'not serialized');
|
console.warn(n, 'not serialized');
|
||||||
@@ -504,8 +527,7 @@ export function serializeNodeWithId(
|
|||||||
preserveWhiteSpace = false;
|
preserveWhiteSpace = false;
|
||||||
}
|
}
|
||||||
for (const childN of Array.from(n.childNodes)) {
|
for (const childN of Array.from(n.childNodes)) {
|
||||||
const serializedChildNode = serializeNodeWithId(
|
const serializedChildNode = serializeNodeWithId(childN, {
|
||||||
childN,
|
|
||||||
doc,
|
doc,
|
||||||
map,
|
map,
|
||||||
blockClass,
|
blockClass,
|
||||||
@@ -516,7 +538,7 @@ export function serializeNodeWithId(
|
|||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
preserveWhiteSpace,
|
preserveWhiteSpace,
|
||||||
);
|
});
|
||||||
if (serializedChildNode) {
|
if (serializedChildNode) {
|
||||||
serializedNode.childNodes.push(serializedChildNode);
|
serializedNode.childNodes.push(serializedChildNode);
|
||||||
}
|
}
|
||||||
@@ -527,16 +549,26 @@ export function serializeNodeWithId(
|
|||||||
|
|
||||||
function snapshot(
|
function snapshot(
|
||||||
n: Document,
|
n: Document,
|
||||||
blockClass: string | RegExp = 'rr-block',
|
options?: {
|
||||||
inlineStylesheet = true,
|
blockClass?: string | RegExp;
|
||||||
maskAllInputsOrOptions: boolean | MaskInputOptions,
|
inlineStylesheet?: boolean;
|
||||||
slimDOMSensibleOrOptions: boolean | SlimDOMOptions,
|
maskAllInputs?: boolean | MaskInputOptions;
|
||||||
recordCanvas?: boolean,
|
slimDOM?: boolean | SlimDOMOptions;
|
||||||
blockSelector: string | null = null,
|
recordCanvas?: boolean;
|
||||||
|
blockSelector?: string | null;
|
||||||
|
},
|
||||||
): [serializedNodeWithId | null, idNodeMap] {
|
): [serializedNodeWithId | null, idNodeMap] {
|
||||||
|
const {
|
||||||
|
blockClass = 'rr-block',
|
||||||
|
inlineStylesheet = true,
|
||||||
|
recordCanvas = false,
|
||||||
|
blockSelector = null,
|
||||||
|
maskAllInputs = false,
|
||||||
|
slimDOM = false,
|
||||||
|
} = options || {};
|
||||||
const idNodeMap: idNodeMap = {};
|
const idNodeMap: idNodeMap = {};
|
||||||
const maskInputOptions: MaskInputOptions =
|
const maskInputOptions: MaskInputOptions =
|
||||||
maskAllInputsOrOptions === true
|
maskAllInputs === true
|
||||||
? {
|
? {
|
||||||
color: true,
|
color: true,
|
||||||
date: true,
|
date: true,
|
||||||
@@ -554,40 +586,39 @@ function snapshot(
|
|||||||
textarea: true,
|
textarea: true,
|
||||||
select: true,
|
select: true,
|
||||||
}
|
}
|
||||||
: maskAllInputsOrOptions === false
|
: maskAllInputs === false
|
||||||
? {}
|
? {}
|
||||||
: maskAllInputsOrOptions;
|
: maskAllInputs;
|
||||||
const slimDOMOptions: SlimDOMOptions =
|
const slimDOMOptions: SlimDOMOptions =
|
||||||
slimDOMSensibleOrOptions === true || slimDOMSensibleOrOptions === 'all'
|
slimDOM === true || slimDOM === 'all'
|
||||||
? // if true: set of sensible options that should not throw away any information
|
? // if true: set of sensible options that should not throw away any information
|
||||||
{
|
{
|
||||||
script: true,
|
script: true,
|
||||||
comment: true,
|
comment: true,
|
||||||
headFavicon: true,
|
headFavicon: true,
|
||||||
headWhitespace: true,
|
headWhitespace: true,
|
||||||
headMetaDescKeywords: slimDOMSensibleOrOptions === 'all', // destructive
|
headMetaDescKeywords: slimDOM === 'all', // destructive
|
||||||
headMetaSocial: true,
|
headMetaSocial: true,
|
||||||
headMetaRobots: true,
|
headMetaRobots: true,
|
||||||
headMetaHttpEquiv: true,
|
headMetaHttpEquiv: true,
|
||||||
headMetaAuthorship: true,
|
headMetaAuthorship: true,
|
||||||
headMetaVerification: true,
|
headMetaVerification: true,
|
||||||
}
|
}
|
||||||
: slimDOMSensibleOrOptions === false
|
: slimDOM === false
|
||||||
? {}
|
? {}
|
||||||
: slimDOMSensibleOrOptions;
|
: slimDOM;
|
||||||
return [
|
return [
|
||||||
serializeNodeWithId(
|
serializeNodeWithId(n, {
|
||||||
n,
|
doc: n,
|
||||||
n,
|
map: idNodeMap,
|
||||||
idNodeMap,
|
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
false,
|
skipChild: false,
|
||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
),
|
}),
|
||||||
idNodeMap,
|
idNodeMap,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { SnapshotState, toMatchSnapshot } from 'jest-snapshot';
|
|||||||
import { Suite } from 'mocha';
|
import { Suite } from 'mocha';
|
||||||
|
|
||||||
const htmlFolder = path.join(__dirname, 'html');
|
const htmlFolder = path.join(__dirname, 'html');
|
||||||
const htmls = fs.readdirSync(htmlFolder).map(filePath => {
|
const htmls = fs.readdirSync(htmlFolder).map((filePath) => {
|
||||||
const raw = fs.readFileSync(path.resolve(htmlFolder, filePath), 'utf-8');
|
const raw = fs.readFileSync(path.resolve(htmlFolder, filePath), 'utf-8');
|
||||||
return {
|
return {
|
||||||
filePath,
|
filePath,
|
||||||
@@ -24,7 +24,7 @@ interface IMimeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const server = () =>
|
const server = () =>
|
||||||
new Promise<http.Server>(resolve => {
|
new Promise<http.Server>((resolve) => {
|
||||||
const mimeType: IMimeType = {
|
const mimeType: IMimeType = {
|
||||||
'.html': 'text/html',
|
'.html': 'text/html',
|
||||||
'.js': 'text/javascript',
|
'.js': 'text/javascript',
|
||||||
@@ -73,7 +73,7 @@ interface ISuite extends Suite {
|
|||||||
code: string;
|
code: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('integration tests', function(this: ISuite) {
|
describe('integration tests', function (this: ISuite) {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
this.server = await server();
|
this.server = await server();
|
||||||
this.browser = await puppeteer.launch({
|
this.browser = await puppeteer.launch({
|
||||||
@@ -102,16 +102,18 @@ describe('integration tests', function(this: ISuite) {
|
|||||||
const page: puppeteer.Page = await this.browser.newPage();
|
const page: puppeteer.Page = await this.browser.newPage();
|
||||||
// console for debug
|
// console for debug
|
||||||
// tslint:disable-next-line: no-console
|
// tslint:disable-next-line: no-console
|
||||||
page.on('console', msg => console.log(msg.text()));
|
page.on('console', (msg) => console.log(msg.text()));
|
||||||
await page.goto(`http://localhost:3030/html`);
|
await page.goto(`http://localhost:3030/html`);
|
||||||
await page.setContent(html.src, {
|
await page.setContent(html.src, {
|
||||||
waitUntil: 'load',
|
waitUntil: 'load',
|
||||||
});
|
});
|
||||||
const rebuildHtml = (await page.evaluate(`${this.code}
|
const rebuildHtml = (
|
||||||
|
await page.evaluate(`${this.code}
|
||||||
const x = new XMLSerializer();
|
const x = new XMLSerializer();
|
||||||
const [snap] = rrweb.snapshot(document);
|
const [snap] = rrweb.snapshot(document);
|
||||||
x.serializeToString(rrweb.rebuild(snap, document)[0]);
|
x.serializeToString(rrweb.rebuild(snap, { doc: document })[0]);
|
||||||
`)).replace(/\n\n/g, '');
|
`)
|
||||||
|
).replace(/\n\n/g, '');
|
||||||
const result = matchSnapshot(rebuildHtml, __filename, title);
|
const result = matchSnapshot(rebuildHtml, __filename, title);
|
||||||
assert(result.pass, result.pass ? '' : result.report());
|
assert(result.pass, result.pass ? '' : result.report());
|
||||||
}).timeout(5000);
|
}).timeout(5000);
|
||||||
|
|||||||
13
typings/rebuild.d.ts
vendored
13
typings/rebuild.d.ts
vendored
@@ -1,5 +1,14 @@
|
|||||||
import { serializedNodeWithId, idNodeMap, INode } from './types';
|
import { serializedNodeWithId, idNodeMap, INode } from './types';
|
||||||
export declare function addHoverClass(cssText: string): string;
|
export declare function addHoverClass(cssText: string): string;
|
||||||
export declare function buildNodeWithSN(n: serializedNodeWithId, doc: Document, map: idNodeMap, skipChild?: boolean, HACK_CSS?: boolean): INode | null;
|
export declare function buildNodeWithSN(n: serializedNodeWithId, options: {
|
||||||
declare function rebuild(n: serializedNodeWithId, doc: Document, onVisit?: (node: INode) => unknown, HACK_CSS?: boolean): [Node | null, idNodeMap];
|
doc: Document;
|
||||||
|
map: idNodeMap;
|
||||||
|
skipChild?: boolean;
|
||||||
|
hackCss: boolean;
|
||||||
|
}): INode | null;
|
||||||
|
declare function rebuild(n: serializedNodeWithId, options: {
|
||||||
|
doc: Document;
|
||||||
|
onVisit?: (node: INode) => unknown;
|
||||||
|
hackCss?: boolean;
|
||||||
|
}): [Node | null, idNodeMap];
|
||||||
export default rebuild;
|
export default rebuild;
|
||||||
|
|||||||
22
typings/snapshot.d.ts
vendored
22
typings/snapshot.d.ts
vendored
@@ -4,8 +4,26 @@ export declare function absoluteToStylesheet(cssText: string | null, href: strin
|
|||||||
export declare function absoluteToDoc(doc: Document, attributeValue: string): string;
|
export declare function absoluteToDoc(doc: Document, attributeValue: string): string;
|
||||||
export declare function transformAttribute(doc: Document, name: string, value: string): string;
|
export declare function transformAttribute(doc: Document, name: string, value: string): string;
|
||||||
export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null): boolean;
|
export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null): boolean;
|
||||||
export declare function serializeNodeWithId(n: Node | INode, doc: Document, map: idNodeMap, blockClass: string | RegExp, blockSelector: string | null, skipChild?: boolean, inlineStylesheet?: boolean, maskInputOptions?: MaskInputOptions, slimDOMOptions?: SlimDOMOptions, recordCanvas?: boolean, preserveWhiteSpace?: boolean): serializedNodeWithId | null;
|
export declare function serializeNodeWithId(n: Node | INode, options: {
|
||||||
declare function snapshot(n: Document, blockClass: string | RegExp | undefined, inlineStylesheet: boolean | undefined, maskAllInputsOrOptions: boolean | MaskInputOptions, slimDOMSensibleOrOptions: boolean | SlimDOMOptions, recordCanvas?: boolean, blockSelector?: string | null): [serializedNodeWithId | null, idNodeMap];
|
doc: Document;
|
||||||
|
map: idNodeMap;
|
||||||
|
blockClass: string | RegExp;
|
||||||
|
blockSelector: string | null;
|
||||||
|
skipChild: boolean;
|
||||||
|
inlineStylesheet: boolean;
|
||||||
|
maskInputOptions?: MaskInputOptions;
|
||||||
|
slimDOMOptions: SlimDOMOptions;
|
||||||
|
recordCanvas?: boolean;
|
||||||
|
preserveWhiteSpace?: boolean;
|
||||||
|
}): serializedNodeWithId | null;
|
||||||
|
declare function snapshot(n: Document, options?: {
|
||||||
|
blockClass?: string | RegExp;
|
||||||
|
inlineStylesheet?: boolean;
|
||||||
|
maskAllInputs?: boolean | MaskInputOptions;
|
||||||
|
slimDOM?: boolean | SlimDOMOptions;
|
||||||
|
recordCanvas?: boolean;
|
||||||
|
blockSelector?: string | null;
|
||||||
|
}): [serializedNodeWithId | null, idNodeMap];
|
||||||
export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void;
|
export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void;
|
||||||
export declare function cleanupSnapshot(): void;
|
export declare function cleanupSnapshot(): void;
|
||||||
export default snapshot;
|
export default snapshot;
|
||||||
|
|||||||
Reference in New Issue
Block a user