Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
hyperHTML, the nitty gritty
// used to retrieve template content
const templates = new Map;
// 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);
// ... clean up the node
this.textContent = '';
// ... append a deep clone of the template content ...
this.appendChild(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;
}
@WebReflection
Copy link
Author

WebReflection commented Nov 7, 2017

Example - live on Code Pen

// example
// bind a node to hyperHTML
const render = hyperHTML.bind(document.body);

// update at speed light its content
(function showTime() {
  requestAnimationFrame(showTime);
  render`about the time ${
    (new Date).toISOString().replace(/^.+T|Z$/g, '')
  }`;
}());

@WebReflection
Copy link
Author

WebReflection commented Nov 8, 2017

hyperHTML and its family

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment