Created
January 26, 2018 14:48
-
-
Save johnsylvain/24c93c77a6d1595f33965b5b0d71a7cc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function h (nodeName, attributes, ...children) { | |
children = [].concat.apply([], children) | |
attributes = attributes || {} | |
return typeof nodeName === 'function' | |
? nodeName(attributes, children) | |
: { nodeName, attributes, children } | |
} | |
export function render (parent, newNode, oldNode) { | |
const patches = diff(newNode, oldNode) | |
patch(parent, patches) | |
} | |
function createElement (vnode) { | |
let node = typeof vnode === "string" || typeof vnode === "number" | |
? document.createTextNode(vnode) | |
: document.createElement(vnode.nodeName) | |
if (vnode.attributes) { | |
setAttributes(node, vnode.attributes) | |
vnode.children | |
.map(createElement) | |
.forEach(node.appendChild.bind(node)) | |
} | |
return node | |
} | |
function setAttribute (node, name, value) { | |
if (name === 'className') | |
node.setAttribute('class', value) | |
else if (name === '__html') | |
node.innerHTML = value | |
else | |
node.setAttribute(name, value) | |
} | |
function removeAttribute (node, name, value) { | |
if (name === 'className') | |
node.remove('class') | |
else if (name === '__html') | |
node.innerHTML = '' | |
else | |
node.removeAttribute(name) | |
} | |
function setAttributes (node, attributes) { | |
for (let name in attributes) { | |
if (/^on/.test(name)) | |
setEventListener(node, name, attributes[name]) | |
else | |
setAttribute(node, name, attributes[name]) | |
} | |
} | |
function setEventListener (node, name, value) { | |
node.addEventListener( | |
name.slice(2).toLowerCase(), value | |
) | |
} | |
function diffChildren (newNode, oldNode) { | |
const patches = [] | |
const patchesLength = Math.max( | |
newNode.children.length, | |
oldNode.children.length | |
) | |
for (let i = 0; i < patchesLength; i++) { | |
patches[i] = diff( | |
newNode.children[i], | |
oldNode.children[i] | |
) | |
} | |
return patches | |
} | |
function diffAttributes (newNode, oldNode) { | |
const patches = [] | |
const attributes = Object.assign({}, newNode.attributes, oldNode.attributes) | |
Object.keys(attributes).forEach(name => { | |
const newVal = newNode.attributes[name] | |
const oldVal = oldNode.attributes[name] | |
if (!newVal) | |
patches.push({ type: 'REMOVE_ATTRIBUTE', name, value: oldVal }) | |
else if (!oldVal || oldVal !== newVal) | |
patches.push({ type: 'SET_ATTRIBUTE', name, value: newVal }) | |
}) | |
return patches | |
} | |
function diff (newNode, oldNode) { | |
if (!oldNode) | |
return { type: 'CREATE', newNode } | |
if (!newNode) | |
return { type: 'REMOVE' } | |
if (changed(newNode, oldNode)) | |
return { type: 'REPLACE', newNode } | |
if (newNode.nodeName) | |
return { | |
type: 'UPDATE', | |
children: diffChildren(newNode, oldNode), | |
attributes: diffAttributes(newNode, oldNode) | |
} | |
} | |
function patchAttributes (parent, patches) { | |
for (let i = 0; i < patches.length; i ++) { | |
const attribute = patches[i] | |
const { type, name, value } = attribute | |
if (type === 'SET_ATTRIBUTE') | |
setAttribute(parent, name, value) | |
else if (type === 'REMOVE_ATTRIBUTE') | |
removeAttribute(parent, name, value) | |
} | |
} | |
function patch (parent, patches, index = 0) { | |
if (!patches) return | |
const el = parent.childNodes[index] | |
switch(patches.type) { | |
case 'CREATE': { | |
const { newNode } = patches | |
const newElement = createElement(newNode) | |
return parent.appendChild(newElement) | |
} | |
case 'REMOVE': | |
return parent.removeChild(el) | |
case 'REPLACE': { | |
const { newNode } = patches | |
const newElement = createElement(newNode) | |
return parent.replaceChild(newElement, el) | |
} | |
case 'UPDATE': { | |
const { children, attributes } = patches | |
patchAttributes(el, attributes) | |
for (let i = 0; i < children.length; i++) { | |
patch(el, children[i], i) | |
} | |
} | |
} | |
} | |
function changed (node1, node2) { | |
return typeof node1 !== typeof node2 || | |
typeof node1 === 'string' && node1 !== node2 || | |
node1.nodeName !== node2.nodeName || | |
node1.attributes && node1.attributes.forceUpdate | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment