Last active
May 30, 2023 06:09
-
-
Save WebReflection/d3aad260ac5007344a0731e797c8b1a4 to your computer and use it in GitHub Desktop.
hyperHTML, the nitty gritty
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
// used to retrieve template content | |
const templates = new WeakMap; | |
// used to retrieve node updates | |
const updates = new WeakMap; | |
// hyperHTML, the nitty gritty | |
function hyperHTML(chunks, ...interpolations) { | |
// if the static chunks are unknown | |
// (once per application) | |
if (!templates.has(chunks)) { | |
// use modern HTMLTemplateElement ... | |
const template = document.createElement('template'); | |
// ... to reliably inject content joined by a unique comment | |
template.innerHTML = chunks.join('<!--⚡️-->'); | |
// associate unique chunks to ... | |
templates.set(chunks, { | |
// ... the DocumentFragment within the template ... | |
content: template.content, | |
// ... and a list of paths to retrieve nodes to update | |
paths: [].filter.call( | |
template.content.childNodes, | |
// consider only comments with the unique content | |
node => node.nodeType === Node.COMMENT_NODE && | |
node.textContent === '⚡️' | |
).map(node => { | |
// a comment is no layout, but it's handy to have it around | |
// what we want though, is a text node to show our content | |
node = node.parentNode.insertBefore( | |
node.ownerDocument.createTextNode(''), | |
node | |
); | |
// we can now create a path to find the node | |
// which is used once on first template setup | |
// [0, 1] for <p>Hello <!--⚡️--></p> | |
const path = []; | |
do { | |
path.unshift(path.indexOf.call(node.parentNode.childNodes, node)); | |
node = node.parentNode; | |
} while(node !== template.content); | |
return path; | |
}) | |
}); | |
} | |
// if the current context has no updates available ... | |
// (once per node that uses this specific template) | |
if (!updates.has(this) || updates.get(this).chunks !== chunks) { | |
// ... grab related info | |
const info = templates.get(chunks); | |
// ... insert a deep clone of the template content ... | |
this.replaceChildren(info.content.cloneNode(true)); | |
// ... and save details for the next update | |
updates.set(this, { | |
// used to check the node is rendering the expected template | |
chunks, | |
// find all nodes that need to be updated | |
// save it as callback to invoke per each update | |
fns: info.paths.map(path => { | |
const node = path.reduce((p, i) => p.childNodes[i], this); | |
return text => node.textContent = text; | |
}) | |
}); | |
} | |
// we are now sure the template is known | |
// the content of the context is the epxected one | |
// and there is a list of updates to invoke | |
updates.get(this).fns.forEach((fn, i) => fn(interpolations[i])); | |
return this; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example - live on Code Pen