├── .gitignore ├── favicon.ico ├── index.html ├── README.md ├── src ├── pvdom │ ├── renderNode.js │ ├── patch.js │ ├── pvdom.js │ └── diff.js └── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khmoryz/plain-virtual-dom/HEAD/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plain-virtual-dom 2 | Very small implementation of Virtual DOM 3 | 4 |  5 | 6 | ## ENVIRONMENT 7 | `Node.js:v16.13.1` 8 | 9 | ## INSTALL 10 | `npm install` 11 | 12 | ## SERVE 13 | `npm run serve` 14 | -------------------------------------------------------------------------------- /src/pvdom/renderNode.js: -------------------------------------------------------------------------------- 1 | function renderNode(node) { 2 | const nodeElement = document.createElement(node.tagName); 3 | for (const key in node.attributes) { 4 | nodeElement.setAttribute(key, node.attributes[key]); 5 | } 6 | 7 | node.children.forEach((child) => { 8 | if (child.tagName == "text") { 9 | nodeElement.innerText = child.attributes.content; 10 | return; 11 | } 12 | nodeElement.appendChild(renderNode(child)); 13 | }); 14 | 15 | return nodeElement; 16 | } 17 | 18 | export default renderNode; 19 | -------------------------------------------------------------------------------- /src/pvdom/patch.js: -------------------------------------------------------------------------------- 1 | function patch(target) { 2 | let targetElement; 3 | let newElement; 4 | switch (target.type) { 5 | case "tagName": 6 | targetElement = window.document.getElementById(target.id); 7 | newElement = document.createElement(target.value); 8 | targetElement.replaceWith(newElement); 9 | break; 10 | case "content": 11 | targetElement = window.document.getElementById(target.id); 12 | targetElement.innerText = target.value; 13 | break; 14 | default: 15 | console.error("invalid patch type"); 16 | break; 17 | } 18 | } 19 | 20 | export default patch; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plain-virtual-dom", 3 | "version": "0.0.1", 4 | "description": "Very small implementation of Virtual DOM", 5 | "main": "index.html", 6 | "scripts": { 7 | "serve": "./node_modules/.bin/serve", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/khmory/plain-virtual-dom.git" 13 | }, 14 | "author": "khmory", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/khmory/plain-virtual-dom/issues" 18 | }, 19 | "homepage": "https://github.com/khmory/plain-virtual-dom#readme", 20 | "dependencies": { 21 | "serve": "^13.0.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/pvdom/pvdom.js: -------------------------------------------------------------------------------- 1 | import diff from "./diff.js"; 2 | import renderNode from "./renderNode.js"; 3 | import patch from "./patch.js"; 4 | 5 | // ブラウザ表示中のオブジェクト 6 | let currentVDOM = null; 7 | 8 | function render(vDOM) { 9 | if (currentVDOM === null) { 10 | currentVDOM = JSON.parse(JSON.stringify(vDOM)); 11 | const realElement = renderNode(vDOM); 12 | document.body.appendChild(realElement); 13 | } 14 | 15 | const patchTagets = diff(currentVDOM, vDOM); 16 | if (typeof patchTagets !== "undefined") { 17 | patchTagets.forEach((patchTaget) => { 18 | patch(patchTaget); 19 | }); 20 | } 21 | currentVDOM = JSON.parse(JSON.stringify(vDOM)); 22 | } 23 | 24 | export default { render }; 25 | -------------------------------------------------------------------------------- /src/pvdom/diff.js: -------------------------------------------------------------------------------- 1 | function diff(oldObj, newObj) { 2 | let patchArray = []; 3 | diffNode(oldObj, newObj, patchArray); 4 | return patchArray; 5 | } 6 | 7 | function diffNode(oldNode, newNode, patchArray) { 8 | if (oldNode.tagName != newNode.tagName) { 9 | patchArray.push({ 10 | id: oldNode.attributes.id, 11 | type: "tagName", 12 | value: newNode, 13 | }); 14 | } 15 | if (oldNode.children && newNode.children) { 16 | // contentの更新対象は親ノードのidとなるため、先読みする 17 | for (let i = 0; i < oldNode.children.length; i++) { 18 | if ( 19 | oldNode.children[i].attributes.content != 20 | newNode.children[i].attributes.content 21 | ) { 22 | patchArray.push({ 23 | id: oldNode.attributes.id, 24 | type: "content", 25 | value: newNode.children[i].attributes.content, 26 | }); 27 | } 28 | diffNode(oldNode.children[i], newNode.children[i], patchArray); 29 | } 30 | } 31 | return; 32 | } 33 | 34 | export default diff; 35 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import PVDOM from "./pvdom/pvdom.js"; 2 | 3 | const vDOM = { 4 | tagName: "div", 5 | attributes: { id: "div-id", content: "" }, 6 | children: [ 7 | { 8 | tagName: "h3", 9 | attributes: { id: "p-id", content: "" }, 10 | children: [ 11 | { 12 | tagName: "text", 13 | attributes: { id: "text-id", content: "入力内容: " }, 14 | }, 15 | ], 16 | }, 17 | { 18 | tagName: "input", 19 | attributes: { 20 | id: "input-id", 21 | type: "text", 22 | size: 30, 23 | content: "", 24 | }, 25 | children: [], 26 | }, 27 | ], 28 | }; 29 | 30 | PVDOM.render(vDOM); 31 | 32 | const loop = function() { 33 | PVDOM.render(vDOM); 34 | requestAnimationFrame(loop); 35 | }; 36 | loop(); 37 | 38 | const input = document.getElementById("input-id"); 39 | input.oninput = handleInput; 40 | function handleInput(e) { 41 | if (e.data) { 42 | vDOM.children[0].children[0].attributes.content += e.data; 43 | } 44 | } 45 | --------------------------------------------------------------------------------