fwebc.js

Toy framework for learning web components
git clone git://git.finwo.net/lib/fwebc.js
Log | Files | Refs | README

commit 0cf6dc0b36d7965bfdae171e0e2cabb8ec06179b
parent dec3c863fa6c34a57dc26c282180e041aaa38fbb
Author: finwo <finwo@pm.me>
Date:   Sun, 14 Feb 2021 22:39:14 +0100

Restructured a bit

Diffstat:
Mfwebc.js | 81++++++++++++++++++++++++++-----------------------------------------------------
Mfwebc.min.js | 2+-
2 files changed, 27 insertions(+), 56 deletions(-)

diff --git a/fwebc.js b/fwebc.js @@ -17,12 +17,6 @@ }; const util = fwebc.util = { - isObject(obj) { - if (null === obj) return false; - if ('object' !== typeof obj) return false; - if (Array.isArray(obj)) return false; - return true; - }, unescape(input) { const el = document.createElement('textarea'); el.innerHTML = input; @@ -80,85 +74,62 @@ if (window.customElements.get(name)) return; // Parse remplate - let template = document.createElement('template'); - template.innerHTML = source; - - // Remove template wrapper - while( - (template.content.children.length == 1) && - (template.content.firstChild instanceof HTMLTemplateElement) - ) { - template = template.content.firstChild; + const wrapper = document.createElement('template'); + wrapper.innerHTML = source; + + // Separate style, script & template + let template = null; + let scripts = []; + let styles = []; + for(const node of [...wrapper.content.children]) { + if (node instanceof HTMLTemplateElement) template = node; + if (node instanceof HTMLScriptElement ) {scripts.push(node);wrapper.content.removeChild(node);} + if (node instanceof HTMLStyleElement ) {styles.push(node);wrapper.content.removeChild(node);} } - - // Remove template wrapper around html section - if (template.content.firstChild instanceof HTMLTemplateElement) { - const wrapper = template.content.firstChild; - template.content.prepend(...wrapper.content.children); - template.content.removeChild(wrapper); + if (!template) { + template = wrapper; } - // Extract scripts + // Convert template and code into a string + template = util.unescape(template.innerHTML); let code = ''; - for(const child of [...template.content.children]) { - if (!(child instanceof HTMLScriptElement)) continue; - if (child.getAttribute('src')) continue; - code += child.innerHTML; - template.content.removeChild(child); + for(const script of scripts) { + if (script.getAttribute('src')) continue; + code += script.innerHTML; } - // Template should be a string - template = util.unescape(template.innerHTML); - // Register the actual element window.customElements.define(name, class extends HTMLElement { constructor() { super(); - - // Initialize shadow root - this.root = this.attachShadow({ mode: 'open' }); - - // Initial state + this.root = this.attachShadow({ mode: 'open' }); this.state = {}; - - // Run plugins - for (const plugin of plugins) { - plugin(this); - } - - // Run component code + for(const plugin of plugins) plugin(this); (new Function(code)).call(this); - - // Load dependencies - if (this.dependencies) { - this.dependencies.forEach(fwebc.load); - } - - // Start observing state & initial rendering + if (this.dependencies) this.dependencies.forEach(fwebc.load); this.state = util.observable(this.state, () => this.emit('update')); this.on('update', this.render.bind(this)); this.render(); } - render() { const fn = new Function(...Object.keys(this.state), 'return `'+template+'`;'); - const styles = Array.from(this.root.ownerDocument.styleSheets).map(stylesheet => stylesheet.ownerNode.outerHTML); + const stylez = Array + .from(this.root.ownerDocument.styleSheets) + .map(stylesheet => stylesheet.ownerNode.outerHTML) + .concat(styles.map(stylesheet => stylesheet.outerHTML)); try { - this.root.innerHTML = styles.join('') + fn(...Object.values(this.state)); + this.root.innerHTML = stylez.join('') + fn(...Object.values(this.state)); } catch(e) { console.error(e); } } - emit(event, data = {}) { const ev = new CustomEvent(event, data); this.dispatchEvent(ev); } - on(event, handler) { this.addEventListener(event, handler); } - }); }; diff --git a/fwebc.min.js b/fwebc.min.js @@ -1 +1 @@ -(e=>{"object"==typeof module&&"exports"in module?module.exports=e({fetch:require("node-fetch")}):"object"==typeof window&&(window.fwebc=e({fetch:window.fetch}))})(({fetch:e})=>{const t={},n=[],o={ext:"tag",base:"/partial"},r=t.util={isObject:e=>null!==e&&("object"==typeof e&&!Array.isArray(e)),unescape(e){const t=document.createElement("textarea");return t.innerHTML=e,0===t.childNodes.length?"":t.childNodes[0].nodeValue},observable(e,t,n=""){if(Object(e)!==e)throw new Error(`Object is not an object, got: ${e}`);if("function"!=typeof t)throw new Error(`Callback is not a function, got: ${t}`);for(const o of Object.keys(e))Object(e[o])===e[o]&&(e[o]=r.observable(e[o],t,`${n}${o}.`));return new Proxy(e,{set(e,o,s,i){if(e[o]===s)return;let c=o in e?"update":"add";const l={name:o,type:c,object:e};return"update"==c&&(l.oldValue=e[o]),e[o]=Object(s)===s?r.observable(s,t,`${n}${o}.`):s,t([l]),!0},deleteProperty(e,n,o){if(!(n in e))return;const r={name:n,type:"delete",object:e,oldValue:e[n]};return delete e[n],t([r]),!0}})}};return t.cfg=(e=>{Object.assign(o,e)}),t.install=(e=>{"function"==typeof e&&n.push(e)}),t.uninstall=(e=>{const t=n.indexOf(e);~t&&n.splice(t,1)}),t.register=((e,o)=>{if(window.customElements.get(e))return;let s=document.createElement("template");for(s.innerHTML=o;1==s.content.children.length&&s.content.firstChild instanceof HTMLTemplateElement;)s=s.content.firstChild;if(s.content.firstChild instanceof HTMLTemplateElement){const e=s.content.firstChild;s.content.prepend(...e.content.children),s.content.removeChild(e)}let i="";for(const e of[...s.content.children])e instanceof HTMLScriptElement&&(e.getAttribute("src")||(i+=e.innerHTML,s.content.removeChild(e)));s=r.unescape(s.innerHTML),window.customElements.define(e,class extends HTMLElement{constructor(){super(),this.root=this.attachShadow({mode:"open"}),this.state={};for(const e of n)e(this);new Function(i).call(this),this.dependencies&&this.dependencies.forEach(t.load),this.state=r.observable(this.state,()=>this.emit("update")),this.on("update",this.render.bind(this)),this.render()}render(){const e=new Function(...Object.keys(this.state),"return `"+s+"`;"),t=Array.from(this.root.ownerDocument.styleSheets).map(e=>e.ownerNode.outerHTML);try{this.root.innerHTML=t.join("")+e(...Object.values(this.state))}catch(e){console.error(e)}}emit(e,t={}){const n=new CustomEvent(e,t);this.dispatchEvent(n)}on(e,t){this.addEventListener(e,t)}})}),t.load=(n=>{e(`${o.base}/${n.replace(/-/g,"/")}.${o.ext}`).then(e=>e.text()).then(e=>{t.register(n,e)})}),t}); +(e=>{"object"==typeof module&&"exports"in module?module.exports=e({fetch:require("node-fetch")}):"object"==typeof window&&(window.fwebc=e({fetch:window.fetch}))})(({fetch:e})=>{const t={},n=[],o={ext:"tag",base:"/partial"},r=t.util={unescape(e){const t=document.createElement("textarea");return t.innerHTML=e,0===t.childNodes.length?"":t.childNodes[0].nodeValue},observable(e,t,n=""){if(Object(e)!==e)throw new Error(`Object is not an object, got: ${e}`);if("function"!=typeof t)throw new Error(`Callback is not a function, got: ${t}`);for(const o of Object.keys(e))Object(e[o])===e[o]&&(e[o]=r.observable(e[o],t,`${n}${o}.`));return new Proxy(e,{set(e,o,s,c){if(e[o]===s)return;let i=o in e?"update":"add";const a={name:o,type:i,object:e};return"update"==i&&(a.oldValue=e[o]),e[o]=Object(s)===s?r.observable(s,t,`${n}${o}.`):s,t([a]),!0},deleteProperty(e,n,o){if(!(n in e))return;const r={name:n,type:"delete",object:e,oldValue:e[n]};return delete e[n],t([r]),!0}})}};return t.cfg=(e=>{Object.assign(o,e)}),t.install=(e=>{"function"==typeof e&&n.push(e)}),t.uninstall=(e=>{const t=n.indexOf(e);~t&&n.splice(t,1)}),t.register=((e,o)=>{if(window.customElements.get(e))return;const s=document.createElement("template");s.innerHTML=o;let c=null,i=[],a=[];for(const e of[...s.content.children])e instanceof HTMLTemplateElement&&(c=e),e instanceof HTMLScriptElement&&(i.push(e),s.content.removeChild(e)),e instanceof HTMLStyleElement&&(a.push(e),s.content.removeChild(e));c||(c=s),c=r.unescape(c.innerHTML);let l="";for(const e of i)e.getAttribute("src")||(l+=e.innerHTML);window.customElements.define(e,class extends HTMLElement{constructor(){super(),this.root=this.attachShadow({mode:"open"}),this.state={};for(const e of n)e(this);new Function(l).call(this),this.dependencies&&this.dependencies.forEach(t.load),this.state=r.observable(this.state,()=>this.emit("update")),this.on("update",this.render.bind(this)),this.render()}render(){const e=new Function(...Object.keys(this.state),"return `"+c+"`;"),t=Array.from(this.root.ownerDocument.styleSheets).map(e=>e.ownerNode.outerHTML).concat(a.map(e=>e.outerHTML));try{this.root.innerHTML=t.join("")+e(...Object.values(this.state))}catch(e){console.error(e)}}emit(e,t={}){const n=new CustomEvent(e,t);this.dispatchEvent(n)}on(e,t){this.addEventListener(e,t)}})}),t.load=(n=>{e(`${o.base}/${n.replace(/-/g,"/")}.${o.ext}`).then(e=>e.text()).then(e=>{t.register(n,e)})}),t});