hmpl-js
Version:
🐜 HMPL.js is a lightweight server-oriented template language for JavaScript. Fetch HTML, render it safely, and keep apps dynamic, modern, and small.
1 lines • 17.4 kB
JavaScript
!function(e,t){"object"==typeof module&&module.exports?module.exports=t():"function"==typeof define&&define.amd?define([],t):e.hmpl=e.hmpl||t()}("undefined"!=typeof self?self:this,(function(){return function(){"use strict";const e="src",t="method",o="initId",n="after",s="repeat",r="memo",i="indicators",l="autoBody",c="hmpl",a="formData",d="disallowedTags",p="sanitize",f="allowedContentTypes",u="get",h="interval",m="bind",$="target",y="prefix",g="BadResponseError",v="RequestInitError",b="RenderError",w="RequestComponentError",T="CompileOptionsError",N="ParseError",O="CompileError",P={formData:!0},x={formData:!1},E=[e,t,o,n,s,i,r,l,f,d,p,h,m],j=["get","post","put","delete","patch","trace","options"],k=[100,101,102,103,300,301,302,303,304,305,306,307,308,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,421,422,423,424,425,426,428,429,431,451,500,501,502,503,504,505,506,507,508,510,511],A=["script","style","iframe"],C=["text/html"],I=[],q=e=>"object"==typeof e&&!Array.isArray(e)&&null!==e,D=e=>"[object Function]"===Object.prototype.toString.call(e),S=e=>{throw new Error(e)},B=e=>{console.warn(e)},R=(e,t=!1,o)=>{let n=e;t&&(n=o?DOMPurify.sanitize(e,o):DOMPurify.sanitize(e));return(new DOMParser).parseFromString(`<template>${n}</template>`,"text/html").childNodes[0].childNodes[0].firstChild},M=(e,t,o,n)=>({prop:e,value:t,context:o,request:n}),z=(e,t,o,n,s,r,i,l,c={},a,d,p,f,u,h,m,$,y)=>{const{mode:b,cache:w,redirect:T,get:N,referrerPolicy:O,signal:P,credentials:x,timeout:E,referrer:j,headers:k,body:A,window:C,integrity:I}=c,D={method:n.toUpperCase()};void 0!==x&&(D.credentials=x),void 0!==A&&(D.body=A),void 0!==b&&(D.mode=b),void 0!==w&&(D.cache=w),void 0!==T&&(D.redirect=T),void 0!==O&&(D.referrerPolicy=O),void 0!==I&&(D.integrity=I),void 0!==j&&(D.referrer=j);const z=void 0!==P;if(z&&(D.signal=P),void 0!==C&&(D.window=C),void 0!==c.keepalive&&B(`${v}: The "keepalive" property is not yet supported`),k)if(q(k)){const e=new Headers;for(const t in k){const o=k[t],n=typeof o;"string"===n?e.set(t,o):S(`${v}: Expected type string, but received type ${n}`)}D.headers=e}else S(`${v}: The "headers" property must contain a value object`);E&&(z?B(`${v}: The "signal" property overwrote the AbortSignal from "timeout"`):D.signal=AbortSignal.timeout(E));const L=l&&!r&&o?.memo,U=e=>"rejected"===e||"number"==typeof e&&(e<200||e>299),V=F(void 0,y),W=e=>{if(!u)return;const{dependentAttrs:t}=u;u.value=e;for(let e=0;e<t.length;e++){t[e].setValue()}},H=e=>{i&&(m.response=e,N?.(M("response",e,V,m))),N?.(M("response",t,V))},_=(t,n=!0,s=!1)=>{if(r)a.response=t.cloneNode(!0),N?.(M("response",t,V));else{let r=[];const i=[...(n?t.cloneNode(!0):t).content.childNodes];if(o.nodes){const e=o.parentNode,t=[],n=o.nodes.length;for(let s=0;s<n;s++){const r=o.nodes[s];if(s===n-1)for(let o=0;o<i.length;o++){const n=i[o],s=e.insertBefore(n,r);t.push(s)}e.removeChild(r)}r=t.slice(),o.nodes=t}else{const t=e.parentNode,n=[],s=i.length;for(let o=0;o<s;o++){const s=i[o],r=t.insertBefore(s,e);n.push(r)}t.removeChild(e),r=n.slice(),o.nodes=n,o.parentNode=t}L&&s&&(o.memo.nodes=o.nodes,o.memo.isPending&&(o.memo.isPending=!1)),H(r)}};let G=!1;const J=()=>{if(r)a.response=void 0,N?.(M("response",void 0,V));else if(o?.nodes){const e=o.parentNode,n=o.nodes.length;for(let t=0;t<n;t++){const s=o.nodes[t];t===n-1&&e.insertBefore(o.comment,s),e.removeChild(s)}o.nodes=null,o.parentNode=null,i&&(m.response=void 0,N?.(M("response",void 0,V,m))),N?.(M("response",t,V))}L&&null!==o.memo.response&&(o.memo.response=null,delete o.memo.isPending,delete o.memo.nodes)},X=e=>{i?m.status!==e&&(m.status=e,W(e),N?.(M("status",e,V,m))):a.status!==e&&(a.status=e,W(e),N?.(M("status",e,V))),L&&U(e)&&(o.memo.response=null,delete o.memo.nodes),(e=>{if($)if(L&&"pending"!==e&&U(e)&&o.memo.isPending&&(o.memo.isPending=!1),"pending"===e){const e=$.pending;void 0!==e&&(L&&(o.memo.isPending=!0),_(e))}else if("rejected"===e){const e=$.rejected;if(void 0!==e)_(e);else{const e=$.error;void 0!==e?_(e):J()}}else{const t=$[`${e}`];if(e>399)if(void 0!==t)_(t);else{const e=$.error;void 0!==e?_(e):J()}else(e<200||e>299)&&(G=!0,void 0!==t?_(t):J())}})(e)};let Z=200;X("pending");let K=!0,Q=!0;fetch(s,D).then((e=>{if(K=!1,Z=e.status,X(Z),e.ok||($&&(Q=!1),S(`${g}: Response with status code ${Z}`)),Array.isArray(d)&&0!==d.length){const t=e.headers.get("Content-Type");((e,t)=>{if(!e)return!0;let o=!1;for(let n=0;n<t.length;n++){const s=t[n];if(e.includes(s)){o=!0;break}}return!o})(t,d)&&S(`${g}: Expected ${d.map((e=>`"${e}"`)).join(", ")}, but received "${t}"`)}return e.text()})).then((n=>{if(!G){if(L){const{response:e}=o.memo;if(null===e)o.memo.response=n;else{if(e===n)return void(()=>{if(o.memo.isPending){const e=o.parentNode,t=o.memo.nodes,n=o.nodes,s=n.length,r=[];for(let o=0;o<s;o++){const i=n[o];if(o===s-1)for(let o=0;o<t.length;o++){const n=t[o],s=e.insertBefore(n,i);r.push(s)}e.removeChild(i)}o.nodes=r.slice(),o.memo.isPending=!1,o.memo.nodes=r.slice()}const e=o.nodes.slice();H(e)})();o.memo.response=n,delete o.memo.nodes}}const s=((e,t=[],o,n)=>{const s=R(e,o,n),r=s.content;for(let e=0;e<t.length;e++){const o=t[e],n=r.querySelectorAll(o);for(let e=0;e<n.length;e++)r.removeChild(n[e])}return s})(n,p,f,h);if(r)a.response=s,N?.(M("response",s,V));else{const n=[],r=[...s.content.childNodes];if(o)_(s,!1,!0);else{const o=e.parentNode;for(let t=0;t<r.length;t++){const s=r[t],l=o.insertBefore(s,e);i&&n.push(l)}o.removeChild(e),i&&(m.response=n,N?.(M("response",n,V,m))),N?.(M("response",t,V))}}}})).catch((e=>{throw K?(X("rejected"),$||J()):Q&&J(),e}))},F=(e,t)=>{const o={};return void 0!==e&&(o.event=e),t&&(o.clearInterval=t),{request:o}},L=(i,a,u,g,T,O,E,A,B,M,L=!1)=>{const _=(i,c)=>{const a=i[e];if(a){const e=(i[t]||"GET").toLowerCase();if((e=>j.includes(e.toLowerCase()))(e)){const t=i[n];let u=i[m];if(t&&L&&S(`${b}: EventTarget is undefined`),u&&L&&S(`${b}: Binding target is undefined`),u){let e;const t=q(u);if(J(u,t),t){const t=u[y];void 0!==t&&(e=t),u=u[$]}g.hasOwnProperty(u)?S(`${w}: Duplicate binding target value "${u}"`):g[u]=void 0!==e?e:null}const N=!i.hasOwnProperty(s),j="all"===(!!N||i[s]?"all":"one"),_=i[h],X=!i.hasOwnProperty(r),Z=!i.hasOwnProperty(h),K=T.sanitizeConfig;let Q=!O&&T[r];X?Q&&(t&&j||(Q=!1)):t?i[r]?j?Q=!0:S(`${w}: Memoization works in the enabled repetition mode`):Q=!1:S(`${w}: Memoization works in the enabled repetition mode`),Z||j&&t&&S(`${w}: The "${h}" property does not work with repetition mode yet`);const Y=!i.hasOwnProperty(l);let ee=!E&&T[l];if(Y)!0===ee&&(ee=P),t||(ee=!1);else if(t){let e=i[l];if(V(e),!0===ee&&(ee=P),!0===e&&(e=P),!1===e)ee=!1;else{const t={...!1===ee?x:ee,...e};ee=t}}else ee=!1,S(`${w}: The "${l}" property does not work without the "${n}" property`);const te=!i.hasOwnProperty(f);let oe=A?C:T[f];if(!te){const e=i[f];U(e),oe=e}const ne=!i.hasOwnProperty(d);let se=B?I:T[d];if(!ne){const e=i[d];W(e),se=e}const re=!i.hasOwnProperty(p);let ie=!M&&T[p];if(!re){const e=i[p];H(e),ie=e}const le=i[o],ce=i.nodeId;let ae=i.indicators;if(ae){const e=e=>{const{trigger:t,content:o}=e;t||S(`${w}: Failed to activate or detect the indicator`),o||S(`${w}: Failed to activate or detect the indicator`),-1===k.indexOf(t)&&"pending"!==t&&"rejected"!==t&&"error"!==t&&S(`${w}: Failed to activate or detect the indicator`);const n=R(o);return{...e,content:n}},t={},o=[];for(let n=0;n<ae.length;n++){const s=e(ae[n]),{trigger:r}=s;-1===o.indexOf(r)?o.push(r):S(`${w}: Indicator trigger must be unique`),t[`${r}`]=s.content}ae=t}const de=(e,t=!1)=>{if(t){if(le){let t;for(let o=0;o<e.length;o++){const n=e[o];if(n.id===le){t=n.value;break}}return t||S(`${w}: ID referenced by request not found`),t}return{}}return le&&S(`${w}: ID referenced by request not found`),e},pe=void 0!==_,fe=j&&t||pe,ue=(t,o,n,s,r,i,l=!1,d,p=!1,f,h,m)=>{const $=s.currentId;if(L)t||(t=c);else if(!t){let e;const{els:o}=s;for(let t=0;t<o.length;t++){const n=o[t];if(n.id===ce){f=n,e=n.el;break}}t=e}let y;L||(fe||ae)&&(y=f.objNode,y||(y={id:$,nodes:null,parentNode:null,comment:t},Q&&(y.memo={response:null},ae&&(y.memo.isPending=!1)),pe&&m&&(y.interval={value:m,clearInterval:()=>clearInterval(m)}),f.objNode=y,s.dataObjects.push(y),s.currentId++));let g=de(o,l);const b=D(g);if(!b&&g&&(g={...g}),ee&&ee.formData&&h&&!b){const{type:e,target:t}=h;"submit"===e&&t&&t instanceof HTMLFormElement&&"FORM"===t.nodeName&&(g.body=new FormData(t,h.submitter))}const w=void 0!==u?r[u]:void 0;let T=m?()=>clearInterval(m):void 0;T=L?T:y?.interval?.clearInterval;const N=b?((e,t,o)=>e(F(t,o)))(g,h,T):g;q(N)||void 0===N||S(`${v}: Expected an object with initialization options`),z(t,i,y,e,a,L,p,Q,N,n,oe,se,ie,w,K,d,ae,T)};let he=ue;if(_){G(_);const e=Number(_);he=(t,o,n,s,r,i,l=!1,c,a=!1,d,p)=>{let f=null;f=setInterval((()=>{ue(t,o,n,s,r,i,l,c,a,d,p,f)}),e)}}let me=he;if(t){const e=(e,t,o,n,s,r,i,l,c,a,d,p)=>{const f=a.querySelectorAll(o);0===f.length&&S(`${b}: Selectors nodes not found`);const u=j?t=>{he(e,n,s,r,i,a,l,d,c,p,t)}:o=>{he(e,n,s,r,i,a,l,d,c,p,o);for(let e=0;e<f.length;e++){f[e].removeEventListener(t,u)}};for(let e=0;e<f.length;e++){f[e].addEventListener(t,u)}};if(t.indexOf(":")>0){const o=t.split(":"),n=o[0],s=o.slice(1).join(":");me=(t,o,r,i,l,c,a=!1,d,p=!1,f)=>{e(t,n,s,o,r,i,l,a,p,c,d,f)}}else S(`${w}: The "${n}" property doesn't work without EventTargets`)}else N||S(`${w}: The "${s}" property doesn't work without "${n}" property`);return me}S(`${w}: The "${t}" property has only GET, POST, PUT, PATCH, TRACE, OPTIONS or DELETE values`)}else S(`${w}: The "${e}" property are not found or empty`)};let X;if(L)u[0].el=i,X=_(u[0]);else{let e=-2;const t=o=>{if(e++,8==o.nodeType){let t=o.nodeValue;if(t&&t.startsWith(c)){t=t.slice(4);const n=Number(t),s=u[n];(Number.isNaN(n)||void 0===s)&&S(`${N}: Block helper with id "${n}" not found`),s.el=o,s.nodeId=e}}if(o.hasChildNodes()){const e=o.childNodes;for(let o=0;o<e.length;o++)t(e[o])}};if(t(i),u.length>1){const e=[];for(let t=0;t<u.length;t++){const o=u[t];e.push(_(o,i))}X=(t,o,n,s,r,i,l=!1)=>{t||(t=i);const c=[],a=s.els;for(let i=0;i<a.length;i++){const d=a[i],p=d.el,f={response:void 0};(0,e[i])(p,o,n,s,r,t,l,f,!0,d),c.push(f)}n.requests=c}}else{X=_(u[0],i)}}return a(X)},U=(e,t=!1)=>{const o=t?T:w;"*"===e||((e,t)=>{if(!Array.isArray(e))return!1;let o=!0;for(let n=0;n<e.length;n++)if("string"!=typeof e[n]){S(`${t}: In the array, the element with index ${n} is not a string`),o=!1;break}return o})(e,o)||S(`${o}: Expected "*" or string array, but got neither`)},V=(e,t=!1)=>{const o=q(e),n=t?T:w;if("boolean"==typeof e||o||S(`${n}: Expected a boolean or object, but got neither`),o)for(const t in e)if(t===a)"boolean"!=typeof e[a]&&S(`${n}: The "${a}" property should be a boolean`);else S(`${n}: Unexpected property "${t}"`)},W=(e,t=!1)=>{const o=t?T:w;Array.isArray(e)||S(`${o}: The value of the property "${d}" must be an array`);for(let t=0;t<e.length;t++){const n=e[t];A.includes(n)||S(`${o}: The value "${n}" is not processed`)}},H=(e,t=!1)=>{"boolean"!=typeof e&&S(`${t?T:w}: The value of the property "${p}" must be a boolean`)},_=e=>{e.hasOwnProperty("id")&&e.hasOwnProperty("value")||S(`${v}: Missing "id" or "value" property`)},G=e=>{"number"!=typeof e&&S(`${w}: The "${h}" value must be number`)},J=(e,t)=>{const o=e=>{e.includes(" ")&&S(`${w}: The binding target "${e}" must not contain spaces`)};if(void 0===t&&(t=q(e)),"string"==typeof e||t||S(`${w}: The "${m}" value must be a string or an object`),t){e.hasOwnProperty($)||S(`${w}: The "${$}" property is missing`);for(const t in e)switch(t){case $:case y:const n=$===t,s=n?$:y;"string"!=typeof e[t]&&S(`${w}: The "${s}" property should be a string`),n&&o(e[t]);break;default:S(`${w}: Unexpected property "${t}"`)}}else o(e)};return{compile:(e,t={})=>{"string"!=typeof e&&S(`${O}: Template was not found or the type of the passed value is not string`),e||S(`${O}: Template must not be a falsy value`),q(t)||S(`${T}: Options must be an object`);const n=!t.hasOwnProperty(r);n||"boolean"==typeof t[r]||S(`${T}: The value of the property ${r} must be a boolean`);const a=!t.hasOwnProperty(l);a||V(t[l],!0);const $=!t.hasOwnProperty(f);$||U(t[f],!0);const y=!t.hasOwnProperty(d);y||W(t[d],!0);const g=!t.hasOwnProperty(p);g||H(t[p],!0);const P=[],x=[],j=e=>{let t="";const o=/\s*([a-zA-Z0-9_-]+)\s*=\s*(("[^"]*"|'[^']*'|`[^`]*`|\[[^\]]*\]|\{[^}]*\}|true|false|\d+)(?=\s|,|$))\s*/g;let n;for(;null!==(n=o.exec(e));){t+=`${n[1].trim()}:${n[2].trim()},`}return t.replace(/,$/,"").trim()},k=(e=>{const t=[];let o=0;const n=[{open:"{{#request",close:"{{/request}}"},{open:"{{#r",close:"{{/r}}"}];for(;o<e.length;){const s=n.map((t=>({...t,index:e.indexOf(t.open,o)}))).filter((e=>-1!==e.index));if(0===s.length){t.push(e.slice(o));break}const r=s.sort(((e,t)=>e.index-t.index))[0];t.push(e.slice(o,r.index));const i=r.index+r.open.length,l=e.indexOf("}}",i);-1===l&&S(`${N}: Unclosed block (no ending '}}') for ${r.open}`);const c=e.slice(i,l).trim(),a=e.indexOf(r.close,l);-1===a&&S(`${N}: No closing '${r.close}' found for ${r.open}}}`);const d=e.slice(l+2,a);d.includes(r.open)&&S(`${N}: Nested ${r.open}}} blocks are not supported`);const p=j(c);if(r.open.startsWith(n[0].open)||r.open.startsWith(n[1].open)){const e=[],o=/{{#indicator\s+([^}]*)}}([\s\S]*?){{\/indicator}}/g;let n;for(;null!==(n=o.exec(d));){const t=n[1].trim();let o=n[2].trim();const s="{{#indicator";o.includes(s)&&S(`${N}: Nested ${s}}} blocks are not supported`);const r=j(t);o=o.replace(/\n/g," ").replace(/\s+/g," ").trim();const i=o.replace(/\\/g,"\\\\").replace(/`/g,"\\`").replace(/\$/g,"\\$");e.push(`{${r}, content:'${i}'}`)}const s=e.length>0?`,indicators:[${e.join(",")}]`:"";t.push(`{${p}${s}}`)}x.push(t.length-1),o=a+r.close.length}return t})(e);0===x.length&&S(`${N}: Block helper not found`);const A=e=>{const t=JSON5.parse(e);for(const e in t){const n=t[e];switch(E.includes(e)||S(`${w}: Property "${e}" is not processed`),e){case i:Array.isArray(n)||S(`${w}: The value of the property "${e}" must be an array`);break;case o:"string"!=typeof n&&"number"!=typeof n&&S(`${w}: The value of the property "${e}" must be a string`);break;case r:case s:"boolean"!=typeof n&&S(`${w}: The value of the property "${e}" must be a boolean value`);break;case l:V(n);break;case f:U(n);break;case d:W(n);break;case p:H(n);break;case h:G(n);break;case m:J(n);break;default:"string"!=typeof n&&S(`${w}: The value of the property "${e}" must be a string`)}}const n={...t};P.push(n)};for(let e=0;e<x.length;e++){const t=x[e];A(k[t]);const o=`\x3c!--hmpl${e}--\x3e`;k[t]=o}e=k.join("");let C=!1;const I=(e=>{const t=R(e.trim());(t.content.childNodes.length>1||1!==t.content.children.length&&8!==t.content.childNodes[0].nodeType)&&S(`${b}: Template includes only one node of the Element type or one response object`);const o=e=>{switch(e.nodeType){case Node.ELEMENT_NODE:if("PRE"===e.tagName)return;break;case Node.TEXT_NODE:if(!/\S/.test(e.textContent))return void e.remove()}for(let t=0;t<e.childNodes.length;t++)o(e.childNodes.item(t))};o(t.content.childNodes[0]);let n=t.content.firstElementChild;if(!n){const e=t.content.firstChild;8===e?.nodeType&&(C=!0,n=e)}return n})(e),B={},M=e=>({dependentAttrs:[],prefix:null!==e?e:"hmpl-status-"});return L(I,(e=>(t={})=>{const o={};for(const e in B){const t=B[e];o[e]=M(t)}const n=I.cloneNode(!0),s={response:C?void 0:n},r={dataObjects:[],els:[],currentId:0};if(!C){let e=-2;const t=n=>{if(e++,1===n.nodeType){const e=n;for(const{value:t,name:n}of Array.from(e.attributes)){if([...t.matchAll(/{{(.*?)}}/g)].map((e=>e[1]))){const s={setValue:()=>{}},r=t.split(/(\{\{\s*[^}]+\s*\}\})/),i={};for(let e=0;e<r.length;e++){const t=r[e];if(!t.trim())continue;const n=t.match(/\{\{\s*([^}]+)\s*\}\}/);if(n){const t=n[1].trim();i[t]||(o.hasOwnProperty(t)||S(`${b}: Request with binding source "${t}" not found`),o[t].dependentAttrs.push(s),i[t]=[]),i[t].push(e)}}const l=()=>{const e=[...r];for(const t in i){const n=i[t],{value:s,prefix:r}=o[t],l=void 0!==s?`${r}${t}-${s}`:"";for(let t=0;t<n.length;t++)e[n[t]]=l}return e.join("")},c=()=>{const t=l();e.setAttribute(n,t)};s.setValue=c}}}if(8==n.nodeType){const t=n.nodeValue;if(t&&t.startsWith(c)){const t={el:n,id:e};r.els.push(t)}}if(n.hasChildNodes()){const e=n.childNodes;for(let o=0;o<e.length;o++)t(e[o])}};t(n)}for(const e in o){const{dependentAttrs:t}=o[e];if(!t.length){S(`${b}: Binding target "${e}" not found`);break}}var i;return q(t)||D(t)?(q(i=t)&&i.hasOwnProperty(`${u}`)&&(D(i[u])||S(`${v}: The "${u}" property has a function value`)),e(void 0,t,s,r,o,n)):Array.isArray(t)?((e=>{const t=[];for(let o=0;o<e.length;o++){const n=e[o];q(n)||S(`${v}: IdentificationRequestInit is of type object`),_(n);const{id:s}=n,r="string"==typeof n.id;r||"number"==typeof n.id||S(`${v}: ID must be a string or a number`),t.indexOf(s)>-1?S(`${v}: ID with value ${r?`"${s}"`:s} already exists`):t.push(s)}})(t),e(void 0,t,s,r,o,n,!0)):S(`${v}: The type of the value being passed does not match the supported types for RequestInit`),s}),P,B,t,n,a,$,y,g,C)},stringify:e=>{const t=e=>"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e?`${e}`:Array.isArray(e)?`[${e.map((e=>t(e))).join(",")}]`:"object"==typeof e&&null!==e?`{${Object.entries(e).map((([e,o])=>`${e}:${t(o)}`)).join(",")}}`:"";let o=Object.entries(e).map((([e,o])=>`${e}=${t(o)}`)).join(" ");return o.endsWith("}")&&(o+=" "),`{{#request ${o}}}{{/request}}`}}}()}));