Skip to content

Instantly share code, notes, and snippets.

@goto-bus-stop
Created February 13, 2018 21:22
Show Gist options
  • Save goto-bus-stop/5b54d652af860f614a1dcba28eb80691 to your computer and use it in GitHub Desktop.
Save goto-bus-stop/5b54d652af860f614a1dcba28eb80691 to your computer and use it in GitHub Desktop.

a thing similar to lit-html, but it uses hyperx and classic DOM, not the fancy new web components stuff. This approach avoids DOM diffing, instead it keeps references to the places where things might change and updates them directly.

when doing

html`
  <div class=${important ? 'important' : ''}>
    ${message}
  </div>
`

it keeps references to the DOM elements that are affected by the template slots; in this case it would store that index 0 is the class attribute on the root element and index 1 is the first child of the root element. there are some caveats atm that make it basically only usable in a nanocomponent like way, since you need to create an instance of the template in order to rerender.

Basically the html template tag simply returns an object with keys { template, values }, where template is the array of quasi literal strings and values is the array of values. Then you can pass that object to html.render which parses the quasi literals and tracks template slots, and returns an { element, update } pair. you can add the element to the DOM. update takes a { template, values } object from the template tag, so you can rerender using

res.update(html`
  <div class=${'not-important'}>
    ${message.toUpperCase()}
  </div>
`)
var html = require('./index')
var world = 'xyz'
var planet = [
document.createElement('strong')
]
planet[0].textContent = 'planet'
var res = html.render(Main(world, planet))
function Main (world, who) {
return html`
<div id="hello" class='def ${world}'>
hello
${who}
</div>
`
}
setTimeout(function () {
res.update(Main(
"abc",
document.createTextNode('planet')
))
}, 3000)
console.log(res)
document.body.appendChild(res.element)
var assert = require('assert')
var hyperx = require('hyperx')
var PLACEHOLDER = '\0placeholder\0'
function isPlaceholder (value) {
return typeof value === 'string' && /^\0placeholder/.test(value)
}
function getPlaceholderIndex (placeholder) {
return parseInt(placeholder.slice('\0placeholder'.length))
}
function toNode (value) {
var type = typeof value
if (type === 'object' && value.nodeType) {
return value
}
if (type === 'function' || type === 'string' || type === 'boolean' ||
value instanceof RegExp || value instanceof Date) {
value = value.toString()
}
if (typeof value === 'string') {
return document.createTextNode(value)
}
if (Array.isArray(value)) {
return toDocumentFragment(value)
}
}
function toDocumentFragment (nodes) {
var node = document.createDocumentFragment()
for (var i = 0; i < nodes.length; i++) {
node.appendChild(nodes[i])
}
return node
}
var parse = hyperx(function (tagName, props, children) {
var el = document.createElement(tagName)
var editors = []
var names = Object.keys(props)
names.forEach(function (name, i) {
if (isPlaceholder(name)) {
editors.push({
index: getPlaceholderIndex(name),
update: function (nameValue) {
setAttribute(nameValue, props[nameValue])
}
})
return
}
if (isPlaceholder(props[name])) {
editors.push({
index: getPlaceholderIndex(props[name]),
update: function (value) {
setAttribute(name, value)
}
})
return
}
if (/\0placeholder/.test(props[name])) {
props[name].replace(/\0placeholder(\d+)\0/g, function (placeholder) {
editors.push({
index: getPlaceholderIndex(placeholder),
update: updater
})
})
function updater (_, all) {
var value = props[name].replace(/\0placeholder(\d+)\0/g, function (_, index) {
return all[index]
})
setAttribute(name, value)
}
return
}
setAttribute(name, props[name])
})
for (i = 0; i < children.length; i++) {
var child = children[i]
if (isPlaceholder(child)) {
var index = getPlaceholderIndex(child)
child = document.createComment('placeholder')
editors.push({
index: index,
update: (function (oldChild) {
return function (newChild) {
newChild = Array.isArray(newChild) ? newChild.map(toNode) : toNode(newChild)
replaceChild(oldChild, newChild)
oldChild = newChild
}
}(child))
})
} else if (child && child.element && child.editors) {
editors = editors.concat(child.editors)
child = child.element
}
el.appendChild(toNode(child))
}
console.log({ element: el, editors: editors })
return { element: el, editors: editors }
function setAttribute (name, value) {
if (/^on/.test(name)) {
el[name] = value
} else {
el.setAttribute(name, value)
}
}
function replaceChild (oldChild, newChild) {
console.log('replaceChild', oldChild, newChild)
if (Array.isArray(oldChild)) {
while (oldChild.length > 1) {
el.removeChild(oldChild.pop())
}
oldChild = oldChild[0]
}
if (Array.isArray(newChild)) {
newChild = toDocumentFragment(newChild)
}
el.replaceChild(newChild, oldChild)
}
})
function render (tpl) {
var parsed = parse.apply(null, [tpl.template].concat(
tpl.values.map(function (v, index) { return '\0placeholder' + index + '\0' })
))
var updaters = parsed.editors.sort(function (a, b) {
return a.index - b.index
}).map(function (editor) { return editor.update })
update(tpl)
return {
element: parsed.element,
update: update
}
function update (tpl) {
var values = tpl.values
assert.equal(values.length, updaters.length, 'number of values (' + values.length + ') must match number of slots (' + updaters.length + ')')
for (var i = 0; i < values.length; i++) {
updaters[i](values[i], values)
}
}
}
function createElement (tpl) {
var args = []
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i])
}
return { template: tpl, values: args }
}
module.exports = createElement
module.exports.render = render
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment