diff --git a/async.js b/async.js index 5e17ffd..084599c 100644 --- a/async.js +++ b/async.js @@ -1 +1 @@ -self.uhtml=function(e){"use strict";const{isArray:t}=Array,n=(e,r)=>{const s=[];for(const{length:l}=e;r{function t(t,n){return e.apply(this,[t].concat(n))}return function(e){return n(arguments,1).then(t.bind(this,e))}},s=e=>({get:t=>e.get(t),set:(t,n)=>(e.set(t,n),n)});const l=/([^\s\\>"'=]+)\s*=\s*(['"]?)$/,o=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,i=/<[a-z][^>]+$/i,a=/>[^<>]*$/,c=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/>)/gi,u=/\s+$/,d=(e,t)=>0o.test(t)?e:`<${t}${n.replace(u,"")}>`;const{isArray:f}=Array,{indexOf:h,slice:m}=[],g=(e,t)=>111===e.nodeType?1/t<0?t?(({firstChild:e,lastChild:t})=>{const n=document.createRange();return n.setStartAfter(e),n.setEndAfter(t),n.deleteContents(),e})(e):e.lastChild:t?e.valueOf():e.firstChild:e;const v=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=f(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}},y=({childNodes:e},t)=>e[t],b=(e,t,n)=>((e,t,n,r,s)=>{const l=n.length;let o=t.length,i=l,a=0,c=0,u=null;for(;as-c){const l=r(t[a],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{n!==!!r&&((n=!!r)?e.setAttribute(t,""):e.removeAttribute(t))})(e,t.slice(1),!1);case".":return((e,t)=>"dataset"===t?(({dataset:e})=>t=>{for(const n in t){const r=t[n];null==r?delete e[n]:e[n]=r}})(e):n=>{e[t]=n})(e,t.slice(1));case"@":return v(e,"on"+t.slice(1));case"o":if("n"===t[1])return v(e,t)}switch(t){case"ref":return(e=>{let t;return n=>{t!==n&&(t=n,"function"==typeof n?n(e):n.current=e)}})(e);case"aria":return(e=>t=>{for(const n in t){const r="role"===n?n:`aria-${n}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=document.createAttributeNS(null,t);return t=>{if(n!==t)if(n=t,null==n)r||(e.removeAttributeNode(s),r=!0);else{const n=t;null==n?(r||e.removeAttributeNode(s),r=!0):(s.value=n,r&&(e.setAttributeNodeNS(s),r=!1))}}})(e,t)};function N(e){const{type:t,path:n}=e,r=n.reduceRight(y,this);return"node"===t?(e=>{let t,n,r=[];const s=l=>{switch(typeof l){case"string":case"number":case"boolean":t!==l&&(t=l,n||(n=document.createTextNode("")),n.data=l,r=b(e,r,[n]));break;case"object":case"undefined":if(null==l){t!=l&&(t=l,r=b(e,r,[]));break}if(f(l)){t=l,0===l.length?r=b(e,r,[]):"object"==typeof l[0]?r=b(e,r,l):s(String(l));break}t!==l&&"ELEMENT_NODE"in l&&(t=l,r=b(e,r,11===l.nodeType?m.call(l.childNodes):[l]));break;case"function":s(l(e))}};return s})(r):"attr"===t?w(r,e.name):(e=>{let t;return n=>{t!=n&&(t=n,e.textContent=null==n?"":n)}})(r)}const $="http://www.w3.org/",x=e=>document.createElementNS($+"1999/xhtml",e),k=(e,t)=>("svg"===t?A:C)(e),C=e=>{const t=x("template");return t.innerHTML=e,t.content},A=e=>{const{content:t}=x("template"),n=x("div");n.innerHTML=''+e+"";const{childNodes:r}=n.firstChild;let{length:s}=r;for(;s--;)t.appendChild(r[0]);return t},E=1!=document.importNode.length,T=E?(e,t,n)=>document.importNode(k(e,t),!0):k,M=E?e=>document.createTreeWalker(e,129,null,!1):e=>document.createTreeWalker(e,129),O=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(h.call(n.childNodes,e)),n=(e=n).parentNode;return t},S="isµ",L=s(new WeakMap),W=/^(?:plaintext|script|style|textarea|title|xmp)$/i,j=(e,t)=>{const n=((e,t,n)=>{const r=[],{length:s}=e;for(let n=1;n`${t}${n-1}=${s||'"'}${r}${s?"":'"'}`)):`${s}\x3c!--${t}${n-1}--\x3e`)}r.push(e[s-1]);const o=r.join("").trim();return n?o:o.replace(c,p)})(t,S,"svg"===e),r=T(n,e),s=M(r),o=[],i=t.length-1;let a=0,u=`isµ${a}`;for(;a{const{content:n,nodes:r}=L.get(t)||L.set(t,j(e,t)),s=document.importNode(n,!0);return{content:s,updates:r.map(N,s)}},P=(e,{type:t,template:n,values:r})=>{const{length:s}=r;z(e,r,s);let{entry:l}=e;l&&l.template===n&&l.type===t||(e.entry=l=((e,t)=>{const{content:n,updates:r}=B(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:o,updates:i,wire:a}=l;for(let e=0;e{const{childNodes:t}=e,{length:n}=t;if(n<2)return n?t[0]:e;const r=m.call(t,0);return{ELEMENT_NODE:1,nodeType:111,firstChild:r[0],lastChild:r[n-1],valueOf(){if(t.length!==n){let t=0;for(;t{for(let r=0;r{const t=s(new WeakMap);return D(((t,...n)=>new H(e,t,n)),{for:{value(n,r){const s=t.get(n)||t.set(n,_(null));return s[r]||(s[r]=(t=>(n,...r)=>P(t,{type:e,template:n,values:r}))({stack:[],entry:null,wire:null}))}},node:{value:(t,...n)=>P({stack:[],entry:null,wire:null},{type:e,template:t,values:n}).valueOf()}})},q=s(new WeakMap),F=R("html"),G=R("svg"),{defineProperties:I}=Object,J=e=>{const t=s(new WeakMap);return I(r(e),{for:{value(n,s){const l=e.for(n,s);return t.get(l)||t.set(l,r(l))}},node:{value:r(e.node)}})},K=J(F),Q=J(G);return e.Hole=H,e.html=K,e.render=(e,t)=>{const n="function"==typeof t?t():t;return Promise.resolve(n).then((t=>((e,t)=>{const n="function"==typeof t?t():t,r=q.get(e)||q.set(e,{stack:[],entry:null,wire:null}),s=n instanceof H?P(r,n):n;return s!==r.wire&&(r.wire=s,e.textContent="",e.appendChild(s.valueOf())),e})(e,t)))},e.svg=Q,e}({}); +self.uhtml=function(e){"use strict";const{isArray:t}=Array,n=(e,r)=>{const s=[];for(const{length:o}=e;r{function t(t,n){return e.apply(this,[t].concat(n))}return function(e){return n(arguments,1).then(t.bind(this,e))}};class s extends Map{set(e,t){return super.set(e,t),t}}class o extends WeakMap{set(e,t){return super.set(e,t),t}}const l=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,i=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g,a=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x00/g,c=/\x00=?/g;const{isArray:u}=Array,{indexOf:d,slice:f}=[],p=(e,t)=>111===e.nodeType?1/t<0?t?(({firstChild:e,lastChild:t})=>{const n=document.createRange();return n.setStartAfter(e),n.setEndAfter(t),n.deleteContents(),e})(e):e.lastChild:t?e.valueOf():e.firstChild:e,h=e=>{const t=document.createElement("template");return t.innerHTML=e,t.content},m=document.createElementNS("http://www.w3.org/2000/svg","svg"),g=document.createRange(),v=e=>(m.innerHTML=e,g.setStartBefore(m.firstChild),g.setEndAfter(m.lastChild),g.extractContents()),b=(e,t)=>("svg"===t?v:h)(e);const y=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=u(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}},w=({childNodes:e},t)=>e[t],x=(e,t,n)=>((e,t,n,r,s)=>{const o=n.length;let l=t.length,i=o,a=0,c=0,u=null;for(;as-c){const o=r(t[a],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{n!==!!r&&((n=!!r)?e.setAttribute(t,""):e.removeAttribute(t))})(e,t.slice(1),!1);case".":return((e,t)=>"dataset"===t?(({dataset:e})=>t=>{for(const n in t){const r=t[n];null==r?delete e[n]:e[n]=r}})(e):n=>{e[t]=n})(e,t.slice(1));case"@":return y(e,"on"+t.slice(1));case"o":if("n"===t[1])return y(e,t)}switch(t){case"ref":return(e=>{let t;return n=>{t!==n&&(t=n,"function"==typeof n?n(e):n.current=e)}})(e);case"aria":return(e=>t=>{for(const n in t){const r="role"===n?n:`aria-${n}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=document.createAttributeNS(null,t);return t=>{if(n!==t)if(n=t,null==n)r||(e.removeAttributeNode(s),r=!0);else{const n=t;null==n?(r||e.removeAttributeNode(s),r=!0):(s.value=n,r&&(e.setAttributeNodeNS(s),r=!1))}}})(e,t)};function N(e){const{type:t,path:n}=e,r=n.reduceRight(w,this);return"node"===t?(e=>{let t,n,r=[];const s=o=>{switch(typeof o){case"string":case"number":case"boolean":t!==o&&(t=o,n||(n=document.createTextNode("")),n.data=o,r=x(e,r,[n]));break;case"object":case"undefined":if(null==o){t!=o&&(t=o,r=x(e,r,[]));break}if(u(o)){t=o,0===o.length?r=x(e,r,[]):"object"==typeof o[0]?r=x(e,r,o):s(String(o));break}t!==o&&"ELEMENT_NODE"in o&&(t=o,r=x(e,r,11===o.nodeType?f.call(o.childNodes):[o]));break;case"function":s(o(e))}};return s})(r):"attr"===t?C(r,e.name):(e=>{let t;return n=>{t!=n&&(t=n,e.textContent=null==n?"":n)}})(r)}const A=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(d.call(n.childNodes,e)),e=n,({parentNode:n}=e);return t},E="isµ",k=new o,T=/^(?:plaintext|script|style|textarea|title|xmp)$/i;class O{constructor(){this.stack=[],this.entry=null,this.wire=null}}const S=(e,t)=>{const n=((e,t,n)=>{let r=0;return e.join("\0").replace(i,((e,t,r,s)=>{let o=t+r.replace(a,"\0=$2$1").trimEnd();return s.length&&(o+=n||l.test(t)?" /":">"})).replace(c,(e=>e.length>1?t+r+++"=":"\x3c!--"+t+r+++"--\x3e"))})(t,E,"svg"===e),r=b(n,e),o=document.createTreeWalker(r,129),u=[],d=new s,f=t.length-1;let p=0,h=`isµ${p}`;for(;p{const{content:n,nodes:r}=k.get(t)||k.set(t,S(e,t)),s=document.importNode(n,!0);return{content:s,updates:r.map(N,s)}},L=(e,{type:t,template:n,values:r})=>{M(e,r);let{entry:s}=e;s&&s.template===n&&s.type===t||(e.entry=s=((e,t)=>{const{content:n,updates:r}=$(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:o,updates:l,wire:i}=s;for(let e=0;e{const{firstChild:t,lastChild:n}=e;if(t===n)return n||e;const{childNodes:r}=e,s=f.call(r,0);return{ELEMENT_NODE:1,nodeType:111,firstChild:t,lastChild:n,valueOf:()=>(r.length!==s.length&&e.append(...s),e)}})(o))},M=({stack:e},t)=>{const{length:n}=t;for(let r=0;r{const t=new o;return P(((t,...n)=>new j(e,t,n)),{for:{value:(n,r)=>{const s=t.get(n)||t.set(n,B(null));return s[r]||(s[r]=(t=>(n,...r)=>L(t,{type:e,template:n,values:r}))(new O))}},node:{value:(t,...n)=>L(new O,{type:e,template:t,values:n}).valueOf()}})},R=new o,_=H("html"),z=H("svg"),{defineProperties:D}=Object,W=e=>{const t=new o;return D(r(e),{for:{value(n,s){const o=e.for(n,s);return t.get(o)||t.set(o,r(o))}},node:{value:r(e.node)}})},q=W(_),F=W(z);return e.Hole=j,e.html=q,e.render=(e,t)=>{const n="function"==typeof t?t():t;return Promise.resolve(n).then((t=>((e,t)=>{const n="function"==typeof t?t():t,r=R.get(e)||R.set(e,new O),s=n instanceof j?L(r,n):n;return s!==r.wire&&(r.wire=s,e.textContent="",e.appendChild(s.valueOf())),e})(e,t)))},e.svg=F,e}({}); diff --git a/cjs/async.js b/cjs/async.js index 416279e..bffb444 100644 --- a/cjs/async.js +++ b/cjs/async.js @@ -1,13 +1,13 @@ 'use strict'; const asyncTag = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('async-tag')); -const umap = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('umap')); +const {WeakMap} = require('@webreflection/dsm'); const {render: $render, html: $html, svg: $svg} = require('./index.js'); const {defineProperties} = Object; const tag = original => { - const wrap = umap(new WeakMap); + const wrap = new WeakMap; return defineProperties( asyncTag(original), { diff --git a/cjs/handlers.js b/cjs/handlers.js index 78277d8..ff0f7c7 100644 --- a/cjs/handlers.js +++ b/cjs/handlers.js @@ -2,7 +2,7 @@ const {isArray, slice} = require('uarray'); const udomdiff = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('udomdiff')); const {aria, attribute, boolean, event, ref, setter, text} = require('uhandlers'); -const {diffable} = require('uwire'); +const {diffable} = require('@webreflection/uwire'); // from a generic path, retrieves the exact targeted node const reducePath = ({childNodes}, i) => childNodes[i]; diff --git a/cjs/index.js b/cjs/index.js index f16a736..2a1efb3 100644 --- a/cjs/index.js +++ b/cjs/index.js @@ -1,6 +1,6 @@ 'use strict'; -const umap = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('umap')); -const {Hole, createCache, unroll} = require('./rabbit.js'); +const {WeakMap} = require('@webreflection/dsm'); +const {Hole, Cache, unroll} = require('./rabbit.js'); const {foreign} = require('uhandlers'); const {create, defineProperties} = Object; @@ -9,7 +9,7 @@ const {create, defineProperties} = Object; // with a `for(ref[, id])` and a `node` tag too const tag = type => { // both `html` and `svg` tags have their own cache - const keyed = umap(new WeakMap); + const keyed = new WeakMap; // keyed operations always re-use the same cache and unroll // the template and its interpolations right away const fixed = cache => (template, ...values) => unroll( @@ -26,9 +26,9 @@ const tag = type => { // which is showing keyed results, and optionally a unique id per each // related node, handy with JSON results and mutable list of objects // that usually carry a unique identifier - value(ref, id) { + value: (ref, id) => { const memo = keyed.get(ref) || keyed.set(ref, create(null)); - return memo[id] || (memo[id] = fixed(createCache())); + return memo[id] || (memo[id] = fixed(new Cache)); } }, node: { @@ -36,7 +36,7 @@ const tag = type => { // this might return the single created node, or a fragment with all // nodes present at the root level and, of course, their child nodes value: (template, ...values) => unroll( - createCache(), + new Cache, {type, template, values} ).valueOf() } @@ -45,7 +45,7 @@ const tag = type => { }; // each rendered node gets its own cache -const cache = umap(new WeakMap); +const cache = new WeakMap; // rendering means understanding what `html` or `svg` tags returned // and it relates a specific node to its own unique cache. @@ -54,7 +54,7 @@ const cache = umap(new WeakMap); // then it's "unrolled" to resolve all its inner nodes. const render = (where, what) => { const hole = typeof what === 'function' ? what() : what; - const info = cache.get(where) || cache.set(where, createCache()); + const info = cache.get(where) || cache.set(where, new Cache); const wire = hole instanceof Hole ? unroll(info, hole) : hole; if (wire !== info.wire) { info.wire = wire; diff --git a/cjs/init.js b/cjs/init.js index 6dd3e13..da60cb9 100644 --- a/cjs/init.js +++ b/cjs/init.js @@ -1,67 +1,29 @@ 'use strict'; -const umap = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('umap')); -const instrument = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('uparser')); +const {WeakMap} = require('@webreflection/dsm'); +const instrument = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/uparser')); const {indexOf, isArray, slice} = require('uarray'); const udomdiff = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('udomdiff')); module.exports = ({document}) => { /**start**/ /*! (c) Andrea Giammarchi - ISC */ -var createContent = (function (document) {'use strict'; - var FRAGMENT = 'fragment'; - var TEMPLATE = 'template'; - var HAS_CONTENT = 'content' in create(TEMPLATE); - - var createHTML = HAS_CONTENT ? - function (html) { - var template = create(TEMPLATE); - template.innerHTML = html; - return template.content; - } : - function (html) { - var content = create(FRAGMENT); - var template = create(TEMPLATE); - var childNodes = null; - if (/^[^\S]*?<(col(?:group)?|t(?:head|body|foot|r|d|h))/i.test(html)) { - var selector = RegExp.$1; - template.innerHTML = '' + html + '
'; - childNodes = template.querySelectorAll(selector); - } else { - template.innerHTML = html; - childNodes = template.childNodes; - } - append(content, childNodes); - return content; - }; - - return function createContent(markup, type) { - return (type === 'svg' ? createSVG : createHTML)(markup); - }; - - function append(root, childNodes) { - var length = childNodes.length; - while (length--) - root.appendChild(childNodes[0]); - } +const createHTML = ml => { + const template = document.createElement('template'); + template.innerHTML = ml; + return template.content; +}; - function create(element) { - return element === FRAGMENT ? - document.createDocumentFragment() : - document.createElementNS('http://www.w3.org/1999/xhtml', element); - } +const root = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); +const range = document.createRange(); +const createSVG = ml => { + root.innerHTML = ml; + range.setStartBefore(svg.firstChild); + range.setEndAfter(svg.lastChild); + return range.extractContents(); +}; - // it could use createElementNS when hasNode is there - // but this fallback is equally fast and easier to maintain - // it is also battle tested already in all IE - function createSVG(svg) { - var content = create(FRAGMENT); - var template = create('div'); - template.innerHTML = '' + svg + ''; - append(content, template.firstChild.childNodes); - return content; - } +const createContent = (ml, type) => (type === 'svg' ? createSVG : createHTML)(ml); -}(document)); @@ -85,24 +47,19 @@ const diffable = (node, operation) => node.nodeType === nodeType ? ; const persistent = fragment => { + const {firstChild, lastChild} = fragment; + if (firstChild === lastChild) + return lastChild || fragment; const {childNodes} = fragment; - const {length} = childNodes; - if (length < 2) - return length ? childNodes[0] : fragment; const nodes = slice.call(childNodes, 0); - const firstChild = nodes[0]; - const lastChild = nodes[length - 1]; return { ELEMENT_NODE, nodeType, firstChild, lastChild, valueOf() { - if (childNodes.length !== length) { - let i = 0; - while (i < length) - fragment.appendChild(nodes[i++]); - } + if (childNodes.length !== nodes.length) + fragment.append(...nodes); return fragment; } }; @@ -110,19 +67,17 @@ const persistent = fragment => { +// flag for foreign checks (slower path, fast by default) +let useForeign = false; + class Foreign { constructor(handler, value) { + useForeign = true; this._ = (...args) => handler(...args, value); } } -// flag for foreign checks (slower path, fast by default) -let useForeign = false; - -const foreign = (handler, value) => { - useForeign = true; - return new Foreign(handler, value); -}; +const foreign = (handler, value) => new Foreign(handler, value); const aria = node => values => { for (const key in values) { @@ -234,30 +189,6 @@ const text = node => { -// this "hack" tells the library if the browser is IE11 or old Edge -const isImportNodeLengthWrong = document.importNode.length != 1; - -// IE11 and old Edge discard empty nodes when cloning, potentially -// resulting in broken paths to find updates. The workaround here -// is to import once, upfront, the fragment that will be cloned -// later on, so that paths are retrieved from one already parsed, -// hence without missing child nodes once re-cloned. -const createFragment = isImportNodeLengthWrong ? - (text, type, normalize) => document.importNode( - createContent(text, type, normalize), - true - ) : - createContent; - -// IE11 and old Edge have a different createTreeWalker signature that -// has been deprecated in other browsers. This export is needed only -// to guarantee the TreeWalker doesn't show warnings and, ultimately, works -const createWalker = isImportNodeLengthWrong ? - fragment => document.createTreeWalker(fragment, 1 | 128, null, false) : - fragment => document.createTreeWalker(fragment, 1 | 128); - - - @@ -413,7 +344,7 @@ const createPath = node => { while (parentNode) { path.push(indexOf.call(parentNode.childNodes, node)); node = parentNode; - parentNode = node.parentNode; + ({parentNode} = node); } return path; }; @@ -431,27 +362,18 @@ const prefix = 'isµ'; // should be parsed once, and once only, as it will always represent the same // content, within the exact same amount of updates each time. // This cache relates each template to its unique content and updates. -const cache = umap(new WeakMap); +const cache = new WeakMap; // a RegExp that helps checking nodes that cannot contain comments const textOnly = /^(?:plaintext|script|style|textarea|title|xmp)$/i; -const createCache = () => ({ - stack: [], // each template gets a stack for each interpolation "hole" - - entry: null, // each entry contains details, such as: - // * the template that is representing - // * the type of node it represents (html or svg) - // * the content fragment with all nodes - // * the list of updates per each node (template holes) - // * the "wired" node or fragment that will get updates - // if the template or type are different from the previous one - // the entry gets re-created each time - - wire: null // each rendered node represent some wired content and - // this reference to the latest one. If different, the node - // will be cleaned up and the new "wire" will be appended -}); +class Cache { + constructor() { + this.stack = []; + this.entry = null; + this.wire = null; + } +} // the entry stored in the rendered node cache, and per each "hole" const createEntry = (type, template) => { @@ -464,11 +386,12 @@ const createEntry = (type, template) => { // operation based on the same template, i.e. data => html`

${data}

` const mapTemplate = (type, template) => { const text = instrument(template, prefix, type === 'svg'); - const content = createFragment(text, type); + const content = createContent(text, type); // once instrumented and reproduced as fragment, it's crawled // to find out where each update is in the fragment tree - const tw = createWalker(content); + const tw = document.createTreeWalker(content, 1 | 128); const nodes = []; + const paths = new Map; const length = template.length - 1; let i = 0; // updates are searched via unique names, linearly increased across the tree @@ -486,7 +409,10 @@ const mapTemplate = (type, template) => { // The only comments to be considered are those // which content is exactly the same as the searched one. if (node.data === search) { - nodes.push({type: 'node', path: createPath(node)}); + nodes.push({ + type: 'node', + path: paths.get(node) || paths.set(node, createPath(node)) + }); search = `${prefix}${++i}`; } } @@ -499,7 +425,7 @@ const mapTemplate = (type, template) => { while (node.hasAttribute(search)) { nodes.push({ type: 'attr', - path: createPath(node), + path: paths.get(node) || paths.set(node, createPath(node)), name: node.getAttribute(search), //svg: svg < 0 ? (svg = ('ownerSVGElement' in node ? 1 : 0)) : svg }); @@ -513,7 +439,10 @@ const mapTemplate = (type, template) => { node.textContent.trim() === `` ){ node.textContent = ''; - nodes.push({type: 'text', path: createPath(node)}); + nodes.push({ + type: 'text', + path: paths.get(node) || paths.set(node, createPath(node)) + }); search = `${prefix}${++i}`; } } @@ -545,20 +474,20 @@ const mapUpdates = (type, template) => { // discover what to do with each interpolation, which will result // into an update operation. const unroll = (info, {type, template, values}) => { - const {length} = values; // interpolations can contain holes and arrays, so these need // to be recursively discovered - unrollValues(info, values, length); + unrollValues(info, values); let {entry} = info; // if the cache entry is either null or different from the template // and the type this unroll should resolve, create a new entry // assigning a new content fragment and the list of updates. if (!entry || (entry.template !== template || entry.type !== type)) info.entry = (entry = createEntry(type, template)); + const {content, updates, wire} = entry; // even if the fragment and its nodes is not live yet, // it is already possible to update via interpolations values. - for (let i = 0; i < length; i++) + for (let i = 0; i < values.length; i++) updates[i](values[i]); // if the entry was new, or representing a different template or type, // create a new persistent entity to use during diffing. @@ -570,23 +499,23 @@ const unroll = (info, {type, template, values}) => { // the stack retains, per each interpolation value, the cache // related to each interpolation value, or null, if the render // was conditional and the value is not special (Array or Hole) -const unrollValues = ({stack}, values, length) => { +const unrollValues = ({stack}, values) => { + const {length} = values; for (let i = 0; i < length; i++) { const hole = values[i]; // each Hole gets unrolled and re-assigned as value // so that domdiff will deal with a node/wire, not with a hole if (hole instanceof Hole) values[i] = unroll( - stack[i] || (stack[i] = createCache()), + stack[i] || (stack[i] = new Cache), hole ); // arrays are recursively resolved so that each entry will contain // also a DOM node or a wire, hence it can be diffed if/when needed else if (isArray(hole)) unrollValues( - stack[i] || (stack[i] = createCache()), - hole, - hole.length + stack[i] || (stack[i] = new Cache), + hole ); // if the value is nothing special, the stack doesn't need to retain data // this is useful also to cleanup previously retained data, if the value @@ -596,22 +525,24 @@ const unrollValues = ({stack}, values, length) => { else stack[i] = null; } + // very dirty but better than splice if (length < stack.length) - stack.splice(length); + stack.length = length; }; -/** - * Holds all details wrappers needed to render the content further on. - * @constructor - * @param {string} type The hole type, either `html` or `svg`. - * @param {string[]} template The template literals used to the define the content. - * @param {Array} values Zero, one, or more interpolated values to render. - */ -function Hole(type, template, values) { - this.type = type; - this.template = template; - this.values = values; -}; +class Hole { + /** + * Holds all details wrappers needed to render the content further on. + * @param {string} type The hole type, either `html` or `svg`. + * @param {string[]} template The template literals used to the define the content. + * @param {Array} values Zero, one, or more interpolated values to render. + */ + constructor(type, template, values) { + this.type = type; + this.template = template; + this.values = values; + } +} @@ -623,7 +554,7 @@ const {create, defineProperties} = Object; // with a `for(ref[, id])` and a `node` tag too const tag = type => { // both `html` and `svg` tags have their own _cache - const keyed = umap(new WeakMap); + const keyed = new WeakMap; // keyed operations always re-use the same _cache and unroll // the template and its interpolations right away const fixed = _cache => (template, ...values) => unroll( @@ -640,9 +571,9 @@ const tag = type => { // which is showing keyed results, and optionally a unique id per each // related node, handy with JSON results and mutable list of objects // that usually carry a unique identifier - value(ref, id) { + value: (ref, id) => { const memo = keyed.get(ref) || keyed.set(ref, create(null)); - return memo[id] || (memo[id] = fixed(createCache())); + return memo[id] || (memo[id] = fixed(new Cache)); } }, node: { @@ -650,7 +581,7 @@ const tag = type => { // this might return the single created node, or a fragment with all // nodes present at the root level and, of course, their child nodes value: (template, ...values) => unroll( - createCache(), + new Cache, {type, template, values} ).valueOf() } @@ -659,7 +590,7 @@ const tag = type => { }; // each rendered node gets its own _cache -const _cache = umap(new WeakMap); +const _cache = new WeakMap; // rendering means understanding what `html` or `svg` tags returned // and it relates a specific node to its own unique _cache. @@ -668,7 +599,7 @@ const _cache = umap(new WeakMap); // then it's "unrolled" to resolve all its inner nodes. const render = (where, what) => { const hole = typeof what === 'function' ? what() : what; - const info = _cache.get(where) || _cache.set(where, createCache()); + const info = _cache.get(where) || _cache.set(where, new Cache); const wire = hole instanceof Hole ? unroll(info, hole) : hole; if (wire !== info.wire) { info.wire = wire; diff --git a/cjs/json.js b/cjs/json.js index fdc1823..c8a6bc2 100644 --- a/cjs/json.js +++ b/cjs/json.js @@ -1,9 +1,9 @@ 'use strict'; -const umap = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('umap')); +const {Map, WeakMap} = require('@webreflection/dsm'); const {render: $render, html, svg} = require('./index.js'); // Sender (SW, Worker, postMessage) -const ids = umap(new WeakMap); +const ids = new WeakMap; let id = 0; const tag = type => (template, ...values) => ({ @@ -18,7 +18,7 @@ html.json = tag('html'); svg.json = tag('svg'); // Receiver (onmessage, from SW, Workers, etc) -const templates = umap(new Map); +const templates = new Map; const unroll = ({type, template, values, id}) => ( (type === 'svg' ? svg : html).apply( diff --git a/cjs/node.js b/cjs/node.js deleted file mode 100644 index d6ef65c..0000000 --- a/cjs/node.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; -const createContent = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@ungap/create-content')); - -// this "hack" tells the library if the browser is IE11 or old Edge -const isImportNodeLengthWrong = document.importNode.length != 1; - -// IE11 and old Edge discard empty nodes when cloning, potentially -// resulting in broken paths to find updates. The workaround here -// is to import once, upfront, the fragment that will be cloned -// later on, so that paths are retrieved from one already parsed, -// hence without missing child nodes once re-cloned. -const createFragment = isImportNodeLengthWrong ? - (text, type, normalize) => document.importNode( - createContent(text, type, normalize), - true - ) : - createContent; -exports.createFragment = createFragment; - -// IE11 and old Edge have a different createTreeWalker signature that -// has been deprecated in other browsers. This export is needed only -// to guarantee the TreeWalker doesn't show warnings and, ultimately, works -const createWalker = isImportNodeLengthWrong ? - fragment => document.createTreeWalker(fragment, 1 | 128, null, false) : - fragment => document.createTreeWalker(fragment, 1 | 128); -exports.createWalker = createWalker; diff --git a/cjs/rabbit.js b/cjs/rabbit.js index 770b1cb..2f2e1d1 100644 --- a/cjs/rabbit.js +++ b/cjs/rabbit.js @@ -1,11 +1,11 @@ 'use strict'; -const umap = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('umap')); -const instrument = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('uparser')); +const {Map, WeakMap} = require('@webreflection/dsm'); +const instrument = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/uparser')); const {indexOf, isArray} = require('uarray'); -const {persistent} = require('uwire'); +const {persistent} = require('@webreflection/uwire'); +const createContent = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/create-content')); const {handlers} = require('./handlers.js'); -const {createFragment, createWalker} = require('./node.js'); // from a fragment container, create an array of indexes // related to its child nodes, so that it's possible @@ -16,7 +16,7 @@ const createPath = node => { while (parentNode) { path.push(indexOf.call(parentNode.childNodes, node)); node = parentNode; - parentNode = node.parentNode; + ({parentNode} = node); } return path; }; @@ -34,28 +34,19 @@ const prefix = 'isµ'; // should be parsed once, and once only, as it will always represent the same // content, within the exact same amount of updates each time. // This cache relates each template to its unique content and updates. -const cache = umap(new WeakMap); +const cache = new WeakMap; // a RegExp that helps checking nodes that cannot contain comments const textOnly = /^(?:plaintext|script|style|textarea|title|xmp)$/i; -const createCache = () => ({ - stack: [], // each template gets a stack for each interpolation "hole" - - entry: null, // each entry contains details, such as: - // * the template that is representing - // * the type of node it represents (html or svg) - // * the content fragment with all nodes - // * the list of updates per each node (template holes) - // * the "wired" node or fragment that will get updates - // if the template or type are different from the previous one - // the entry gets re-created each time - - wire: null // each rendered node represent some wired content and - // this reference to the latest one. If different, the node - // will be cleaned up and the new "wire" will be appended -}); -exports.createCache = createCache; +class Cache { + constructor() { + this.stack = []; + this.entry = null; + this.wire = null; + } +} +exports.Cache = Cache // the entry stored in the rendered node cache, and per each "hole" const createEntry = (type, template) => { @@ -68,11 +59,12 @@ const createEntry = (type, template) => { // operation based on the same template, i.e. data => html`

${data}

` const mapTemplate = (type, template) => { const text = instrument(template, prefix, type === 'svg'); - const content = createFragment(text, type); + const content = createContent(text, type); // once instrumented and reproduced as fragment, it's crawled // to find out where each update is in the fragment tree - const tw = createWalker(content); + const tw = document.createTreeWalker(content, 1 | 128); const nodes = []; + const paths = new Map; const length = template.length - 1; let i = 0; // updates are searched via unique names, linearly increased across the tree @@ -90,7 +82,10 @@ const mapTemplate = (type, template) => { // The only comments to be considered are those // which content is exactly the same as the searched one. if (node.data === search) { - nodes.push({type: 'node', path: createPath(node)}); + nodes.push({ + type: 'node', + path: paths.get(node) || paths.set(node, createPath(node)) + }); search = `${prefix}${++i}`; } } @@ -103,7 +98,7 @@ const mapTemplate = (type, template) => { while (node.hasAttribute(search)) { nodes.push({ type: 'attr', - path: createPath(node), + path: paths.get(node) || paths.set(node, createPath(node)), name: node.getAttribute(search), //svg: svg < 0 ? (svg = ('ownerSVGElement' in node ? 1 : 0)) : svg }); @@ -117,7 +112,10 @@ const mapTemplate = (type, template) => { node.textContent.trim() === `` ){ node.textContent = ''; - nodes.push({type: 'text', path: createPath(node)}); + nodes.push({ + type: 'text', + path: paths.get(node) || paths.set(node, createPath(node)) + }); search = `${prefix}${++i}`; } } @@ -149,20 +147,20 @@ const mapUpdates = (type, template) => { // discover what to do with each interpolation, which will result // into an update operation. const unroll = (info, {type, template, values}) => { - const {length} = values; // interpolations can contain holes and arrays, so these need // to be recursively discovered - unrollValues(info, values, length); + unrollValues(info, values); let {entry} = info; // if the cache entry is either null or different from the template // and the type this unroll should resolve, create a new entry // assigning a new content fragment and the list of updates. if (!entry || (entry.template !== template || entry.type !== type)) info.entry = (entry = createEntry(type, template)); + const {content, updates, wire} = entry; // even if the fragment and its nodes is not live yet, // it is already possible to update via interpolations values. - for (let i = 0; i < length; i++) + for (let i = 0; i < values.length; i++) updates[i](values[i]); // if the entry was new, or representing a different template or type, // create a new persistent entity to use during diffing. @@ -175,23 +173,23 @@ exports.unroll = unroll; // the stack retains, per each interpolation value, the cache // related to each interpolation value, or null, if the render // was conditional and the value is not special (Array or Hole) -const unrollValues = ({stack}, values, length) => { +const unrollValues = ({stack}, values) => { + const {length} = values; for (let i = 0; i < length; i++) { const hole = values[i]; // each Hole gets unrolled and re-assigned as value // so that domdiff will deal with a node/wire, not with a hole if (hole instanceof Hole) values[i] = unroll( - stack[i] || (stack[i] = createCache()), + stack[i] || (stack[i] = new Cache), hole ); // arrays are recursively resolved so that each entry will contain // also a DOM node or a wire, hence it can be diffed if/when needed else if (isArray(hole)) unrollValues( - stack[i] || (stack[i] = createCache()), - hole, - hole.length + stack[i] || (stack[i] = new Cache), + hole ); // if the value is nothing special, the stack doesn't need to retain data // this is useful also to cleanup previously retained data, if the value @@ -201,20 +199,22 @@ const unrollValues = ({stack}, values, length) => { else stack[i] = null; } + // very dirty but better than splice if (length < stack.length) - stack.splice(length); + stack.length = length; }; -/** - * Holds all details wrappers needed to render the content further on. - * @constructor - * @param {string} type The hole type, either `html` or `svg`. - * @param {string[]} template The template literals used to the define the content. - * @param {Array} values Zero, one, or more interpolated values to render. - */ -function Hole(type, template, values) { - this.type = type; - this.template = template; - this.values = values; +class Hole { + /** + * Holds all details wrappers needed to render the content further on. + * @param {string} type The hole type, either `html` or `svg`. + * @param {string[]} template The template literals used to the define the content. + * @param {Array} values Zero, one, or more interpolated values to render. + */ + constructor(type, template, values) { + this.type = type; + this.template = template; + this.values = values; + } } -exports.Hole = Hole; +exports.Hole = Hole diff --git a/es.js b/es.js index 7d8d5fe..8604803 100644 --- a/es.js +++ b/es.js @@ -1 +1 @@ -self.uhtml=function(e){"use strict";var t=e=>({get:t=>e.get(t),set:(t,n)=>(e.set(t,n),n)});const n=/([^\s\\>"'=]+)\s*=\s*(['"]?)$/,r=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,s=/<[a-z][^>]+$/i,l=/>[^<>]*$/,o=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/>)/gi,i=/\s+$/,a=(e,t)=>0r.test(t)?e:`<${t}${n.replace(i,"")}>`;const{isArray:u}=Array,{indexOf:d,slice:p}=[],f=(e,t)=>111===e.nodeType?1/t<0?t?(({firstChild:e,lastChild:t})=>{const n=document.createRange();return n.setStartAfter(e),n.setEndAfter(t),n.deleteContents(),e})(e):e.lastChild:t?e.valueOf():e.firstChild:e;class h{constructor(e,t){this._=(...n)=>e(...n,t)}}let m=!1;const g=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=u(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}},v=({childNodes:e},t)=>e[t],y=(e,t,n)=>((e,t,n,r,s)=>{const l=n.length;let o=t.length,i=l,a=0,c=0,u=null;for(;as-c){const l=r(t[a],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{n!==!!r&&((n=!!r)?e.setAttribute(t,""):e.removeAttribute(t))})(e,t.slice(1),!1);case".":return((e,t)=>"dataset"===t?(({dataset:e})=>t=>{for(const n in t){const r=t[n];null==r?delete e[n]:e[n]=r}})(e):n=>{e[t]=n})(e,t.slice(1));case"@":return g(e,"on"+t.slice(1));case"o":if("n"===t[1])return g(e,t)}switch(t){case"ref":return(e=>{let t;return n=>{t!==n&&(t=n,"function"==typeof n?n(e):n.current=e)}})(e);case"aria":return(e=>t=>{for(const n in t){const r="role"===n?n:`aria-${n}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=document.createAttributeNS(null,t);return l=>{if(n!==l)if(n=l,null==n)r||(e.removeAttributeNode(s),r=!0);else{const n=m&&l instanceof h?l._(e,t):l;null==n?(r||e.removeAttributeNode(s),r=!0):(s.value=n,r&&(e.setAttributeNodeNS(s),r=!1))}}})(e,t)};function w(e){const{type:t,path:n}=e,r=n.reduceRight(v,this);return"node"===t?(e=>{let t,n,r=[];const s=l=>{switch(typeof l){case"string":case"number":case"boolean":t!==l&&(t=l,n||(n=document.createTextNode("")),n.data=l,r=y(e,r,[n]));break;case"object":case"undefined":if(null==l){t!=l&&(t=l,r=y(e,r,[]));break}if(u(l)){t=l,0===l.length?r=y(e,r,[]):"object"==typeof l[0]?r=y(e,r,l):s(String(l));break}t!==l&&"ELEMENT_NODE"in l&&(t=l,r=y(e,r,11===l.nodeType?p.call(l.childNodes):[l]));break;case"function":s(l(e))}};return s})(r):"attr"===t?b(r,e.name):(e=>{let t;return n=>{t!=n&&(t=n,e.textContent=null==n?"":n)}})(r)}const N="http://www.w3.org/",$=e=>document.createElementNS(N+"1999/xhtml",e),x=(e,t)=>("svg"===t?k:C)(e),C=e=>{const t=$("template");return t.innerHTML=e,t.content},k=e=>{const{content:t}=$("template"),n=$("div");n.innerHTML=''+e+"";const{childNodes:r}=n.firstChild;let{length:s}=r;for(;s--;)t.appendChild(r[0]);return t},A=1!=document.importNode.length,E=A?(e,t,n)=>document.importNode(x(e,t),!0):x,T=A?e=>document.createTreeWalker(e,129,null,!1):e=>document.createTreeWalker(e,129),M=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(d.call(n.childNodes,e)),n=(e=n).parentNode;return t},O="isµ",S=t(new WeakMap),L=/^(?:plaintext|script|style|textarea|title|xmp)$/i,W=(e,t)=>{const r=((e,t,r)=>{const s=[],{length:l}=e;for(let r=1;r`${t}${r-1}=${s||'"'}${n}${s?"":'"'}`)):`${l}\x3c!--${t}${r-1}--\x3e`)}s.push(e[l-1]);const i=s.join("").trim();return r?i:i.replace(o,c)})(t,O,"svg"===e),s=E(r,e),l=T(s),i=[],u=t.length-1;let d=0,p=`isµ${d}`;for(;d{const{content:n,nodes:r}=S.get(t)||S.set(t,W(e,t)),s=document.importNode(n,!0);return{content:s,updates:r.map(w,s)}},j=(e,{type:t,template:n,values:r})=>{const{length:s}=r;B(e,r,s);let{entry:l}=e;l&&l.template===n&&l.type===t||(e.entry=l=((e,t)=>{const{content:n,updates:r}=_(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:o,updates:i,wire:a}=l;for(let e=0;e{const{childNodes:t}=e,{length:n}=t;if(n<2)return n?t[0]:e;const r=p.call(t,0);return{ELEMENT_NODE:1,nodeType:111,firstChild:r[0],lastChild:r[n-1],valueOf(){if(t.length!==n){let t=0;for(;t{for(let r=0;r{const n=t(new WeakMap);return D(((t,...n)=>new z(e,t,n)),{for:{value(t,r){const s=n.get(t)||n.set(t,H(null));return s[r]||(s[r]=(t=>(n,...r)=>j(t,{type:e,template:n,values:r}))({stack:[],entry:null,wire:null}))}},node:{value:(t,...n)=>j({stack:[],entry:null,wire:null},{type:e,template:t,values:n}).valueOf()}})},P=t(new WeakMap),q=R("html"),F=R("svg");return e.Hole=z,e.foreign=(e,t)=>(m=!0,new h(e,t)),e.html=q,e.render=(e,t)=>{const n="function"==typeof t?t():t,r=P.get(e)||P.set(e,{stack:[],entry:null,wire:null}),s=n instanceof z?j(r,n):n;return s!==r.wire&&(r.wire=s,e.textContent="",e.appendChild(s.valueOf())),e},e.svg=F,e}({}); +class e extends Map{set(e,t){return super.set(e,t),t}}class t extends WeakMap{set(e,t){return super.set(e,t),t}}const n=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,r=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g,s=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x00/g,l=/\x00=?/g;const{isArray:o}=Array,{indexOf:a,slice:i}=[],c=(e,t)=>111===e.nodeType?1/t<0?t?(({firstChild:e,lastChild:t})=>{const n=document.createRange();return n.setStartAfter(e),n.setEndAfter(t),n.deleteContents(),e})(e):e.lastChild:t?e.valueOf():e.firstChild:e,u=e=>{const t=document.createElement("template");return t.innerHTML=e,t.content},d=document.createElementNS("http://www.w3.org/2000/svg","svg"),p=document.createRange(),f=e=>(d.innerHTML=e,p.setStartBefore(d.firstChild),p.setEndAfter(d.lastChild),p.extractContents()),h=(e,t)=>("svg"===t?f:u)(e);let m=!1;class g{constructor(e,t){m=!0,this._=(...n)=>e(...n,t)}}const b=(e,t)=>new g(e,t),w=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=o(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}},v=({childNodes:e},t)=>e[t],y=(e,t,n)=>((e,t,n,r,s)=>{const l=n.length;let o=t.length,a=l,i=0,c=0,u=null;for(;is-c){const l=r(t[i],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{n!==!!r&&((n=!!r)?e.setAttribute(t,""):e.removeAttribute(t))})(e,t.slice(1),!1);case".":return((e,t)=>"dataset"===t?(({dataset:e})=>t=>{for(const n in t){const r=t[n];null==r?delete e[n]:e[n]=r}})(e):n=>{e[t]=n})(e,t.slice(1));case"@":return w(e,"on"+t.slice(1));case"o":if("n"===t[1])return w(e,t)}switch(t){case"ref":return(e=>{let t;return n=>{t!==n&&(t=n,"function"==typeof n?n(e):n.current=e)}})(e);case"aria":return(e=>t=>{for(const n in t){const r="role"===n?n:`aria-${n}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=document.createAttributeNS(null,t);return l=>{if(n!==l)if(n=l,null==n)r||(e.removeAttributeNode(s),r=!0);else{const n=m&&l instanceof g?l._(e,t):l;null==n?(r||e.removeAttributeNode(s),r=!0):(s.value=n,r&&(e.setAttributeNodeNS(s),r=!1))}}})(e,t)};function C(e){const{type:t,path:n}=e,r=n.reduceRight(v,this);return"node"===t?(e=>{let t,n,r=[];const s=l=>{switch(typeof l){case"string":case"number":case"boolean":t!==l&&(t=l,n||(n=document.createTextNode("")),n.data=l,r=y(e,r,[n]));break;case"object":case"undefined":if(null==l){t!=l&&(t=l,r=y(e,r,[]));break}if(o(l)){t=l,0===l.length?r=y(e,r,[]):"object"==typeof l[0]?r=y(e,r,l):s(String(l));break}t!==l&&"ELEMENT_NODE"in l&&(t=l,r=y(e,r,11===l.nodeType?i.call(l.childNodes):[l]));break;case"function":s(l(e))}};return s})(r):"attr"===t?x(r,e.name):(e=>{let t;return n=>{t!=n&&(t=n,e.textContent=null==n?"":n)}})(r)}const N=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(a.call(n.childNodes,e)),e=n,({parentNode:n}=e);return t},A=new t,E=/^(?:plaintext|script|style|textarea|title|xmp)$/i;class k{constructor(){this.stack=[],this.entry=null,this.wire=null}}const T=(t,o)=>{const a=((e,t,o)=>{let a=0;return e.join("\0").replace(r,((e,t,r,l)=>{let a=t+r.replace(s,"\0=$2$1").trimEnd();return l.length&&(a+=o||n.test(t)?" /":">"})).replace(l,(e=>e.length>1?t+a+++"=":"\x3c!--"+t+a+++"--\x3e"))})(o,"isµ","svg"===t),i=h(a,t),c=document.createTreeWalker(i,129),u=[],d=new e,p=o.length-1;let f=0,m=`isµ${f}`;for(;f{const{content:n,nodes:r}=A.get(t)||A.set(t,T(e,t)),s=document.importNode(n,!0);return{content:s,updates:r.map(C,s)}},O=(e,{type:t,template:n,values:r})=>{$(e,r);let{entry:s}=e;s&&s.template===n&&s.type===t||(e.entry=s=((e,t)=>{const{content:n,updates:r}=S(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:l,updates:o,wire:a}=s;for(let e=0;e{const{firstChild:t,lastChild:n}=e;if(t===n)return n||e;const{childNodes:r}=e,s=i.call(r,0);return{ELEMENT_NODE:1,nodeType:111,firstChild:t,lastChild:n,valueOf:()=>(r.length!==s.length&&e.append(...s),e)}})(l))},$=({stack:e},t)=>{const{length:n}=t;for(let r=0;r{const n=new t;return B(((t,...n)=>new L(e,t,n)),{for:{value:(t,r)=>{const s=n.get(t)||n.set(t,M(null));return s[r]||(s[r]=(t=>(n,...r)=>O(t,{type:e,template:n,values:r}))(new k))}},node:{value:(t,...n)=>O(new k,{type:e,template:t,values:n}).valueOf()}})},j=new t,R=(e,t)=>{const n="function"==typeof t?t():t,r=j.get(e)||j.set(e,new k),s=n instanceof L?O(r,n):n;return s!==r.wire&&(r.wire=s,e.textContent="",e.appendChild(s.valueOf())),e},z=_("html"),D=_("svg");export{L as Hole,b as foreign,z as html,R as render,D as svg}; diff --git a/esm/async.js b/esm/async.js index 10a14dc..2bb7e46 100644 --- a/esm/async.js +++ b/esm/async.js @@ -1,12 +1,12 @@ import asyncTag from 'async-tag'; -import umap from 'umap'; +import {WeakMap} from '@webreflection/dsm'; import {render as $render, html as $html, svg as $svg} from './index.js'; const {defineProperties} = Object; const tag = original => { - const wrap = umap(new WeakMap); + const wrap = new WeakMap; return defineProperties( asyncTag(original), { diff --git a/esm/handlers.js b/esm/handlers.js index b429fff..dea47ea 100644 --- a/esm/handlers.js +++ b/esm/handlers.js @@ -1,7 +1,7 @@ import {isArray, slice} from 'uarray'; import udomdiff from 'udomdiff'; import {aria, attribute, boolean, event, ref, setter, text} from 'uhandlers'; -import {diffable} from 'uwire'; +import {diffable} from '@webreflection/uwire'; // from a generic path, retrieves the exact targeted node const reducePath = ({childNodes}, i) => childNodes[i]; diff --git a/esm/index.js b/esm/index.js index 15c0724..50e1041 100644 --- a/esm/index.js +++ b/esm/index.js @@ -1,5 +1,5 @@ -import umap from 'umap'; -import {Hole, createCache, unroll} from './rabbit.js'; +import {WeakMap} from '@webreflection/dsm'; +import {Hole, Cache, unroll} from './rabbit.js'; import {foreign} from 'uhandlers'; const {create, defineProperties} = Object; @@ -8,7 +8,7 @@ const {create, defineProperties} = Object; // with a `for(ref[, id])` and a `node` tag too const tag = type => { // both `html` and `svg` tags have their own cache - const keyed = umap(new WeakMap); + const keyed = new WeakMap; // keyed operations always re-use the same cache and unroll // the template and its interpolations right away const fixed = cache => (template, ...values) => unroll( @@ -25,9 +25,9 @@ const tag = type => { // which is showing keyed results, and optionally a unique id per each // related node, handy with JSON results and mutable list of objects // that usually carry a unique identifier - value(ref, id) { + value: (ref, id) => { const memo = keyed.get(ref) || keyed.set(ref, create(null)); - return memo[id] || (memo[id] = fixed(createCache())); + return memo[id] || (memo[id] = fixed(new Cache)); } }, node: { @@ -35,7 +35,7 @@ const tag = type => { // this might return the single created node, or a fragment with all // nodes present at the root level and, of course, their child nodes value: (template, ...values) => unroll( - createCache(), + new Cache, {type, template, values} ).valueOf() } @@ -44,7 +44,7 @@ const tag = type => { }; // each rendered node gets its own cache -const cache = umap(new WeakMap); +const cache = new WeakMap; // rendering means understanding what `html` or `svg` tags returned // and it relates a specific node to its own unique cache. @@ -53,7 +53,7 @@ const cache = umap(new WeakMap); // then it's "unrolled" to resolve all its inner nodes. const render = (where, what) => { const hole = typeof what === 'function' ? what() : what; - const info = cache.get(where) || cache.set(where, createCache()); + const info = cache.get(where) || cache.set(where, new Cache); const wire = hole instanceof Hole ? unroll(info, hole) : hole; if (wire !== info.wire) { info.wire = wire; diff --git a/esm/init.js b/esm/init.js index 848a4cd..5a5e8d0 100644 --- a/esm/init.js +++ b/esm/init.js @@ -1,66 +1,28 @@ -import umap from 'umap'; -import instrument from 'uparser'; +import {WeakMap} from '@webreflection/dsm'; +import instrument from '@webreflection/uparser'; import {indexOf, isArray, slice} from 'uarray'; import udomdiff from 'udomdiff'; export default ({document}) => { /**start**/ /*! (c) Andrea Giammarchi - ISC */ -var createContent = (function (document) {'use strict'; - var FRAGMENT = 'fragment'; - var TEMPLATE = 'template'; - var HAS_CONTENT = 'content' in create(TEMPLATE); - - var createHTML = HAS_CONTENT ? - function (html) { - var template = create(TEMPLATE); - template.innerHTML = html; - return template.content; - } : - function (html) { - var content = create(FRAGMENT); - var template = create(TEMPLATE); - var childNodes = null; - if (/^[^\S]*?<(col(?:group)?|t(?:head|body|foot|r|d|h))/i.test(html)) { - var selector = RegExp.$1; - template.innerHTML = '' + html + '
'; - childNodes = template.querySelectorAll(selector); - } else { - template.innerHTML = html; - childNodes = template.childNodes; - } - append(content, childNodes); - return content; - }; - - return function createContent(markup, type) { - return (type === 'svg' ? createSVG : createHTML)(markup); - }; - - function append(root, childNodes) { - var length = childNodes.length; - while (length--) - root.appendChild(childNodes[0]); - } +const createHTML = ml => { + const template = document.createElement('template'); + template.innerHTML = ml; + return template.content; +}; - function create(element) { - return element === FRAGMENT ? - document.createDocumentFragment() : - document.createElementNS('http://www.w3.org/1999/xhtml', element); - } +const root = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); +const range = document.createRange(); +const createSVG = ml => { + root.innerHTML = ml; + range.setStartBefore(svg.firstChild); + range.setEndAfter(svg.lastChild); + return range.extractContents(); +}; - // it could use createElementNS when hasNode is there - // but this fallback is equally fast and easier to maintain - // it is also battle tested already in all IE - function createSVG(svg) { - var content = create(FRAGMENT); - var template = create('div'); - template.innerHTML = '' + svg + ''; - append(content, template.firstChild.childNodes); - return content; - } +const createContent = (ml, type) => (type === 'svg' ? createSVG : createHTML)(ml); -}(document)); @@ -84,24 +46,19 @@ const diffable = (node, operation) => node.nodeType === nodeType ? ; const persistent = fragment => { + const {firstChild, lastChild} = fragment; + if (firstChild === lastChild) + return lastChild || fragment; const {childNodes} = fragment; - const {length} = childNodes; - if (length < 2) - return length ? childNodes[0] : fragment; const nodes = slice.call(childNodes, 0); - const firstChild = nodes[0]; - const lastChild = nodes[length - 1]; return { ELEMENT_NODE, nodeType, firstChild, lastChild, valueOf() { - if (childNodes.length !== length) { - let i = 0; - while (i < length) - fragment.appendChild(nodes[i++]); - } + if (childNodes.length !== nodes.length) + fragment.append(...nodes); return fragment; } }; @@ -109,19 +66,17 @@ const persistent = fragment => { +// flag for foreign checks (slower path, fast by default) +let useForeign = false; + class Foreign { constructor(handler, value) { + useForeign = true; this._ = (...args) => handler(...args, value); } } -// flag for foreign checks (slower path, fast by default) -let useForeign = false; - -const foreign = (handler, value) => { - useForeign = true; - return new Foreign(handler, value); -}; +const foreign = (handler, value) => new Foreign(handler, value); const aria = node => values => { for (const key in values) { @@ -233,30 +188,6 @@ const text = node => { -// this "hack" tells the library if the browser is IE11 or old Edge -const isImportNodeLengthWrong = document.importNode.length != 1; - -// IE11 and old Edge discard empty nodes when cloning, potentially -// resulting in broken paths to find updates. The workaround here -// is to import once, upfront, the fragment that will be cloned -// later on, so that paths are retrieved from one already parsed, -// hence without missing child nodes once re-cloned. -const createFragment = isImportNodeLengthWrong ? - (text, type, normalize) => document.importNode( - createContent(text, type, normalize), - true - ) : - createContent; - -// IE11 and old Edge have a different createTreeWalker signature that -// has been deprecated in other browsers. This export is needed only -// to guarantee the TreeWalker doesn't show warnings and, ultimately, works -const createWalker = isImportNodeLengthWrong ? - fragment => document.createTreeWalker(fragment, 1 | 128, null, false) : - fragment => document.createTreeWalker(fragment, 1 | 128); - - - @@ -412,7 +343,7 @@ const createPath = node => { while (parentNode) { path.push(indexOf.call(parentNode.childNodes, node)); node = parentNode; - parentNode = node.parentNode; + ({parentNode} = node); } return path; }; @@ -430,27 +361,18 @@ const prefix = 'isµ'; // should be parsed once, and once only, as it will always represent the same // content, within the exact same amount of updates each time. // This cache relates each template to its unique content and updates. -const cache = umap(new WeakMap); +const cache = new WeakMap; // a RegExp that helps checking nodes that cannot contain comments const textOnly = /^(?:plaintext|script|style|textarea|title|xmp)$/i; -const createCache = () => ({ - stack: [], // each template gets a stack for each interpolation "hole" - - entry: null, // each entry contains details, such as: - // * the template that is representing - // * the type of node it represents (html or svg) - // * the content fragment with all nodes - // * the list of updates per each node (template holes) - // * the "wired" node or fragment that will get updates - // if the template or type are different from the previous one - // the entry gets re-created each time - - wire: null // each rendered node represent some wired content and - // this reference to the latest one. If different, the node - // will be cleaned up and the new "wire" will be appended -}); +class Cache { + constructor() { + this.stack = []; + this.entry = null; + this.wire = null; + } +} // the entry stored in the rendered node cache, and per each "hole" const createEntry = (type, template) => { @@ -463,11 +385,12 @@ const createEntry = (type, template) => { // operation based on the same template, i.e. data => html`

${data}

` const mapTemplate = (type, template) => { const text = instrument(template, prefix, type === 'svg'); - const content = createFragment(text, type); + const content = createContent(text, type); // once instrumented and reproduced as fragment, it's crawled // to find out where each update is in the fragment tree - const tw = createWalker(content); + const tw = document.createTreeWalker(content, 1 | 128); const nodes = []; + const paths = new Map; const length = template.length - 1; let i = 0; // updates are searched via unique names, linearly increased across the tree @@ -485,7 +408,10 @@ const mapTemplate = (type, template) => { // The only comments to be considered are those // which content is exactly the same as the searched one. if (node.data === search) { - nodes.push({type: 'node', path: createPath(node)}); + nodes.push({ + type: 'node', + path: paths.get(node) || paths.set(node, createPath(node)) + }); search = `${prefix}${++i}`; } } @@ -498,7 +424,7 @@ const mapTemplate = (type, template) => { while (node.hasAttribute(search)) { nodes.push({ type: 'attr', - path: createPath(node), + path: paths.get(node) || paths.set(node, createPath(node)), name: node.getAttribute(search), //svg: svg < 0 ? (svg = ('ownerSVGElement' in node ? 1 : 0)) : svg }); @@ -512,7 +438,10 @@ const mapTemplate = (type, template) => { node.textContent.trim() === `` ){ node.textContent = ''; - nodes.push({type: 'text', path: createPath(node)}); + nodes.push({ + type: 'text', + path: paths.get(node) || paths.set(node, createPath(node)) + }); search = `${prefix}${++i}`; } } @@ -544,20 +473,20 @@ const mapUpdates = (type, template) => { // discover what to do with each interpolation, which will result // into an update operation. const unroll = (info, {type, template, values}) => { - const {length} = values; // interpolations can contain holes and arrays, so these need // to be recursively discovered - unrollValues(info, values, length); + unrollValues(info, values); let {entry} = info; // if the cache entry is either null or different from the template // and the type this unroll should resolve, create a new entry // assigning a new content fragment and the list of updates. if (!entry || (entry.template !== template || entry.type !== type)) info.entry = (entry = createEntry(type, template)); + const {content, updates, wire} = entry; // even if the fragment and its nodes is not live yet, // it is already possible to update via interpolations values. - for (let i = 0; i < length; i++) + for (let i = 0; i < values.length; i++) updates[i](values[i]); // if the entry was new, or representing a different template or type, // create a new persistent entity to use during diffing. @@ -569,23 +498,23 @@ const unroll = (info, {type, template, values}) => { // the stack retains, per each interpolation value, the cache // related to each interpolation value, or null, if the render // was conditional and the value is not special (Array or Hole) -const unrollValues = ({stack}, values, length) => { +const unrollValues = ({stack}, values) => { + const {length} = values; for (let i = 0; i < length; i++) { const hole = values[i]; // each Hole gets unrolled and re-assigned as value // so that domdiff will deal with a node/wire, not with a hole if (hole instanceof Hole) values[i] = unroll( - stack[i] || (stack[i] = createCache()), + stack[i] || (stack[i] = new Cache), hole ); // arrays are recursively resolved so that each entry will contain // also a DOM node or a wire, hence it can be diffed if/when needed else if (isArray(hole)) unrollValues( - stack[i] || (stack[i] = createCache()), - hole, - hole.length + stack[i] || (stack[i] = new Cache), + hole ); // if the value is nothing special, the stack doesn't need to retain data // this is useful also to cleanup previously retained data, if the value @@ -595,22 +524,24 @@ const unrollValues = ({stack}, values, length) => { else stack[i] = null; } + // very dirty but better than splice if (length < stack.length) - stack.splice(length); + stack.length = length; }; -/** - * Holds all details wrappers needed to render the content further on. - * @constructor - * @param {string} type The hole type, either `html` or `svg`. - * @param {string[]} template The template literals used to the define the content. - * @param {Array} values Zero, one, or more interpolated values to render. - */ -function Hole(type, template, values) { - this.type = type; - this.template = template; - this.values = values; -}; +class Hole { + /** + * Holds all details wrappers needed to render the content further on. + * @param {string} type The hole type, either `html` or `svg`. + * @param {string[]} template The template literals used to the define the content. + * @param {Array} values Zero, one, or more interpolated values to render. + */ + constructor(type, template, values) { + this.type = type; + this.template = template; + this.values = values; + } +} @@ -622,7 +553,7 @@ const {create, defineProperties} = Object; // with a `for(ref[, id])` and a `node` tag too const tag = type => { // both `html` and `svg` tags have their own _cache - const keyed = umap(new WeakMap); + const keyed = new WeakMap; // keyed operations always re-use the same _cache and unroll // the template and its interpolations right away const fixed = _cache => (template, ...values) => unroll( @@ -639,9 +570,9 @@ const tag = type => { // which is showing keyed results, and optionally a unique id per each // related node, handy with JSON results and mutable list of objects // that usually carry a unique identifier - value(ref, id) { + value: (ref, id) => { const memo = keyed.get(ref) || keyed.set(ref, create(null)); - return memo[id] || (memo[id] = fixed(createCache())); + return memo[id] || (memo[id] = fixed(new Cache)); } }, node: { @@ -649,7 +580,7 @@ const tag = type => { // this might return the single created node, or a fragment with all // nodes present at the root level and, of course, their child nodes value: (template, ...values) => unroll( - createCache(), + new Cache, {type, template, values} ).valueOf() } @@ -658,7 +589,7 @@ const tag = type => { }; // each rendered node gets its own _cache -const _cache = umap(new WeakMap); +const _cache = new WeakMap; // rendering means understanding what `html` or `svg` tags returned // and it relates a specific node to its own unique _cache. @@ -667,7 +598,7 @@ const _cache = umap(new WeakMap); // then it's "unrolled" to resolve all its inner nodes. const render = (where, what) => { const hole = typeof what === 'function' ? what() : what; - const info = _cache.get(where) || _cache.set(where, createCache()); + const info = _cache.get(where) || _cache.set(where, new Cache); const wire = hole instanceof Hole ? unroll(info, hole) : hole; if (wire !== info.wire) { info.wire = wire; diff --git a/esm/json.js b/esm/json.js index 33f8ed9..f5d21bf 100644 --- a/esm/json.js +++ b/esm/json.js @@ -1,8 +1,8 @@ -import umap from 'umap'; +import {Map, WeakMap} from '@webreflection/dsm'; import {render as $render, html, svg} from './index.js'; // Sender (SW, Worker, postMessage) -const ids = umap(new WeakMap); +const ids = new WeakMap; let id = 0; const tag = type => (template, ...values) => ({ @@ -17,7 +17,7 @@ html.json = tag('html'); svg.json = tag('svg'); // Receiver (onmessage, from SW, Workers, etc) -const templates = umap(new Map); +const templates = new Map; const unroll = ({type, template, values, id}) => ( (type === 'svg' ? svg : html).apply( diff --git a/esm/node.js b/esm/node.js deleted file mode 100644 index 21ca5a1..0000000 --- a/esm/node.js +++ /dev/null @@ -1,23 +0,0 @@ -import createContent from '@ungap/create-content'; - -// this "hack" tells the library if the browser is IE11 or old Edge -const isImportNodeLengthWrong = document.importNode.length != 1; - -// IE11 and old Edge discard empty nodes when cloning, potentially -// resulting in broken paths to find updates. The workaround here -// is to import once, upfront, the fragment that will be cloned -// later on, so that paths are retrieved from one already parsed, -// hence without missing child nodes once re-cloned. -export const createFragment = isImportNodeLengthWrong ? - (text, type, normalize) => document.importNode( - createContent(text, type, normalize), - true - ) : - createContent; - -// IE11 and old Edge have a different createTreeWalker signature that -// has been deprecated in other browsers. This export is needed only -// to guarantee the TreeWalker doesn't show warnings and, ultimately, works -export const createWalker = isImportNodeLengthWrong ? - fragment => document.createTreeWalker(fragment, 1 | 128, null, false) : - fragment => document.createTreeWalker(fragment, 1 | 128); diff --git a/esm/rabbit.js b/esm/rabbit.js index 6d208cb..0565187 100644 --- a/esm/rabbit.js +++ b/esm/rabbit.js @@ -1,10 +1,10 @@ -import umap from 'umap'; -import instrument from 'uparser'; +import {Map, WeakMap} from '@webreflection/dsm'; +import instrument from '@webreflection/uparser'; import {indexOf, isArray} from 'uarray'; -import {persistent} from 'uwire'; +import {persistent} from '@webreflection/uwire'; +import createContent from '@webreflection/create-content'; import {handlers} from './handlers.js'; -import {createFragment, createWalker} from './node.js'; // from a fragment container, create an array of indexes // related to its child nodes, so that it's possible @@ -15,7 +15,7 @@ const createPath = node => { while (parentNode) { path.push(indexOf.call(parentNode.childNodes, node)); node = parentNode; - parentNode = node.parentNode; + ({parentNode} = node); } return path; }; @@ -33,27 +33,18 @@ const prefix = 'isµ'; // should be parsed once, and once only, as it will always represent the same // content, within the exact same amount of updates each time. // This cache relates each template to its unique content and updates. -const cache = umap(new WeakMap); +const cache = new WeakMap; // a RegExp that helps checking nodes that cannot contain comments const textOnly = /^(?:plaintext|script|style|textarea|title|xmp)$/i; -export const createCache = () => ({ - stack: [], // each template gets a stack for each interpolation "hole" - - entry: null, // each entry contains details, such as: - // * the template that is representing - // * the type of node it represents (html or svg) - // * the content fragment with all nodes - // * the list of updates per each node (template holes) - // * the "wired" node or fragment that will get updates - // if the template or type are different from the previous one - // the entry gets re-created each time - - wire: null // each rendered node represent some wired content and - // this reference to the latest one. If different, the node - // will be cleaned up and the new "wire" will be appended -}); +export class Cache { + constructor() { + this.stack = []; + this.entry = null; + this.wire = null; + } +} // the entry stored in the rendered node cache, and per each "hole" const createEntry = (type, template) => { @@ -66,11 +57,12 @@ const createEntry = (type, template) => { // operation based on the same template, i.e. data => html`

${data}

` const mapTemplate = (type, template) => { const text = instrument(template, prefix, type === 'svg'); - const content = createFragment(text, type); + const content = createContent(text, type); // once instrumented and reproduced as fragment, it's crawled // to find out where each update is in the fragment tree - const tw = createWalker(content); + const tw = document.createTreeWalker(content, 1 | 128); const nodes = []; + const paths = new Map; const length = template.length - 1; let i = 0; // updates are searched via unique names, linearly increased across the tree @@ -88,7 +80,10 @@ const mapTemplate = (type, template) => { // The only comments to be considered are those // which content is exactly the same as the searched one. if (node.data === search) { - nodes.push({type: 'node', path: createPath(node)}); + nodes.push({ + type: 'node', + path: paths.get(node) || paths.set(node, createPath(node)) + }); search = `${prefix}${++i}`; } } @@ -101,7 +96,7 @@ const mapTemplate = (type, template) => { while (node.hasAttribute(search)) { nodes.push({ type: 'attr', - path: createPath(node), + path: paths.get(node) || paths.set(node, createPath(node)), name: node.getAttribute(search), //svg: svg < 0 ? (svg = ('ownerSVGElement' in node ? 1 : 0)) : svg }); @@ -115,7 +110,10 @@ const mapTemplate = (type, template) => { node.textContent.trim() === `` ){ node.textContent = ''; - nodes.push({type: 'text', path: createPath(node)}); + nodes.push({ + type: 'text', + path: paths.get(node) || paths.set(node, createPath(node)) + }); search = `${prefix}${++i}`; } } @@ -147,20 +145,20 @@ const mapUpdates = (type, template) => { // discover what to do with each interpolation, which will result // into an update operation. export const unroll = (info, {type, template, values}) => { - const {length} = values; // interpolations can contain holes and arrays, so these need // to be recursively discovered - unrollValues(info, values, length); + unrollValues(info, values); let {entry} = info; // if the cache entry is either null or different from the template // and the type this unroll should resolve, create a new entry // assigning a new content fragment and the list of updates. if (!entry || (entry.template !== template || entry.type !== type)) info.entry = (entry = createEntry(type, template)); + const {content, updates, wire} = entry; // even if the fragment and its nodes is not live yet, // it is already possible to update via interpolations values. - for (let i = 0; i < length; i++) + for (let i = 0; i < values.length; i++) updates[i](values[i]); // if the entry was new, or representing a different template or type, // create a new persistent entity to use during diffing. @@ -172,23 +170,23 @@ export const unroll = (info, {type, template, values}) => { // the stack retains, per each interpolation value, the cache // related to each interpolation value, or null, if the render // was conditional and the value is not special (Array or Hole) -const unrollValues = ({stack}, values, length) => { +const unrollValues = ({stack}, values) => { + const {length} = values; for (let i = 0; i < length; i++) { const hole = values[i]; // each Hole gets unrolled and re-assigned as value // so that domdiff will deal with a node/wire, not with a hole if (hole instanceof Hole) values[i] = unroll( - stack[i] || (stack[i] = createCache()), + stack[i] || (stack[i] = new Cache), hole ); // arrays are recursively resolved so that each entry will contain // also a DOM node or a wire, hence it can be diffed if/when needed else if (isArray(hole)) unrollValues( - stack[i] || (stack[i] = createCache()), - hole, - hole.length + stack[i] || (stack[i] = new Cache), + hole ); // if the value is nothing special, the stack doesn't need to retain data // this is useful also to cleanup previously retained data, if the value @@ -198,19 +196,21 @@ const unrollValues = ({stack}, values, length) => { else stack[i] = null; } + // very dirty but better than splice if (length < stack.length) - stack.splice(length); + stack.length = length; }; -/** - * Holds all details wrappers needed to render the content further on. - * @constructor - * @param {string} type The hole type, either `html` or `svg`. - * @param {string[]} template The template literals used to the define the content. - * @param {Array} values Zero, one, or more interpolated values to render. - */ -export function Hole(type, template, values) { - this.type = type; - this.template = template; - this.values = values; -}; +export class Hole { + /** + * Holds all details wrappers needed to render the content further on. + * @param {string} type The hole type, either `html` or `svg`. + * @param {string[]} template The template literals used to the define the content. + * @param {Array} values Zero, one, or more interpolated values to render. + */ + constructor(type, template, values) { + this.type = type; + this.template = template; + this.values = values; + } +} diff --git a/index.js b/index.js index d0a51e6..825db0e 100644 --- a/index.js +++ b/index.js @@ -1,111 +1,103 @@ self.uhtml = (function (exports) { 'use strict'; - var umap = (function (_) { - return { - // About: get: _.get.bind(_) - // It looks like WebKit/Safari didn't optimize bind at all, - // so that using bind slows it down by 60%. - // Firefox and Chrome are just fine in both cases, - // so let's use the approach that works fast everywhere 👍 - get: function get(key) { - return _.get(key); - }, - set: function set(key, value) { - return _.set(key, value), value; - } - }; - }); - - var attr = /([^\s\\>"'=]+)\s*=\s*(['"]?)$/; - var empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i; - var node = /<[a-z][^>]+$/i; - var notNode = />[^<>]*$/; - var selfClosing = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/>)/ig; - var trimEnd = /\s+$/; - - var isNode = function isNode(template, i) { - return 0 < i-- && (node.test(template[i]) || !notNode.test(template[i]) && isNode(template, i)); - }; - - var regular = function regular(original, name, extra) { - return empty.test(name) ? original : "<".concat(name).concat(extra.replace(trimEnd, ''), ">"); - }; - - var instrument = (function (template, prefix, svg) { - var text = []; - var length = template.length; - - var _loop = function _loop(i) { - var chunk = template[i - 1]; - text.push(attr.test(chunk) && isNode(template, i) ? chunk.replace(attr, function (_, $1, $2) { - return "".concat(prefix).concat(i - 1, "=").concat($2 || '"').concat($1).concat($2 ? '' : '"'); - }) : "".concat(chunk, "")); - }; + class M extends Map { + set(key, value) { + super.set(key, value); + return value; + } + } - for (var i = 1; i < length; i++) { - _loop(i); + class WM extends WeakMap { + set(key, value) { + super.set(key, value); + return value; } + } - text.push(template[length - 1]); - var output = text.join('').trim(); - return svg ? output : output.replace(selfClosing, regular); - }); + const empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i; + const elements = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g; + const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x00/g; + const holes = /\x00=?/g; + + var instrument = (template, prefix, svg) => { + let i = 0; + return template + .join('\x00') + .replace( + elements, + (_, name, attrs, selfClosing) => { + let ml = name + attrs.replace(attributes, '\x00=$2$1').trimEnd(); + if (selfClosing.length) + ml += (svg || empty.test(name)) ? ' /' : ('>'; + } + ) + .replace( + holes, + hole => hole.length > 1 ? + (prefix + i++ + '=') : + ('') + ); + }; - var isArray = Array.isArray; - var _ref = [], - indexOf = _ref.indexOf, - slice = _ref.slice; + const {isArray} = Array; + const {indexOf, slice} = []; - var ELEMENT_NODE = 1; - var nodeType = 111; + const ELEMENT_NODE = 1; + const nodeType = 111; - var remove = function remove(_ref) { - var firstChild = _ref.firstChild, - lastChild = _ref.lastChild; - var range = document.createRange(); + const remove = ({firstChild, lastChild}) => { + const range = document.createRange(); range.setStartAfter(firstChild); range.setEndAfter(lastChild); range.deleteContents(); return firstChild; }; - var diffable = function diffable(node, operation) { - return node.nodeType === nodeType ? 1 / operation < 0 ? operation ? remove(node) : node.lastChild : operation ? node.valueOf() : node.firstChild : node; - }; - var persistent = function persistent(fragment) { - var childNodes = fragment.childNodes; - var length = childNodes.length; - if (length < 2) return length ? childNodes[0] : fragment; - var nodes = slice.call(childNodes, 0); - var firstChild = nodes[0]; - var lastChild = nodes[length - 1]; + const diffable = (node, operation) => node.nodeType === nodeType ? + ((1 / operation) < 0 ? + (operation ? remove(node) : node.lastChild) : + (operation ? node.valueOf() : node.firstChild)) : + node + ; + + const persistent = fragment => { + const {firstChild, lastChild} = fragment; + if (firstChild === lastChild) + return lastChild || fragment; + const {childNodes} = fragment; + const nodes = slice.call(childNodes, 0); return { - ELEMENT_NODE: ELEMENT_NODE, - nodeType: nodeType, - firstChild: firstChild, - lastChild: lastChild, - valueOf: function valueOf() { - if (childNodes.length !== length) { - var i = 0; - - while (i < length) { - fragment.appendChild(nodes[i++]); - } - } - + ELEMENT_NODE, + nodeType, + firstChild, + lastChild, + valueOf() { + if (childNodes.length !== nodes.length) + fragment.append(...nodes); return fragment; } }; }; - + /*! (c) Andrea Giammarchi - ISC */ + const createHTML = ml => { + const template = document.createElement('template'); + template.innerHTML = ml; + return template.content; + }; - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } + const svg$1 = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + const range = document.createRange(); + const createSVG = ml => { + svg$1.innerHTML = ml; + range.setStartBefore(svg$1.firstChild); + range.setEndAfter(svg$1.lastChild); + return range.extractContents(); + }; + + const create$1 = (ml, type) => (type === 'svg' ? createSVG : createHTML)(ml); /** * ISC License @@ -134,14 +126,13 @@ self.uhtml = (function (exports) { * @param {Node} [before] The optional node used as anchor to insert before. * @returns {Node[]} The same list of future children. */ - var udomdiff = (function (parentNode, a, b, get, before) { - var bLength = b.length; - var aEnd = a.length; - var bEnd = bLength; - var aStart = 0; - var bStart = 0; - var map = null; - + var udomdiff = (parentNode, a, b, get, before) => { + const bLength = b.length; + let aEnd = a.length; + let bEnd = bLength; + let aStart = 0; + let bStart = 0; + let map = null; while (aStart < aEnd || bStart < bEnd) { // append head, tail, or nodes in between: fast path if (aEnd === aStart) { @@ -149,47 +140,61 @@ self.uhtml = (function (exports) { // need to be added are not at the end, and in such case // the node to `insertBefore`, if the index is more than 0 // must be retrieved, otherwise it's gonna be the first item. - var node = bEnd < bLength ? bStart ? get(b[bStart - 1], -0).nextSibling : get(b[bEnd - bStart], 0) : before; - - while (bStart < bEnd) { + const node = bEnd < bLength ? + (bStart ? + (get(b[bStart - 1], -0).nextSibling) : + get(b[bEnd - bStart], 0)) : + before; + while (bStart < bEnd) parentNode.insertBefore(get(b[bStart++], 1), node); - } - } // remove head or tail: fast path + } + // remove head or tail: fast path else if (bEnd === bStart) { while (aStart < aEnd) { // remove the node only if it's unknown or not live - if (!map || !map.has(a[aStart])) parentNode.removeChild(get(a[aStart], -1)); + if (!map || !map.has(a[aStart])) + parentNode.removeChild(get(a[aStart], -1)); aStart++; } - } // same node: fast path + } + // same node: fast path else if (a[aStart] === b[bStart]) { aStart++; bStart++; - } // same tail: fast path + } + // same tail: fast path else if (a[aEnd - 1] === b[bEnd - 1]) { aEnd--; bEnd--; - } // The once here single last swap "fast path" has been removed in v1.1.0 + } + // The once here single last swap "fast path" has been removed in v1.1.0 // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85 // reverse swap: also fast path - else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) { + else if ( + a[aStart] === b[bEnd - 1] && + b[bStart] === a[aEnd - 1] + ) { // this is a "shrink" operation that could happen in these cases: // [1, 2, 3, 4, 5] // [1, 4, 3, 2, 5] // or asymmetric too // [1, 2, 3, 4, 5] // [1, 2, 3, 5, 6, 4] - var _node = get(a[--aEnd], -1).nextSibling; - parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling); - parentNode.insertBefore(get(b[--bEnd], 1), _node); // mark the future index as identical (yeah, it's dirty, but cheap 👍) + const node = get(a[--aEnd], -1).nextSibling; + parentNode.insertBefore( + get(b[bStart++], 1), + get(a[aStart++], -1).nextSibling + ); + parentNode.insertBefore(get(b[--bEnd], 1), node); + // mark the future index as identical (yeah, it's dirty, but cheap 👍) // The main reason to do this, is that when a[aEnd] will be reached, // the loop will likely be on the fast path, as identical to b[bEnd]. // In the best case scenario, the next loop will skip the tail, // but in the worst one, this node will be considered as already // processed, bailing out pretty quickly from the map index check - a[aEnd] = b[bEnd]; - } // map based fallback, "slow" path + } + // map based fallback, "slow" path else { // the map requires an O(bEnd - bStart) operation once // to store all future nodes indexes for later purposes. @@ -197,27 +202,23 @@ self.uhtml = (function (exports) { // and such scenario happens at least when all nodes are different, // but also if both first and last items of the lists are different if (!map) { - map = new Map(); - var i = bStart; - - while (i < bEnd) { + map = new Map; + let i = bStart; + while (i < bEnd) map.set(b[i], i++); - } - } // if it's a future node, hence it needs some handling - - + } + // if it's a future node, hence it needs some handling if (map.has(a[aStart])) { // grab the index of such node, 'cause it might have been processed - var index = map.get(a[aStart]); // if it's not already processed, look on demand for the next LCS - + const index = map.get(a[aStart]); + // if it's not already processed, look on demand for the next LCS if (bStart < index && index < bEnd) { - var _i = aStart; // counts the amount of nodes that are the same in the future - - var sequence = 1; - - while (++_i < aEnd && _i < bEnd && map.get(a[_i]) === index + sequence) { + let i = aStart; + // counts the amount of nodes that are the same in the future + let sequence = 1; + while (++i < aEnd && i < bEnd && map.get(a[i]) === (index + sequence)) sequence++; - } // effort decision here: if the sequence is longer than replaces + // effort decision here: if the sequence is longer than replaces // needed to reach such sequence, which would brings again this loop // to the fast path, prepend the difference before a sequence, // and move only the future list index forward, so that aStart @@ -227,80 +228,80 @@ self.uhtml = (function (exports) { // b: [7, 1, 2, 3, 6] // this would place 7 before 1 and, from that time on, 1, 2, and 3 // will be processed at zero cost - - - if (sequence > index - bStart) { - var _node2 = get(a[aStart], 0); - - while (bStart < index) { - parentNode.insertBefore(get(b[bStart++], 1), _node2); - } - } // if the effort wasn't good enough, fallback to a replace, + if (sequence > (index - bStart)) { + const node = get(a[aStart], 0); + while (bStart < index) + parentNode.insertBefore(get(b[bStart++], 1), node); + } + // if the effort wasn't good enough, fallback to a replace, // moving both source and target indexes forward, hoping that some // similar node will be found later on, to go back to the fast path else { - parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1)); + parentNode.replaceChild( + get(b[bStart++], 1), + get(a[aStart++], -1) + ); } - } // otherwise move the source forward, 'cause there's nothing to do - else aStart++; - } // this node has no meaning in the future list, so it's more than safe + } + // otherwise move the source forward, 'cause there's nothing to do + else + aStart++; + } + // this node has no meaning in the future list, so it's more than safe // to remove it, and check the next live node out instead, meaning // that only the live list index should be forwarded - else parentNode.removeChild(get(a[aStart++], -1)); + else + parentNode.removeChild(get(a[aStart++], -1)); } } - return b; - }); + }; - var Foreign = function Foreign(handler, value) { - _classCallCheck(this, Foreign); + // flag for foreign checks (slower path, fast by default) + let useForeign = false; - this._ = function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } + class Foreign { + constructor(handler, value) { + useForeign = true; + this._ = (...args) => handler(...args, value); + } + } - return handler.apply(void 0, args.concat([value])); - }; - }; // flag for foreign checks (slower path, fast by default) + const foreign = (handler, value) => new Foreign(handler, value); - var useForeign = false; - var foreign = function foreign(handler, value) { - useForeign = true; - return new Foreign(handler, value); - }; - var aria = function aria(node) { - return function (values) { - for (var key in values) { - var name = key === 'role' ? key : "aria-".concat(key); - var value = values[key]; - if (value == null) node.removeAttribute(name);else node.setAttribute(name, value); - } - }; + const aria = node => values => { + for (const key in values) { + const name = key === 'role' ? key : `aria-${key}`; + const value = values[key]; + if (value == null) + node.removeAttribute(name); + else + node.setAttribute(name, value); + } }; - var attribute = function attribute(node, name) { - var oldValue, - orphan = true; - var attributeNode = document.createAttributeNS(null, name); - return function (newValue) { + + const attribute = (node, name) => { + let oldValue, orphan = true; + const attributeNode = document.createAttributeNS(null, name); + return newValue => { if (oldValue !== newValue) { oldValue = newValue; - if (oldValue == null) { if (!orphan) { node.removeAttributeNode(attributeNode); orphan = true; } - } else { - var value = useForeign && newValue instanceof Foreign ? newValue._(node, name) : newValue; - + } + else { + const value = useForeign && (newValue instanceof Foreign) ? + newValue._(node, name) : newValue; if (value == null) { - if (!orphan) node.removeAttributeNode(attributeNode); - orphan = true; - } else { + if (!orphan) + node.removeAttributeNode(attributeNode); + orphan = true; + } + else { attributeNode.value = value; - if (orphan) { node.setAttributeNodeNS(attributeNode); orphan = false; @@ -311,55 +312,64 @@ self.uhtml = (function (exports) { }; }; - var _boolean = function _boolean(node, key, oldValue) { - return function (newValue) { - if (oldValue !== !!newValue) { - // when IE won't be around anymore ... - // node.toggleAttribute(key, oldValue = !!newValue); - if (oldValue = !!newValue) node.setAttribute(key, '');else node.removeAttribute(key); - } - }; + const boolean = (node, key, oldValue) => newValue => { + if (oldValue !== !!newValue) { + // when IE won't be around anymore ... + // node.toggleAttribute(key, oldValue = !!newValue); + if ((oldValue = !!newValue)) + node.setAttribute(key, ''); + else + node.removeAttribute(key); + } }; - var data = function data(_ref) { - var dataset = _ref.dataset; - return function (values) { - for (var key in values) { - var value = values[key]; - if (value == null) delete dataset[key];else dataset[key] = value; - } - }; + + const data = ({dataset}) => values => { + for (const key in values) { + const value = values[key]; + if (value == null) + delete dataset[key]; + else + dataset[key] = value; + } }; - var event = function event(node, name) { - var oldValue, - lower, - type = name.slice(2); - if (!(name in node) && (lower = name.toLowerCase()) in node) type = lower.slice(2); - return function (newValue) { - var info = isArray(newValue) ? newValue : [newValue, false]; + const event = (node, name) => { + let oldValue, lower, type = name.slice(2); + if (!(name in node) && (lower = name.toLowerCase()) in node) + type = lower.slice(2); + return newValue => { + const info = isArray(newValue) ? newValue : [newValue, false]; if (oldValue !== info[0]) { - if (oldValue) node.removeEventListener(type, oldValue, info[1]); - if (oldValue = info[0]) node.addEventListener(type, oldValue, info[1]); + if (oldValue) + node.removeEventListener(type, oldValue, info[1]); + if (oldValue = info[0]) + node.addEventListener(type, oldValue, info[1]); } }; }; - var ref = function ref(node) { - var oldValue; - return function (value) { + + const ref = node => { + let oldValue; + return value => { if (oldValue !== value) { oldValue = value; - if (typeof value === 'function') value(node);else value.current = node; + if (typeof value === 'function') + value(node); + else + value.current = node; } }; }; - var setter = function setter(node, key) { - return key === 'dataset' ? data(node) : function (value) { + + const setter = (node, key) => key === 'dataset' ? + data(node) : + value => { node[key] = value; }; - }; - var text = function text(node) { - var oldValue; - return function (newValue) { + + const text = node => { + let oldValue; + return newValue => { if (oldValue != newValue) { oldValue = newValue; node.textContent = newValue == null ? '' : newValue; @@ -367,14 +377,13 @@ self.uhtml = (function (exports) { }; }; - var reducePath = function reducePath(_ref, i) { - var childNodes = _ref.childNodes; - return childNodes[i]; - }; // this helper avoid code bloat around handleAnything() callback + // from a generic path, retrieves the exact targeted node + const reducePath = ({childNodes}, i) => childNodes[i]; - - var diff = function diff(comment, oldNodes, newNodes) { - return udomdiff(comment.parentNode, // TODO: there is a possible edge case where a node has been + // this helper avoid code bloat around handleAnything() callback + const diff = (comment, oldNodes, newNodes) => udomdiff( + comment.parentNode, + // TODO: there is a possible edge case where a node has been // removed manually, or it was a keyed one, attached // to a shared reference between renders. // In this case udomdiff might fail at removing such node @@ -389,34 +398,33 @@ self.uhtml = (function (exports) { // and both lighterhtml and hyperHTML might fail with this // very specific edge case, I might as well document this possible // "diffing shenanigan" and call it a day. - oldNodes, newNodes, diffable, comment); - }; // if an interpolation represents a comment, the whole + oldNodes, + newNodes, + diffable, + comment + ); + + // if an interpolation represents a comment, the whole // diffing will be related to such comment. // This helper is in charge of understanding how the new // content for such interpolation/hole should be updated - - - var handleAnything = function handleAnything(comment) { - var oldValue, - text, - nodes = []; - - var anyContent = function anyContent(newValue) { - switch (typeof(newValue)) { + const handleAnything = comment => { + let oldValue, text, nodes = []; + const anyContent = newValue => { + switch (typeof newValue) { // primitives are handled as text content case 'string': case 'number': case 'boolean': if (oldValue !== newValue) { oldValue = newValue; - if (!text) text = document.createTextNode(''); + if (!text) + text = document.createTextNode(''); text.data = newValue; nodes = diff(comment, nodes, [text]); } - break; // null, and undefined are used to cleanup previous content - case 'object': case 'undefined': if (newValue == null) { @@ -424,40 +432,47 @@ self.uhtml = (function (exports) { oldValue = newValue; nodes = diff(comment, nodes, []); } - break; - } // arrays and nodes have a special treatment - - + } + // arrays and nodes have a special treatment if (isArray(newValue)) { - oldValue = newValue; // arrays can be used to cleanup, if empty - - if (newValue.length === 0) nodes = diff(comment, nodes, []); // or diffed, if these contains nodes or "wires" - else if (typeof(newValue[0]) === 'object') nodes = diff(comment, nodes, newValue); // in all other cases the content is stringified as is - else anyContent(String(newValue)); + oldValue = newValue; + // arrays can be used to cleanup, if empty + if (newValue.length === 0) + nodes = diff(comment, nodes, []); + // or diffed, if these contains nodes or "wires" + else if (typeof newValue[0] === 'object') + nodes = diff(comment, nodes, newValue); + // in all other cases the content is stringified as is + else + anyContent(String(newValue)); break; - } // if the new value is a DOM node, or a wire, and it's + } + // if the new value is a DOM node, or a wire, and it's // different from the one already live, then it's diffed. // if the node is a fragment, it's appended once via its childNodes // There is no `else` here, meaning if the content // is not expected one, nothing happens, as easy as that. - - if (oldValue !== newValue && 'ELEMENT_NODE' in newValue) { oldValue = newValue; - nodes = diff(comment, nodes, newValue.nodeType === 11 ? slice.call(newValue.childNodes) : [newValue]); + nodes = diff( + comment, + nodes, + newValue.nodeType === 11 ? + slice.call(newValue.childNodes) : + [newValue] + ); } - break; - case 'function': anyContent(newValue(comment)); break; } }; - return anyContent; - }; // attributes can be: + }; + + // attributes can be: // * ref=${...} for hooks and other purposes // * aria=${...} for aria attributes // * ?boolean=${...} for boolean attributes @@ -467,224 +482,118 @@ self.uhtml = (function (exports) { // * @event=${...} to explicitly handle event listeners // * onevent=${...} to automatically handle event listeners // * generic=${...} to handle an attribute just like an attribute - - - var handleAttribute = function handleAttribute(node, name - /*, svg*/ - ) { + const handleAttribute = (node, name/*, svg*/) => { switch (name[0]) { - case '?': - return _boolean(node, name.slice(1), false); - - case '.': - return setter(node, name.slice(1)); - - case '@': - return event(node, 'on' + name.slice(1)); - - case 'o': - if (name[1] === 'n') return event(node, name); + case '?': return boolean(node, name.slice(1), false); + case '.': return setter(node, name.slice(1)); + case '@': return event(node, 'on' + name.slice(1)); + case 'o': if (name[1] === 'n') return event(node, name); } switch (name) { - case 'ref': - return ref(node); - - case 'aria': - return aria(node); + case 'ref': return ref(node); + case 'aria': return aria(node); } - return attribute(node, name - /*, svg*/ - ); - }; // each mapped update carries the update type and its path + return attribute(node, name/*, svg*/); + }; + + // each mapped update carries the update type and its path // the type is either node, attribute, or text, while // the path is how to retrieve the related node to update. // In the attribute case, the attribute name is also carried along. - - function handlers(options) { - var type = options.type, - path = options.path; - var node = path.reduceRight(reducePath, this); - return type === 'node' ? handleAnything(node) : type === 'attr' ? handleAttribute(node, options.name - /*, options.svg*/ - ) : text(node); + const {type, path} = options; + const node = path.reduceRight(reducePath, this); + return type === 'node' ? + handleAnything(node) : + (type === 'attr' ? + handleAttribute(node, options.name/*, options.svg*/) : + text(node)); } - /*! (c) Andrea Giammarchi - ISC */ - var createContent = function (document) { - - var FRAGMENT = 'fragment'; - var TEMPLATE = 'template'; - var HAS_CONTENT = ('content' in create(TEMPLATE)); - var createHTML = HAS_CONTENT ? function (html) { - var template = create(TEMPLATE); - template.innerHTML = html; - return template.content; - } : function (html) { - var content = create(FRAGMENT); - var template = create(TEMPLATE); - var childNodes = null; - - if (/^[^\S]*?<(col(?:group)?|t(?:head|body|foot|r|d|h))/i.test(html)) { - var selector = RegExp.$1; - template.innerHTML = '' + html + '
'; - childNodes = template.querySelectorAll(selector); - } else { - template.innerHTML = html; - childNodes = template.childNodes; - } - - append(content, childNodes); - return content; - }; - return function createContent(markup, type) { - return (type === 'svg' ? createSVG : createHTML)(markup); - }; - - function append(root, childNodes) { - var length = childNodes.length; - - while (length--) { - root.appendChild(childNodes[0]); - } - } - - function create(element) { - return element === FRAGMENT ? document.createDocumentFragment() : document.createElementNS('http://www.w3.org/1999/xhtml', element); - } // it could use createElementNS when hasNode is there - // but this fallback is equally fast and easier to maintain - // it is also battle tested already in all IE - - - function createSVG(svg) { - var content = create(FRAGMENT); - var template = create('div'); - template.innerHTML = '' + svg + ''; - append(content, template.firstChild.childNodes); - return content; - } - }(document); - - var isImportNodeLengthWrong = document.importNode.length != 1; // IE11 and old Edge discard empty nodes when cloning, potentially - // resulting in broken paths to find updates. The workaround here - // is to import once, upfront, the fragment that will be cloned - // later on, so that paths are retrieved from one already parsed, - // hence without missing child nodes once re-cloned. - - var createFragment = isImportNodeLengthWrong ? function (text, type, normalize) { - return document.importNode(createContent(text, type, normalize), true); - } : createContent; // IE11 and old Edge have a different createTreeWalker signature that - // has been deprecated in other browsers. This export is needed only - // to guarantee the TreeWalker doesn't show warnings and, ultimately, works - - var createWalker = isImportNodeLengthWrong ? function (fragment) { - return document.createTreeWalker(fragment, 1 | 128, null, false); - } : function (fragment) { - return document.createTreeWalker(fragment, 1 | 128); - }; - + // from a fragment container, create an array of indexes // related to its child nodes, so that it's possible // to retrieve later on exact node via reducePath - - var createPath = function createPath(node) { - var path = []; - var _node = node, - parentNode = _node.parentNode; - + const createPath = node => { + const path = []; + let {parentNode} = node; while (parentNode) { path.push(indexOf.call(parentNode.childNodes, node)); node = parentNode; - parentNode = node.parentNode; + ({parentNode} = node); } - return path; - }; // the prefix is used to identify either comments, attributes, or nodes + }; + + // the prefix is used to identify either comments, attributes, or nodes // that contain the related unique id. In the attribute cases // isµX="attribute-name" will be used to map current X update to that // attribute name, while comments will be like , to map // the update to that specific comment node, hence its parent. // style and textarea will have text content, and are handled // directly through text-only updates. + const prefix = 'isµ'; - - var prefix = 'isµ'; // Template Literals are unique per scope and static, meaning a template + // Template Literals are unique per scope and static, meaning a template // should be parsed once, and once only, as it will always represent the same // content, within the exact same amount of updates each time. // This cache relates each template to its unique content and updates. + const cache$1 = new WM; - var cache$1 = umap(new WeakMap()); // a RegExp that helps checking nodes that cannot contain comments + // a RegExp that helps checking nodes that cannot contain comments + const textOnly = /^(?:plaintext|script|style|textarea|title|xmp)$/i; - var textOnly = /^(?:plaintext|script|style|textarea|title|xmp)$/i; - var createCache = function createCache() { - return { - stack: [], - // each template gets a stack for each interpolation "hole" - entry: null, - // each entry contains details, such as: - // * the template that is representing - // * the type of node it represents (html or svg) - // * the content fragment with all nodes - // * the list of updates per each node (template holes) - // * the "wired" node or fragment that will get updates - // if the template or type are different from the previous one - // the entry gets re-created each time - wire: null // each rendered node represent some wired content and - // this reference to the latest one. If different, the node - // will be cleaned up and the new "wire" will be appended - - }; - }; // the entry stored in the rendered node cache, and per each "hole" + class Cache { + constructor() { + this.stack = []; + this.entry = null; + this.wire = null; + } + } - var createEntry = function createEntry(type, template) { - var _mapUpdates = mapUpdates(type, template), - content = _mapUpdates.content, - updates = _mapUpdates.updates; + // the entry stored in the rendered node cache, and per each "hole" + const createEntry = (type, template) => { + const {content, updates} = mapUpdates(type, template); + return {type, template, content, updates, wire: null}; + }; - return { - type: type, - template: template, - content: content, - updates: updates, - wire: null - }; - }; // a template is instrumented to be able to retrieve where updates are needed. + // a template is instrumented to be able to retrieve where updates are needed. // Each unique template becomes a fragment, cloned once per each other // operation based on the same template, i.e. data => html`

${data}

` - - - var mapTemplate = function mapTemplate(type, template) { - var text = instrument(template, prefix, type === 'svg'); - var content = createFragment(text, type); // once instrumented and reproduced as fragment, it's crawled + const mapTemplate = (type, template) => { + const text = instrument(template, prefix, type === 'svg'); + const content = create$1(text, type); + // once instrumented and reproduced as fragment, it's crawled // to find out where each update is in the fragment tree - - var tw = createWalker(content); - var nodes = []; - var length = template.length - 1; - var i = 0; // updates are searched via unique names, linearly increased across the tree + const tw = document.createTreeWalker(content, 1 | 128); + const nodes = []; + const paths = new M; + const length = template.length - 1; + let i = 0; + // updates are searched via unique names, linearly increased across the tree //
- - var search = "".concat(prefix).concat(i); - + let search = `${prefix}${i}`; while (i < length) { - var node = tw.nextNode(); // if not all updates are bound but there's nothing else to crawl + const node = tw.nextNode(); + // if not all updates are bound but there's nothing else to crawl // it means that there is something wrong with the template. - - if (!node) throw "bad template: ".concat(text); // if the current node is a comment, and it contains isµX + if (!node) + throw `bad template: ${text}`; + // if the current node is a comment, and it contains isµX // it means the update should take care of any content - if (node.nodeType === 8) { // The only comments to be considered are those // which content is exactly the same as the searched one. if (node.data === search) { nodes.push({ type: 'node', - path: createPath(node) + path: paths.get(node) || paths.set(node, createPath(node)) }); - search = "".concat(prefix).concat(++i); + search = `${prefix}${++i}`; } - } else { + } + else { // if the node is not a comment, loop through all its attributes // named isµX and relate attribute updates to this node and the // attribute name, retrieved through node.getAttribute("isµX") @@ -693,213 +602,192 @@ self.uhtml = (function (exports) { while (node.hasAttribute(search)) { nodes.push({ type: 'attr', - path: createPath(node), - name: node.getAttribute(search) //svg: svg < 0 ? (svg = ('ownerSVGElement' in node ? 1 : 0)) : svg - + path: paths.get(node) || paths.set(node, createPath(node)), + name: node.getAttribute(search), + //svg: svg < 0 ? (svg = ('ownerSVGElement' in node ? 1 : 0)) : svg }); node.removeAttribute(search); - search = "".concat(prefix).concat(++i); - } // if the node was a style, textarea, or others, check its content + search = `${prefix}${++i}`; + } + // if the node was a style, textarea, or others, check its content // and if it is then update tex-only this node - - - if (textOnly.test(node.tagName) && node.textContent.trim() === "")) { + if ( + textOnly.test(node.tagName) && + node.textContent.trim() === `` + ){ node.textContent = ''; nodes.push({ type: 'text', - path: createPath(node) + path: paths.get(node) || paths.set(node, createPath(node)) }); - search = "".concat(prefix).concat(++i); + search = `${prefix}${++i}`; } } - } // once all nodes to update, or their attributes, are known, the content + } + // once all nodes to update, or their attributes, are known, the content // will be cloned in the future to represent the template, and all updates // related to such content retrieved right away without needing to re-crawl // the exact same template, and its content, more than once. + return {content, nodes}; + }; - - return { - content: content, - nodes: nodes - }; - }; // if a template is unknown, perform the previous mapping, otherwise grab + // if a template is unknown, perform the previous mapping, otherwise grab // its details such as the fragment with all nodes, and updates info. + const mapUpdates = (type, template) => { + const {content, nodes} = ( + cache$1.get(template) || + cache$1.set(template, mapTemplate(type, template)) + ); + // clone deeply the fragment + const fragment = document.importNode(content, true); + // and relate an update handler per each node that needs one + const updates = nodes.map(handlers, fragment); + // return the fragment and all updates to use within its nodes + return {content: fragment, updates}; + }; - - var mapUpdates = function mapUpdates(type, template) { - var _ref = cache$1.get(template) || cache$1.set(template, mapTemplate(type, template)), - content = _ref.content, - nodes = _ref.nodes; // clone deeply the fragment - - - var fragment = document.importNode(content, true); // and relate an update handler per each node that needs one - - var updates = nodes.map(handlers, fragment); // return the fragment and all updates to use within its nodes - - return { - content: fragment, - updates: updates - }; - }; // as html and svg can be nested calls, but no parent node is known + // as html and svg can be nested calls, but no parent node is known // until rendered somewhere, the unroll operation is needed to // discover what to do with each interpolation, which will result // into an update operation. - - - var unroll = function unroll(info, _ref2) { - var type = _ref2.type, - template = _ref2.template, - values = _ref2.values; - var length = values.length; // interpolations can contain holes and arrays, so these need + const unroll = (info, {type, template, values}) => { + // interpolations can contain holes and arrays, so these need // to be recursively discovered - - unrollValues(info, values, length); - var entry = info.entry; // if the cache entry is either null or different from the template + unrollValues(info, values); + let {entry} = info; + // if the cache entry is either null or different from the template // and the type this unroll should resolve, create a new entry // assigning a new content fragment and the list of updates. + if (!entry || (entry.template !== template || entry.type !== type)) + info.entry = (entry = createEntry(type, template)); - if (!entry || entry.template !== template || entry.type !== type) info.entry = entry = createEntry(type, template); - var _entry = entry, - content = _entry.content, - updates = _entry.updates, - wire = _entry.wire; // even if the fragment and its nodes is not live yet, + const {content, updates, wire} = entry; + // even if the fragment and its nodes is not live yet, // it is already possible to update via interpolations values. - - for (var i = 0; i < length; i++) { + for (let i = 0; i < values.length; i++) updates[i](values[i]); - } // if the entry was new, or representing a different template or type, + // if the entry was new, or representing a different template or type, // create a new persistent entity to use during diffing. // This is simply a DOM node, when the template has a single container, // as in `

`, or a "wire" in `

` and similar cases. - - return wire || (entry.wire = persistent(content)); - }; // the stack retains, per each interpolation value, the cache + }; + + // the stack retains, per each interpolation value, the cache // related to each interpolation value, or null, if the render // was conditional and the value is not special (Array or Hole) - - var unrollValues = function unrollValues(_ref3, values, length) { - var stack = _ref3.stack; - - for (var i = 0; i < length; i++) { - var hole = values[i]; // each Hole gets unrolled and re-assigned as value + const unrollValues = ({stack}, values) => { + const {length} = values; + for (let i = 0; i < length; i++) { + const hole = values[i]; + // each Hole gets unrolled and re-assigned as value // so that domdiff will deal with a node/wire, not with a hole - - if (hole instanceof Hole) values[i] = unroll(stack[i] || (stack[i] = createCache()), hole); // arrays are recursively resolved so that each entry will contain + if (hole instanceof Hole) + values[i] = unroll( + stack[i] || (stack[i] = new Cache), + hole + ); + // arrays are recursively resolved so that each entry will contain // also a DOM node or a wire, hence it can be diffed if/when needed - else if (isArray(hole)) unrollValues(stack[i] || (stack[i] = createCache()), hole, hole.length); // if the value is nothing special, the stack doesn't need to retain data + else if (isArray(hole)) + unrollValues( + stack[i] || (stack[i] = new Cache), + hole + ); + // if the value is nothing special, the stack doesn't need to retain data // this is useful also to cleanup previously retained data, if the value // was a Hole, or an Array, but not anymore, i.e.: // const update = content => html`
${content}
`; // update(listOfItems); update(null); update(html`hole`) - else stack[i] = null; + else + stack[i] = null; } - - if (length < stack.length) stack.splice(length); + // very dirty but better than splice + if (length < stack.length) + stack.length = length; }; - /** - * Holds all details wrappers needed to render the content further on. - * @constructor - * @param {string} type The hole type, either `html` or `svg`. - * @param {string[]} template The template literals used to the define the content. - * @param {Array} values Zero, one, or more interpolated values to render. - */ - - function Hole(type, template, values) { - this.type = type; - this.template = template; - this.values = values; + class Hole { + /** + * Holds all details wrappers needed to render the content further on. + * @param {string} type The hole type, either `html` or `svg`. + * @param {string[]} template The template literals used to the define the content. + * @param {Array} values Zero, one, or more interpolated values to render. + */ + constructor(type, template, values) { + this.type = type; + this.template = template; + this.values = values; + } } - var create = Object.create, - defineProperties = Object.defineProperties; // both `html` and `svg` template literal tags are polluted - // with a `for(ref[, id])` and a `node` tag too + const {create, defineProperties} = Object; - var tag = function tag(type) { + // both `html` and `svg` template literal tags are polluted + // with a `for(ref[, id])` and a `node` tag too + const tag = type => { // both `html` and `svg` tags have their own cache - var keyed = umap(new WeakMap()); // keyed operations always re-use the same cache and unroll + const keyed = new WM; + // keyed operations always re-use the same cache and unroll // the template and its interpolations right away - - var fixed = function fixed(cache) { - return function (template) { - for (var _len = arguments.length, values = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - values[_key - 1] = arguments[_key]; - } - - return unroll(cache, { - type: type, - template: template, - values: values - }); - }; - }; - - return defineProperties( // non keyed operations are recognized as instance of Hole - // during the "unroll", recursively resolved and updated - function (template) { - for (var _len2 = arguments.length, values = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - values[_key2 - 1] = arguments[_key2]; - } - - return new Hole(type, template, values); - }, { - "for": { - // keyed operations need a reference object, usually the parent node - // which is showing keyed results, and optionally a unique id per each - // related node, handy with JSON results and mutable list of objects - // that usually carry a unique identifier - value: function value(ref, id) { - var memo = keyed.get(ref) || keyed.set(ref, create(null)); - return memo[id] || (memo[id] = fixed(createCache())); - } - }, - node: { - // it is possible to create one-off content out of the box via node tag - // this might return the single created node, or a fragment with all - // nodes present at the root level and, of course, their child nodes - value: function value(template) { - for (var _len3 = arguments.length, values = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { - values[_key3 - 1] = arguments[_key3]; + const fixed = cache => (template, ...values) => unroll( + cache, + {type, template, values} + ); + return defineProperties( + // non keyed operations are recognized as instance of Hole + // during the "unroll", recursively resolved and updated + (template, ...values) => new Hole(type, template, values), + { + for: { + // keyed operations need a reference object, usually the parent node + // which is showing keyed results, and optionally a unique id per each + // related node, handy with JSON results and mutable list of objects + // that usually carry a unique identifier + value: (ref, id) => { + const memo = keyed.get(ref) || keyed.set(ref, create(null)); + return memo[id] || (memo[id] = fixed(new Cache)); } - - return unroll(createCache(), { - type: type, - template: template, - values: values - }).valueOf(); + }, + node: { + // it is possible to create one-off content out of the box via node tag + // this might return the single created node, or a fragment with all + // nodes present at the root level and, of course, their child nodes + value: (template, ...values) => unroll( + new Cache, + {type, template, values} + ).valueOf() } } - }); - }; // each rendered node gets its own cache + ); + }; + // each rendered node gets its own cache + const cache = new WM; - var cache = umap(new WeakMap()); // rendering means understanding what `html` or `svg` tags returned + // rendering means understanding what `html` or `svg` tags returned // and it relates a specific node to its own unique cache. // Each time the content to render changes, the node is cleaned up // and the new new content is appended, and if such content is a Hole // then it's "unrolled" to resolve all its inner nodes. - - var render = function render(where, what) { - var hole = typeof what === 'function' ? what() : what; - var info = cache.get(where) || cache.set(where, createCache()); - var wire = hole instanceof Hole ? unroll(info, hole) : hole; - + const render = (where, what) => { + const hole = typeof what === 'function' ? what() : what; + const info = cache.get(where) || cache.set(where, new Cache); + const wire = hole instanceof Hole ? unroll(info, hole) : hole; if (wire !== info.wire) { info.wire = wire; - where.textContent = ''; // valueOf() simply returns the node itself, but in case it was a "wire" + where.textContent = ''; + // valueOf() simply returns the node itself, but in case it was a "wire" // it will eventually re-append all nodes to its fragment so that such // fragment can be re-appended many times in a meaningful way // (wires are basically persistent fragments facades with special behavior) - where.appendChild(wire.valueOf()); } - return where; }; - var html = tag('html'); - var svg = tag('svg'); + const html = tag('html'); + const svg = tag('svg'); exports.Hole = Hole; exports.foreign = foreign; diff --git a/min.js b/min.js index 84bd41e..048fba7 100644 --- a/min.js +++ b/min.js @@ -1,2 +1 @@ -self.uhtml=function(e){"use strict";var t=function(e){return{get:function(t){return e.get(t)},set:function(t,n){return e.set(t,n),n}}},n=/([^\s\\>"'=]+)\s*=\s*(['"]?)$/,r=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,a=/<[a-z][^>]+$/i,o=/>[^<>]*$/,i=/<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/>)/gi,c=/\s+$/,u=function e(t,n){return 0")},f=function(e,t,r){for(var a=[],o=e.length,c=function(r){var o=e[r-1];a.push(n.test(o)&&u(e,r)?o.replace(n,(function(e,n,a){return"".concat(t).concat(r-1,"=").concat(a||'"').concat(n).concat(a?"":'"')})):"".concat(o,"\x3c!--").concat(t).concat(r-1,"--\x3e"))},f=1;fp-l)for(var g=r(t[u],0);l"+e+"",c=i.querySelectorAll(u)}else i.innerHTML=e,c=i.childNodes;return a(r,c),r};return function(e,t){return("svg"===t?i:r)(e)};function a(e,t){for(var n=t.length;n--;)e.appendChild(t[0])}function o(n){return n===t?e.createDocumentFragment():e.createElementNS("http://www.w3.org/1999/xhtml",n)}function i(e){var n=o(t),r=o("div");return r.innerHTML=''+e+"",a(n,r.firstChild.childNodes),n}}(document),C=1!=document.importNode.length,k=C?function(e,t,n){return document.importNode(A(e,t,n),!0)}:A,E=C?function(e){return document.createTreeWalker(e,129,null,!1)}:function(e){return document.createTreeWalker(e,129)},T=function(e){for(var t=[],n=e.parentNode;n;)t.push(d.call(n.childNodes,e)),n=(e=n).parentNode;return t},M="isµ",S=t(new WeakMap),L=/^(?:plaintext|script|style|textarea|title|xmp)$/i,O=function(e,t){var n=S.get(t)||S.set(t,function(e,t){for(var n=f(t,M,"svg"===e),r=k(n,e),a=E(r),o=[],i=t.length-1,c=0,u="".concat(M).concat(c);c1?n-1:0),a=1;a1?r-1:0),o=1;o1?n-1:0),a=1;a]*?)(\/?)>/g,l=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x00/g,o=/\x00=?/g;const{isArray:i}=Array,{indexOf:a,slice:c}=[],u=(e,t)=>111===e.nodeType?1/t<0?t?(({firstChild:e,lastChild:t})=>{const n=document.createRange();return n.setStartAfter(e),n.setEndAfter(t),n.deleteContents(),e})(e):e.lastChild:t?e.valueOf():e.firstChild:e,d=e=>{const t=document.createElement("template");return t.innerHTML=e,t.content},f=document.createElementNS("http://www.w3.org/2000/svg","svg"),p=document.createRange(),h=e=>(f.innerHTML=e,p.setStartBefore(f.firstChild),p.setEndAfter(f.lastChild),p.extractContents()),m=(e,t)=>("svg"===t?h:d)(e);let g=!1;class b{constructor(e,t){g=!0,this._=(...n)=>e(...n,t)}}const w=(e,t)=>{let n,r,s=t.slice(2);return!(t in e)&&(r=t.toLowerCase())in e&&(s=r.slice(2)),t=>{const r=i(t)?t:[t,!1];n!==r[0]&&(n&&e.removeEventListener(s,n,r[1]),(n=r[0])&&e.addEventListener(s,n,r[1]))}},v=({childNodes:e},t)=>e[t],y=(e,t,n)=>((e,t,n,r,s)=>{const l=n.length;let o=t.length,i=l,a=0,c=0,u=null;for(;as-c){const l=r(t[a],0);for(;c{switch(t[0]){case"?":return((e,t,n)=>r=>{n!==!!r&&((n=!!r)?e.setAttribute(t,""):e.removeAttribute(t))})(e,t.slice(1),!1);case".":return((e,t)=>"dataset"===t?(({dataset:e})=>t=>{for(const n in t){const r=t[n];null==r?delete e[n]:e[n]=r}})(e):n=>{e[t]=n})(e,t.slice(1));case"@":return w(e,"on"+t.slice(1));case"o":if("n"===t[1])return w(e,t)}switch(t){case"ref":return(e=>{let t;return n=>{t!==n&&(t=n,"function"==typeof n?n(e):n.current=e)}})(e);case"aria":return(e=>t=>{for(const n in t){const r="role"===n?n:`aria-${n}`,s=t[n];null==s?e.removeAttribute(r):e.setAttribute(r,s)}})(e)}return((e,t)=>{let n,r=!0;const s=document.createAttributeNS(null,t);return l=>{if(n!==l)if(n=l,null==n)r||(e.removeAttributeNode(s),r=!0);else{const n=g&&l instanceof b?l._(e,t):l;null==n?(r||e.removeAttributeNode(s),r=!0):(s.value=n,r&&(e.setAttributeNodeNS(s),r=!1))}}})(e,t)};function C(e){const{type:t,path:n}=e,r=n.reduceRight(v,this);return"node"===t?(e=>{let t,n,r=[];const s=l=>{switch(typeof l){case"string":case"number":case"boolean":t!==l&&(t=l,n||(n=document.createTextNode("")),n.data=l,r=y(e,r,[n]));break;case"object":case"undefined":if(null==l){t!=l&&(t=l,r=y(e,r,[]));break}if(i(l)){t=l,0===l.length?r=y(e,r,[]):"object"==typeof l[0]?r=y(e,r,l):s(String(l));break}t!==l&&"ELEMENT_NODE"in l&&(t=l,r=y(e,r,11===l.nodeType?c.call(l.childNodes):[l]));break;case"function":s(l(e))}};return s})(r):"attr"===t?x(r,e.name):(e=>{let t;return n=>{t!=n&&(t=n,e.textContent=null==n?"":n)}})(r)}const N=e=>{const t=[];let{parentNode:n}=e;for(;n;)t.push(a.call(n.childNodes,e)),e=n,({parentNode:n}=e);return t},A="isµ",E=new n,k=/^(?:plaintext|script|style|textarea|title|xmp)$/i;class T{constructor(){this.stack=[],this.entry=null,this.wire=null}}const S=(e,n)=>{const i=((e,t,n)=>{let i=0;return e.join("\0").replace(s,((e,t,s,o)=>{let i=t+s.replace(l,"\0=$2$1").trimEnd();return o.length&&(i+=n||r.test(t)?" /":">"})).replace(o,(e=>e.length>1?t+i+++"=":"\x3c!--"+t+i+++"--\x3e"))})(n,A,"svg"===e),a=m(i,e),c=document.createTreeWalker(a,129),u=[],d=new t,f=n.length-1;let p=0,h=`isµ${p}`;for(;p{const{content:n,nodes:r}=E.get(t)||E.set(t,S(e,t)),s=document.importNode(n,!0);return{content:s,updates:r.map(C,s)}},$=(e,{type:t,template:n,values:r})=>{L(e,r);let{entry:s}=e;s&&s.template===n&&s.type===t||(e.entry=s=((e,t)=>{const{content:n,updates:r}=O(e,t);return{type:e,template:t,content:n,updates:r,wire:null}})(t,n));const{content:l,updates:o,wire:i}=s;for(let e=0;e{const{firstChild:t,lastChild:n}=e;if(t===n)return n||e;const{childNodes:r}=e,s=c.call(r,0);return{ELEMENT_NODE:1,nodeType:111,firstChild:t,lastChild:n,valueOf:()=>(r.length!==s.length&&e.append(...s),e)}})(l))},L=({stack:e},t)=>{const{length:n}=t;for(let r=0;r{const t=new n;return _(((t,...n)=>new M(e,t,n)),{for:{value:(n,r)=>{const s=t.get(n)||t.set(n,B(null));return s[r]||(s[r]=(t=>(n,...r)=>$(t,{type:e,template:n,values:r}))(new T))}},node:{value:(t,...n)=>$(new T,{type:e,template:t,values:n}).valueOf()}})},H=new n,R=j("html"),z=j("svg");return e.Hole=M,e.foreign=(e,t)=>new b(e,t),e.html=R,e.render=(e,t)=>{const n="function"==typeof t?t():t,r=H.get(e)||H.set(e,new T),s=n instanceof M?$(r,n):n;return s!==r.wire&&(r.wire=s,e.textContent="",e.appendChild(s.valueOf())),e},e.svg=z,e}({}); \ No newline at end of file diff --git a/package.json b/package.json index 60322e7..41e3f19 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "cjs/index.js", "types": "index.d.ts", "scripts": { - "build": "node pony.js && npm run cjs && npm run rollup:async && npm run rollup:es && npm run rollup:babel && drop-babel-typeof ./index.js && npm run min && npm run test && npm run size", - "cjs": "rm cjs/*.js && ascjs --no-default esm cjs", + "build": "node pony.js && npm run cjs && npm run rollup:async && npm run rollup:es && npm run rollup:babel && npm run min && npm run test && npm run size", + "cjs": "rm -f cjs/*.js && ascjs --no-default esm cjs", "rollup:async": "rollup --config rollup/async.config.js && sed -i.bck 's/^var /self./' async.js && rm -rf async.js.bck", "rollup:es": "rollup --config rollup/es.config.js && sed -i.bck 's/^var /self./' es.js && rm -rf es.js.bck", "rollup:babel": "rollup --config rollup/babel.config.js && sed -i.bck 's/^var /self./' index.js && rm -rf index.js.bck", @@ -23,33 +23,27 @@ "author": "Andrea Giammarchi", "license": "ISC", "devDependencies": { - "@babel/core": "^7.16.0", - "@babel/preset-env": "^7.16.4", - "@rollup/plugin-babel": "^5.3.0", - "@rollup/plugin-node-resolve": "^13.0.6", - "@ungap/degap": "^0.2.8", + "@rollup/plugin-node-resolve": "^13.1.1", "ascjs": "^5.0.1", "c8": "^7.10.0", "coveralls": "^3.1.1", - "drop-babel-typeof": "^1.0.3", "linkedom": "^0.13.0", - "rollup": "^2.61.0", - "rollup-plugin-includepaths": "^0.2.4", + "rollup": "^2.61.1", "rollup-plugin-terser": "^7.0.2", "terser": "^5.10.0" }, "dependencies": { - "@ungap/create-content": "^0.3.1", + "@webreflection/create-content": "^0.1.1", + "@webreflection/dsm": "^0.1.0", + "@webreflection/uparser": "^0.2.1", + "@webreflection/uwire": "^1.1.0", "async-tag": "^0.2.0", "jsx2tag": "^0.3.0", "uarray": "^1.0.0", "udomdiff": "^1.1.0", - "uhandlers": "^0.6.0", - "umap": "^1.0.2", - "uparser": "^0.2.1", - "uwire": "^1.1.0" + "uhandlers": "^0.6.1" }, - "module": "./esm/index.js", + "module": "./es.js", "type": "module", "exports": { ".": { @@ -74,7 +68,7 @@ }, "./package.json": "./package.json" }, - "unpkg": "min.js", + "unpkg": "es.js", "repository": { "type": "git", "url": "git+https://github.com/WebReflection/uhtml.git" diff --git a/pony.js b/pony.js index c99dee5..c5ba5de 100644 --- a/pony.js +++ b/pony.js @@ -2,21 +2,19 @@ import {readFileSync, writeFileSync} from 'fs'; const dropIE = s => s.replace(/^import\s+.+/mg, '').replace(/^export\s+/mg, ''); -const createContent = readFileSync('./node_modules/@ungap/create-content/esm/index.js').toString(); -const uwire = readFileSync('./node_modules/uwire/esm/index.js').toString(); +const createContent = readFileSync('./node_modules/@webreflection/create-content/esm/index.js').toString(); +const uwire = readFileSync('./node_modules/@webreflection/uwire/esm/index.js').toString(); const uhandlers = readFileSync('./node_modules/uhandlers/esm/index.js').toString(); const init = readFileSync('./esm/init.js').toString(); -const node = readFileSync('./esm/node.js').toString(); const handlers = readFileSync('./esm/handlers.js').toString(); const rabbit = readFileSync('./esm/rabbit.js').toString(); const index = readFileSync('./esm/index.js').toString(); const outcome = [ - createContent.replace(/^export\s+.*/mg, ''), + createContent.replace(/^export\s+.*/mg, '').replace(/ svg\b/g, ' root').replace(/\bcreate\b/g, 'createContent'), dropIE(uwire), dropIE(uhandlers), - dropIE(node).replace(/^\{.+?\};/mg, ''), dropIE(handlers), dropIE(rabbit), dropIE(index).replace(/\bcache\b/g, '_cache').replace(/^\{/m, 'return {') diff --git a/rollup/async.config.js b/rollup/async.config.js index 71f7cb5..65efb56 100644 --- a/rollup/async.config.js +++ b/rollup/async.config.js @@ -1,15 +1,9 @@ import {nodeResolve} from '@rollup/plugin-node-resolve'; import {terser} from 'rollup-plugin-terser'; -import includePaths from 'rollup-plugin-includepaths'; export default { input: './esm/async.js', plugins: [ - includePaths({ - include: { - '@ungap/create-content': 'node_modules/@ungap/degap/create-content.js' - }, - }), nodeResolve(), terser() ], diff --git a/rollup/babel.config.js b/rollup/babel.config.js index dad2a8b..4be2157 100644 --- a/rollup/babel.config.js +++ b/rollup/babel.config.js @@ -1,14 +1,9 @@ import {nodeResolve} from '@rollup/plugin-node-resolve'; -import babel from '@rollup/plugin-babel'; export default { input: './esm/index.js', plugins: [ nodeResolve(), - babel({ - presets: ['@babel/preset-env'], - babelHelpers: 'bundled' - }) ], output: { diff --git a/rollup/es.config.js b/rollup/es.config.js index 7b571e0..593b69f 100644 --- a/rollup/es.config.js +++ b/rollup/es.config.js @@ -1,24 +1,16 @@ import {nodeResolve} from '@rollup/plugin-node-resolve'; import {terser} from 'rollup-plugin-terser'; -import includePaths from 'rollup-plugin-includepaths'; export default { input: './esm/index.js', plugins: [ - includePaths({ - include: { - '@ungap/create-content': 'node_modules/@ungap/degap/create-content.js' - }, - }), nodeResolve(), terser() ], output: { esModule: false, - exports: 'named', file: './es.js', - format: 'iife', - name: 'uhtml' + format: 'module' } }; diff --git a/test/coverage.js b/test/coverage.js index b27b190..79d7715 100644 --- a/test/coverage.js +++ b/test/coverage.js @@ -178,7 +178,6 @@ console.assert(!body.firstElementChild.hasAttribute('thing'), '?thing=${falsy}') // cover importNode delete require.cache[require.resolve('../cjs/handlers.js')]; -delete require.cache[require.resolve('../cjs/node.js')]; delete require.cache[require.resolve('../cjs/rabbit.js')]; delete require.cache[require.resolve('../cjs')]; const importNode = document.importNode; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy