Created
July 11, 2020 17:39
-
-
Save tomhodgins/0c7f3ba2ef9a1e922a1f69bdd8aed354 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 default function html(taggedTemplateString = [''], ...expressions) { | |
const nodes = [] | |
const functions = new Map | |
const strings = typeof taggedTemplateString === 'string' | |
? [taggedTemplateString] | |
: taggedTemplateString | |
function stringifyObject(object = '') { | |
// Null | |
if (object === null) { | |
return '' | |
} | |
// String | |
if (typeof object === 'string') { | |
return object | |
} | |
// Array | |
if (Array.isArray(object)) { | |
return object.map(stringifyObject).join('') | |
} | |
// Non-Array Iterables: e.g. NodeList, HTMLCollection, Set, etc. | |
if (object[Symbol.iterator]) { | |
return stringifyObject([Array.from(object)]) | |
} | |
// Document, DocumentFragment | |
if ([Document, DocumentFragment].some(type => object instanceof type)) { | |
return Array.from(object.childNodes).reduce( | |
(slots, child) => { | |
nodes.push(child) | |
return slots += `<template data-dom-slot="${nodes.length - 1}"></template>` | |
}, | |
'' | |
) | |
} | |
// All other DOM node types: e.g. #text, #comment, Element, etc. | |
if (object instanceof Node) { | |
nodes.push(object) | |
return `<template data-dom-slot="${nodes.length - 1}"></template>` | |
} | |
// Functions (for use with on:* attributes) | |
if (typeof object === 'function') { | |
const name = `--function-reference-${functions.size}` | |
functions.set(name, object) | |
return name | |
} | |
// Otherwise stringify anything else yourself, where you interpolate it, before gets here | |
return String(object) | |
} | |
// Parse a string of text as a fragment of an HTML Document | |
const fragment = document.implementation | |
.createHTMLDocument() | |
.createRange() | |
.createContextualFragment( | |
strings.reduce((markup, string, index) => | |
// Interpolate the stringified results of any JavaScript expressions used | |
markup + stringifyObject(expressions[index - 1]) + string | |
) | |
) | |
// Replace any DOM nodes with stored nodes | |
if (nodes.length) { | |
fragment.querySelectorAll('template[data-dom-slot]').forEach(tag => | |
tag.replaceWith(nodes[tag.dataset.domSlot]) | |
) | |
} | |
// Replace any on:* attribute event handlers | |
if (functions.size) { | |
fragment.querySelectorAll('*').forEach(node => { | |
const onAttributes = Array.from(node.attributes).filter(({name}) => | |
name.startsWith('on:') | |
) | |
if (onAttributes.length) { | |
onAttributes.forEach(({name, value}) => { | |
node.addEventListener( | |
name.replace(/^on:/, '').toLowerCase(), | |
functions.get(value) | |
) | |
node.removeAttribute(name) | |
}) | |
} | |
}) | |
} | |
return fragment | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment