Skip to content

Commit

Permalink
Imrpoved template nesting
Browse files Browse the repository at this point in the history
  • Loading branch information
Lcfvs committed Dec 10, 2020
1 parent 1a586a6 commit 4c458bb
Show file tree
Hide file tree
Showing 4 changed files with 568 additions and 1,029 deletions.
183 changes: 82 additions & 101 deletions lib/dom.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,20 @@
/**
* @name identifier
* @type {RegExp}
*/
const parts = /(^<!doctype [^>]+>|)([\s\S]*)$/i
const identifier = /({[a-z][a-z\d]*(?:\.[a-z][a-z\d]*)*})/gi

/**
* @name symbols
* @type {{node: symbol, window: symbol, render: symbol}}
*/
const symbols = {
export const symbols = {
doctype: Symbol('dom.doctype'),
node: Symbol('dom.node'),
render: Symbol('dom.render'),
source: Symbol('dom.source'),
window: Symbol('dom.window')
}

/**
* @typedef {Object} Template
*/
/**
* @memberOf Template
* @name {@link symbols.node}
* @type {Function}
*/
/**
* @memberOf Template
* @name {@link symbols.render}
* @type {Function}
*/
/**
* @memberOf Template
* @name {@link symbols.window}
* @type {Function}
*/

/**
* @type {Template} proto
*/
const proto = {
[symbols.doctype]: null,
[symbols.node]: null,
[symbols.window]: null,
[symbols.render] () {
const { [symbols.node]: node } = this
const clone = from(this, node.cloneNode(true))

return find([], clone)
.reduce(filler, clone)[symbols.node]
}
[symbols.source]: null,
[symbols.window]: null
}

/**
* @param {Template} template
* @param {Node} node
*/
function filler (template, node) {
const { [symbols.window]: window } = template
const { Node } = window
Expand All @@ -68,9 +30,6 @@ function filler (template, node) {
return template
}

/**
* @param {Template} template
*/
function fill (template) {
const { [symbols.node]: node, [symbols.window]: window } = template
const { document, Node } = window
Expand All @@ -82,26 +41,17 @@ function fill (template) {
const container = document.createDocumentFragment()
const nodes = [value].flat()

nodes.forEach(child => container.appendChild(child[symbols.render]()))
nodes.forEach(child => container.appendChild(render(child)))
node.parentNode.replaceChild(container, node)
} else {
node.textContent = value
}
}

/**
* @param {Node} node
* @return {string[]}
*/
function filter (node) {
return node.textContent.match(identifier)
}

/**
* @param {Template[]} templates
* @param {Template} template
* @return {Template[]}
*/
function find (templates, template) {
const { [symbols.node]: node, [symbols.window]: window } = template
const { nodeType } = node
Expand All @@ -119,41 +69,17 @@ function find (templates, template) {
]
}

/**
* @param {Template} template
* @param {Node} node
* @return {Template}
*/
function from (template, node) {
return {
...template,
[symbols.node]: node
}
}

/**
* @param {Node} node
* @return {Template}
*/
function map (node) {
return from(this, node)
}

/**
* @param {window} window
* @param {string} source
* @param {string} type
* @return {Document|DocumentFragment}
*/
function parse ({ document, DOMParser }, source, type) {
return source.slice(0, 9).toLowerCase() === '<!doctype'
? new DOMParser().parseFromString(source, type)
: document.createRange().createContextualFragment(source)
}

/**
* @param {Template} template
*/
function replace (template) {
const { [symbols.node]: node, [symbols.window]: window } = template
const { document } = window
Expand All @@ -175,11 +101,6 @@ function replace (template) {
parentNode.replaceChild(container, node)
}

/**
* @param {string} content
* @param {Template} template
* @return {boolean|number|string|Template|Template[]}
*/
function resolve (content, template) {
return content.slice(1, -1).split('.')
.reduce((data, name) => {
Expand All @@ -193,28 +114,88 @@ function resolve (content, template) {
}, template)
}

/**
* @param {Window} window
* @param {string} [source]
* @param {Object} [data]
* @param {string} [type
* @return {Template}
*/
export function template (window, source = '', { ...data } = {}, type = 'text/html') {
function content (fragment) {
return fragment.childNodes[0].content
}

function render (template) {
const { [symbols.node]: node } = template
const clone = from(template, node.cloneNode(true))

return find([], clone)
.reduce(filler, clone)[symbols.node]
}

function wrap (source) {
return `<template>${source}</template>`
}

function parse (document, source) {
return document
.createRange()
.createContextualFragment(source)
}

function tree (fragment) {
const [...templates] = fragment.querySelectorAll('template')

templates.forEach(template => {
const { content, parentNode } = template

tree(content)
parentNode.replaceChild(content, template)
})

return fragment
}

function rewrite (source, match) {//console.log(source)
return source.replace(match, wrap(match))
}

function dom ({ document, NodeFilter }, source) {
let [, doctype, contents] = source.match(parts)
const fragment = parse(document, contents)
const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_TEXT)

while (true) {
const node = walker.nextNode()

if (!node) {
break
}

const { nodeValue } = node
const matches = nodeValue.match(identifier) || []

contents = matches.reduce(rewrite, contents)
}

return {
doctype,
template: tree(parse(document, wrap(contents)))
}
}

export function template (window, source = '', { ...data } = {}) {
const { doctype, template } = dom(window, source)

return from({
...data,
...proto,
[symbols.doctype]: doctype,
[symbols.source]: source,
[symbols.window]: window
}, parse(window, source, type))
}, template)
}

/**
* @param {Template} template
* @return {string}
*/
export function serialize (template) {
const { [symbols.window]: { XMLSerializer } } = template
const {
[symbols.doctype]: doctype,
[symbols.window]: { XMLSerializer }
} = template

const source = new XMLSerializer().serializeToString(render(template))

return new XMLSerializer().serializeToString(template[symbols.render]())
return `${doctype}${source}`
}
Loading

0 comments on commit 4c458bb

Please sign in to comment.