UNPKG

atomico

Version:

Atomico is a small library for the creation of interfaces based on web-components, only using functions and hooks.

220 lines (197 loc) 4.95 kB
import { createElement } from './src/core.js'; const MODE_SLASH = 0; const MODE_TEXT = 1; const MODE_WHITESPACE = 2; const MODE_TAGNAME = 3; const MODE_COMMENT = 4; const MODE_PROP_SET = 5; const MODE_PROP_APPEND = 6; const CHILD_APPEND = 0; const CHILD_RECURSE = 2; const TAG_SET = 3; const PROPS_ASSIGN = 4; const PROP_SET = MODE_PROP_SET; const PROP_APPEND = MODE_PROP_APPEND; const evaluate = (h, built, fields, args) => { let tmp; // `build()` used the first element of the operation list as // temporary workspace. Now that `build()` is done we can use // that space to track whether the current element is "dynamic" // (i.e. it or any of its descendants depend on dynamic values). built[0] = 0; for (let i = 1; i < built.length; i++) { const type = built[i++]; // Set `built[0]`'s appropriate bits if this element depends on a dynamic value. const value = built[i] ? ((built[0] |= type ? 1 : 2), fields[built[i++]]) : built[++i]; if (type === TAG_SET) { args[0] = value; } else if (type === PROPS_ASSIGN) { args[1] = Object.assign(args[1] || {}, value); } else if (type === PROP_SET) { (args[1] = args[1] || {})[built[++i]] = value; } else if (type === PROP_APPEND) { args[1][built[++i]] += (value + ''); } else if (type) { // type === CHILD_RECURSE // Set the operation list (including the staticness bits) as // `this` for the `h` call. tmp = h.apply(value, evaluate(h, value, fields, ['', null])); args.push(tmp); if (value[0]) { // Set the 2nd lowest bit it the child element is dynamic. built[0] |= 2; } else { // Rewrite the operation list in-place if the child element is static. // The currently evaluated piece `CHILD_RECURSE, 0, [...]` becomes // `CHILD_APPEND, 0, tmp`. // Essentially the operation list gets optimized for potential future // re-evaluations. built[i-2] = CHILD_APPEND; built[i] = tmp; } } else { // type === CHILD_APPEND args.push(value); } } return args; }; const build = function(statics) { let mode = MODE_TEXT; let buffer = ''; let quote = ''; let current = [0]; let char, propName; const commit = field => { if (mode === MODE_TEXT && (field || (buffer = buffer.replace(/^\s*\n\s*|\s*\n\s*$/g,'')))) { { current.push(CHILD_APPEND, field, buffer); } } else if (mode === MODE_TAGNAME && (field || buffer)) { { current.push(TAG_SET, field, buffer); } mode = MODE_WHITESPACE; } else if (mode === MODE_WHITESPACE && buffer === '...' && field) { { current.push(PROPS_ASSIGN, field, 0); } } else if (mode === MODE_WHITESPACE && buffer && !field) { { current.push(PROP_SET, 0, true, buffer); } } else if (mode >= MODE_PROP_SET) { { if (buffer || (!field && mode === MODE_PROP_SET)) { current.push(mode, 0, buffer, propName); mode = MODE_PROP_APPEND; } if (field) { current.push(mode, field, 0, propName); mode = MODE_PROP_APPEND; } } } buffer = ''; }; for (let i=0; i<statics.length; i++) { if (i) { if (mode === MODE_TEXT) { commit(); } commit(i); } for (let j=0; j<statics[i].length;j++) { char = statics[i][j]; if (mode === MODE_TEXT) { if (char === '<') { // commit buffer commit(); { current = [current]; } mode = MODE_TAGNAME; } else { buffer += char; } } else if (mode === MODE_COMMENT) { // Ignore everything until the last three characters are '-', '-' and '>' if (buffer === '--' && char === '>') { mode = MODE_TEXT; buffer = ''; } else { buffer = char + buffer[0]; } } else if (quote) { if (char === quote) { quote = ''; } else { buffer += char; } } else if (char === '"' || char === "'") { quote = char; } else if (char === '>') { commit(); mode = MODE_TEXT; } else if (!mode) ; else if (char === '=') { mode = MODE_PROP_SET; propName = buffer; buffer = ''; } else if (char === '/' && (mode < MODE_PROP_SET || statics[i][j+1] === '>')) { commit(); if (mode === MODE_TAGNAME) { current = current[0]; } mode = current; { (current = current[0]).push(CHILD_RECURSE, 0, mode); } mode = MODE_SLASH; } else if (char === ' ' || char === '\t' || char === '\n' || char === '\r') { // <a disabled> commit(); mode = MODE_WHITESPACE; } else { buffer += char; } if (mode === MODE_TAGNAME && buffer === '!--') { mode = MODE_COMMENT; current = current[0]; } } } commit(); return current; }; const CACHE = new Map(); function html(statics) { let tmp = CACHE; tmp = evaluate( createElement, tmp.get(statics) || (tmp.set(statics, (tmp = build(statics))), tmp), arguments, [] ); return tmp.length > 1 ? tmp : tmp[0]; } export { html };