Skip to content

Commit 6d9291b

Browse files
committed
[add] Core methods of PoC
1 parent 9b34434 commit 6d9291b

File tree

7 files changed

+175
-0
lines changed

7 files changed

+175
-0
lines changed

package.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "dom-renderer",
3+
"version": "2.0.0-alpha.0",
4+
"license": "LGPL-3.0",
5+
"author": "shiy2008@gmail.com",
6+
"description": "DOM Renderer based on TSX & DOM-compatible virtual DOM",
7+
"keywords": [
8+
"DOM",
9+
"render",
10+
"TypeScript",
11+
"JSX",
12+
"vDOM"
13+
],
14+
"homepage": "https://web-cell.dev/DOM-Renderer/",
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/EasyWebApp/DOM-Renderer.git"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/EasyWebApp/DOM-Renderer/issues"
21+
},
22+
"source": "source/index.ts",
23+
"devDependencies": {
24+
"@types/jest": "^25.1.4",
25+
"@types/jsdom": "^16.1.0",
26+
"jest": "^25.1.0",
27+
"jsdom": "^16.2.1",
28+
"lint-staged": "^10.0.8",
29+
"prettier": "^1.19.1",
30+
"ts-jest": "^25.2.1",
31+
"typescript": "^3.8.3"
32+
},
33+
"prettier": {
34+
"singleQuote": true,
35+
"tabWidth": 4
36+
},
37+
"lint-staged": {
38+
"*.{json,ts,tsx}": [
39+
"prettier --write"
40+
]
41+
},
42+
"jest": {
43+
"preset": "ts-jest",
44+
"testEnvironment": "node"
45+
},
46+
"scripts": {
47+
"test": "lint-staged && jest"
48+
}
49+
}

source/factory.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { CustomElementClass, VChild, VNode } from './type';
2+
3+
export function createCell(
4+
tag: string | Function | CustomElementClass,
5+
data?: any,
6+
...childNodes: VChild[]
7+
): VNode {
8+
if (typeof tag === 'function') {
9+
try {
10+
const node = new (tag as CustomElementClass)();
11+
12+
if (node instanceof HTMLElement) tag = node.tagName.toLowerCase();
13+
} catch {}
14+
15+
if (typeof tag === 'function') return (tag as Function)(data);
16+
}
17+
18+
return { ...data, tagName: tag, childNodes };
19+
}

source/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './type';
2+
export * from './renderer';
3+
export * from './factory';

source/renderer.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { VChild } from './type';
2+
3+
const { slice } = Array.prototype;
4+
5+
export function create(vNode: VChild) {
6+
if (typeof vNode === 'string') return document.createTextNode(vNode);
7+
8+
const { tagName, childNodes, ...props } = vNode;
9+
10+
return Object.assign(document.createElement(tagName), props);
11+
}
12+
13+
const cache = new WeakMap();
14+
15+
function save(root: Node, id: string, child: Node) {
16+
var map = cache.get(root);
17+
18+
if (!map) cache.set(root, (map = {}));
19+
20+
map[id] = child;
21+
}
22+
23+
export function update(node: Element, vNode: VChild) {
24+
if (typeof vNode === 'string') return node.replaceWith(vNode);
25+
26+
const { tagName, childNodes, ...props } = vNode;
27+
28+
if (node.tagName?.toLowerCase() !== tagName) {
29+
const tag = document.createElement(tagName);
30+
31+
node.replaceWith(tag);
32+
33+
node = tag;
34+
}
35+
36+
const prop_map = Object.entries(props);
37+
38+
for (const { name } of node.attributes) {
39+
const [key] =
40+
prop_map.find(([key]) => key.toLowerCase() === name) || [];
41+
42+
if (!key) node.removeAttribute(name);
43+
}
44+
45+
for (const [key, value] of prop_map)
46+
if (value !== node[key]) node[key] = value;
47+
48+
const children = node.childNodes;
49+
50+
vNode.childNodes.forEach((child, index) => {
51+
var old = children[index];
52+
53+
if (!old) {
54+
old = create(child);
55+
56+
if (child.id) save(node, child.id, old);
57+
58+
node.append(old);
59+
} else {
60+
const cached = cache.get(node)?.[child.id];
61+
62+
if (cached) {
63+
old.before(cached);
64+
65+
old = cached;
66+
}
67+
}
68+
69+
update(old as Element, child);
70+
});
71+
72+
for (const child of slice.call(children, vNode.childNodes.length))
73+
child.remove();
74+
}

source/type.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export interface VNode {
2+
tagName: string;
3+
childNodes: VNode[];
4+
id?: string;
5+
[key: string]: any;
6+
}
7+
8+
export type VChild = string | VNode;
9+
10+
export interface CustomElementClass {
11+
new (): HTMLElement;
12+
}

test/polyfill.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { JSDOM } from 'jsdom';
2+
3+
const { window } = new JSDOM();
4+
5+
for (const key of ['window', 'document', 'customElements'])
6+
global[key] = window[key];

tsconfig.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES5",
4+
"module": "ES6",
5+
"moduleResolution": "Node",
6+
"esModuleInterop": true,
7+
"downlevelIteration": true,
8+
"jsx": "react",
9+
"jsxFactory": "createCell",
10+
"lib": ["ES2019", "DOM", "DOM.Iterable"]
11+
}
12+
}

0 commit comments

Comments
 (0)