Created
September 11, 2020 00:43
-
-
Save tomhodgins/22bf6c9624237ef5a840672d44e314ad to your computer and use it in GitHub Desktop.
This tagged template for HTML literals works in all modern browsers and Deno
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
if ('Deno' in globalThis) { | |
globalThis.DOMParser = (await import('https://deno.land/x/deno_dom/deno-dom-wasm.ts')).DOMParser | |
} | |
function html(taggedTemplateString = [''], ...expressions) { | |
const nodes = [] | |
const functions = new Map | |
const strings = typeof taggedTemplateString === 'string' | |
? [taggedTemplateString] | |
: taggedTemplateString | |
function stringify(object = '') { | |
// Null | |
if (object === null) { | |
return '' | |
} | |
// String | |
if (typeof object === 'string') { | |
return object | |
} | |
// Array | |
if (Array.isArray(object)) { | |
return object.map(stringify).join('') | |
} | |
// Non-Array Iterables: e.g. NodeList, HTMLCollection, Set, etc. | |
if (object[Symbol.iterator]) { | |
return stringify([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 = new DOMParser().parseFromString( | |
strings.reduce((markup, string, index) => | |
markup + stringify(expressions[index - 1]) + string | |
), | |
'text/html' | |
).body | |
// 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 | |
} | |
console.log( | |
Array.from( | |
html`one<p><b>two<p>three`.childNodes, | |
({outerHTML, data}) => outerHTML ?? data | |
).join('') | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment