basic rebuild implementation
This commit is contained in:
140
src/index.ts
140
src/index.ts
@@ -1,138 +1,4 @@
|
|||||||
let _id = 1;
|
import snapshot from './snapshot';
|
||||||
|
import rebuild from './rebuild';
|
||||||
|
|
||||||
function genId(): number {
|
export { snapshot, rebuild };
|
||||||
return _id++;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NodeType {
|
|
||||||
Document,
|
|
||||||
DocumentType,
|
|
||||||
Element,
|
|
||||||
Text,
|
|
||||||
CDATA,
|
|
||||||
Comment,
|
|
||||||
}
|
|
||||||
|
|
||||||
type serializedNode =
|
|
||||||
| documentNode
|
|
||||||
| documentTypeNode
|
|
||||||
| elementNode
|
|
||||||
| textNode
|
|
||||||
| cdataNode
|
|
||||||
| commentNode;
|
|
||||||
|
|
||||||
type documentNode = {
|
|
||||||
type: NodeType.Document;
|
|
||||||
childNodes: serializedNode[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type documentTypeNode = {
|
|
||||||
type: NodeType.DocumentType;
|
|
||||||
name: string;
|
|
||||||
publicId: string;
|
|
||||||
systemId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type attributes = {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
|
||||||
type elementNode = {
|
|
||||||
type: NodeType.Element;
|
|
||||||
tagName: string;
|
|
||||||
attributes: attributes;
|
|
||||||
childNodes: serializedNode[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type textNode = {
|
|
||||||
type: NodeType.Text;
|
|
||||||
textContent: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type cdataNode = {
|
|
||||||
type: NodeType.CDATA;
|
|
||||||
textContent: '';
|
|
||||||
};
|
|
||||||
|
|
||||||
type commentNode = {
|
|
||||||
type: NodeType.Comment;
|
|
||||||
textContent: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function serializeNode(n: Node): serializedNode | false {
|
|
||||||
switch (n.nodeType) {
|
|
||||||
case n.DOCUMENT_NODE:
|
|
||||||
return {
|
|
||||||
type: NodeType.Document,
|
|
||||||
childNodes: [],
|
|
||||||
};
|
|
||||||
case n.DOCUMENT_TYPE_NODE:
|
|
||||||
return {
|
|
||||||
type: NodeType.DocumentType,
|
|
||||||
name: (n as DocumentType).name,
|
|
||||||
publicId: (n as DocumentType).publicId,
|
|
||||||
systemId: (n as DocumentType).systemId,
|
|
||||||
};
|
|
||||||
case n.ELEMENT_NODE:
|
|
||||||
const tagName = (n as HTMLElement).tagName.toLowerCase();
|
|
||||||
const attributes: attributes = {};
|
|
||||||
for (const { name, value } of Array.from((n as HTMLElement).attributes)) {
|
|
||||||
attributes[name] = value;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: NodeType.Element,
|
|
||||||
tagName,
|
|
||||||
attributes,
|
|
||||||
childNodes: [],
|
|
||||||
};
|
|
||||||
case n.TEXT_NODE:
|
|
||||||
// The parent node may not be a html element which has a tagName attribute.
|
|
||||||
// So just let it be undefined which is ok in this use case.
|
|
||||||
const parentTagName =
|
|
||||||
n.parentNode && (n.parentNode as HTMLElement).tagName;
|
|
||||||
let textContent = (n as Text).textContent;
|
|
||||||
if (parentTagName === 'SCRIPT') {
|
|
||||||
textContent = '';
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: NodeType.Text,
|
|
||||||
textContent,
|
|
||||||
};
|
|
||||||
case n.CDATA_SECTION_NODE:
|
|
||||||
return {
|
|
||||||
type: NodeType.CDATA,
|
|
||||||
textContent: '',
|
|
||||||
};
|
|
||||||
case n.COMMENT_NODE:
|
|
||||||
return {
|
|
||||||
type: NodeType.Comment,
|
|
||||||
textContent: (n as Comment).textContent,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type serializedNodeWithId = serializedNode & { id: number };
|
|
||||||
|
|
||||||
function snapshot(n: Node): serializedNodeWithId | null {
|
|
||||||
const _serializedNode = serializeNode(n);
|
|
||||||
if (!_serializedNode) {
|
|
||||||
// TODO: dev only
|
|
||||||
console.warn(n, 'not serialized');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const serializedNode: serializedNodeWithId = Object.assign(_serializedNode, {
|
|
||||||
id: genId(),
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
serializedNode.type === NodeType.Document ||
|
|
||||||
serializedNode.type === NodeType.Element
|
|
||||||
) {
|
|
||||||
for (const childN of Array.from(n.childNodes)) {
|
|
||||||
serializedNode.childNodes.push(snapshot(childN));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return serializedNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default snapshot;
|
|
||||||
|
|||||||
46
src/rebuild.ts
Normal file
46
src/rebuild.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { serializedNodeWithId, NodeType } from './types';
|
||||||
|
|
||||||
|
function buildNode(n: serializedNodeWithId): Node | null {
|
||||||
|
switch (n.type) {
|
||||||
|
case NodeType.Document:
|
||||||
|
return document.implementation.createDocument(null, '', null);
|
||||||
|
case NodeType.DocumentType:
|
||||||
|
return document.implementation.createDocumentType(
|
||||||
|
n.name,
|
||||||
|
n.publicId,
|
||||||
|
n.systemId,
|
||||||
|
);
|
||||||
|
case NodeType.Element:
|
||||||
|
const node = document.createElement(n.tagName);
|
||||||
|
for (const name in n.attributes) {
|
||||||
|
if (n.attributes.hasOwnProperty(name)) {
|
||||||
|
node.setAttribute(name, n.attributes[name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
case NodeType.Text:
|
||||||
|
return document.createTextNode(n.textContent);
|
||||||
|
case NodeType.CDATA:
|
||||||
|
return document.createCDATASection(n.textContent);
|
||||||
|
case NodeType.Comment:
|
||||||
|
return document.createComment(n.textContent);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuild(n: serializedNodeWithId): Node | null {
|
||||||
|
const root = buildNode(n);
|
||||||
|
if (!root) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (n.type === NodeType.Document || n.type === NodeType.Element) {
|
||||||
|
for (const childN of n.childNodes) {
|
||||||
|
const childNode = rebuild(childN);
|
||||||
|
root.appendChild(childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default rebuild;
|
||||||
89
src/snapshot.ts
Normal file
89
src/snapshot.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import {
|
||||||
|
serializedNode,
|
||||||
|
serializedNodeWithId,
|
||||||
|
NodeType,
|
||||||
|
attributes,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
let _id = 1;
|
||||||
|
|
||||||
|
function genId(): number {
|
||||||
|
return _id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeNode(n: Node): serializedNode | false {
|
||||||
|
switch (n.nodeType) {
|
||||||
|
case n.DOCUMENT_NODE:
|
||||||
|
return {
|
||||||
|
type: NodeType.Document,
|
||||||
|
childNodes: [],
|
||||||
|
};
|
||||||
|
case n.DOCUMENT_TYPE_NODE:
|
||||||
|
return {
|
||||||
|
type: NodeType.DocumentType,
|
||||||
|
name: (n as DocumentType).name,
|
||||||
|
publicId: (n as DocumentType).publicId,
|
||||||
|
systemId: (n as DocumentType).systemId,
|
||||||
|
};
|
||||||
|
case n.ELEMENT_NODE:
|
||||||
|
const tagName = (n as HTMLElement).tagName.toLowerCase();
|
||||||
|
const attributes: attributes = {};
|
||||||
|
for (const { name, value } of Array.from((n as HTMLElement).attributes)) {
|
||||||
|
attributes[name] = value;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: NodeType.Element,
|
||||||
|
tagName,
|
||||||
|
attributes,
|
||||||
|
childNodes: [],
|
||||||
|
};
|
||||||
|
case n.TEXT_NODE:
|
||||||
|
// The parent node may not be a html element which has a tagName attribute.
|
||||||
|
// So just let it be undefined which is ok in this use case.
|
||||||
|
const parentTagName =
|
||||||
|
n.parentNode && (n.parentNode as HTMLElement).tagName;
|
||||||
|
let textContent = (n as Text).textContent;
|
||||||
|
if (parentTagName === 'SCRIPT') {
|
||||||
|
textContent = '';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: NodeType.Text,
|
||||||
|
textContent,
|
||||||
|
};
|
||||||
|
case n.CDATA_SECTION_NODE:
|
||||||
|
return {
|
||||||
|
type: NodeType.CDATA,
|
||||||
|
textContent: '',
|
||||||
|
};
|
||||||
|
case n.COMMENT_NODE:
|
||||||
|
return {
|
||||||
|
type: NodeType.Comment,
|
||||||
|
textContent: (n as Comment).textContent,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function snapshot(n: Node): serializedNodeWithId | null {
|
||||||
|
const _serializedNode = serializeNode(n);
|
||||||
|
if (!_serializedNode) {
|
||||||
|
// TODO: dev only
|
||||||
|
console.warn(n, 'not serialized');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const serializedNode: serializedNodeWithId = Object.assign(_serializedNode, {
|
||||||
|
id: genId(),
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
serializedNode.type === NodeType.Document ||
|
||||||
|
serializedNode.type === NodeType.Element
|
||||||
|
) {
|
||||||
|
for (const childN of Array.from(n.childNodes)) {
|
||||||
|
serializedNode.childNodes.push(snapshot(childN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serializedNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default snapshot;
|
||||||
55
src/types.ts
Normal file
55
src/types.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
export enum NodeType {
|
||||||
|
Document,
|
||||||
|
DocumentType,
|
||||||
|
Element,
|
||||||
|
Text,
|
||||||
|
CDATA,
|
||||||
|
Comment,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type documentNode = {
|
||||||
|
type: NodeType.Document;
|
||||||
|
childNodes: serializedNodeWithId[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type documentTypeNode = {
|
||||||
|
type: NodeType.DocumentType;
|
||||||
|
name: string;
|
||||||
|
publicId: string;
|
||||||
|
systemId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type attributes = {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
export type elementNode = {
|
||||||
|
type: NodeType.Element;
|
||||||
|
tagName: string;
|
||||||
|
attributes: attributes;
|
||||||
|
childNodes: serializedNodeWithId[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type textNode = {
|
||||||
|
type: NodeType.Text;
|
||||||
|
textContent: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type cdataNode = {
|
||||||
|
type: NodeType.CDATA;
|
||||||
|
textContent: '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type commentNode = {
|
||||||
|
type: NodeType.Comment;
|
||||||
|
textContent: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type serializedNode =
|
||||||
|
| documentNode
|
||||||
|
| documentTypeNode
|
||||||
|
| elementNode
|
||||||
|
| textNode
|
||||||
|
| cdataNode
|
||||||
|
| commentNode;
|
||||||
|
|
||||||
|
export type serializedNodeWithId = serializedNode & { id: number };
|
||||||
Reference in New Issue
Block a user