@cfdez11/vex 0.10.17 → 0.10.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -89,3 +89,4 @@ npm run dev
89
89
  - [ ] Authentication middleware
90
90
  - [ ] CDN cache integration
91
91
  - [ ] Fix Suspense marker replacement with multi-root templates
92
+ - [ ] Refactor template engine — replace `htmlparser2` + `dom-serializer` parse/transform/serialize pipeline with a build-time template compiler (à la Vue/Svelte) that generates render functions. Current limitations: `htmlparser2` decodes HTML entities on parse and `dom-serializer` doesn't re-encode them correctly (`&lt;` inside `<pre>` becomes literal `<`); whitespace inside `<pre>`/`<code>` is incorrectly stripped; workarounds patched but root cause is architectural. Build-time compilation would eliminate all these issues and improve runtime performance.
@@ -1 +1 @@
1
- import{setupLinkInterceptor as s}from"./link-interceptor.js";import{setupPrefetchObserver as f}from"./prefetch.js";import{navigateInternal as m}from"./navigate.js";import{findRouteWithParams as u}from"./router.js";import{createLayoutRenderer as p}from"./create-layouts.js";function w(){let t=null;const n=p();function i(){t&&t.abort()}async function r(e,l=!0){i();const o=new AbortController;t=o;try{await m({path:e,addToHistory:l,controller:o,layoutRenderer:n,onFinish:()=>{t===o&&(t=null)}})}catch(a){a.name!=="AbortError"&&console.error("Navigation error:",a)}}function c(){window.addEventListener("popstate",()=>{r(location.pathname,!1)}),s(r),f(),n.reset();const{route:e}=u(location.pathname);e?.meta?.ssr||r(location.pathname,!1)}return{navigate:r,initialize:c}}export{w as createNavigationRuntime};
1
+ import{setupLinkInterceptor as s}from"./link-interceptor.js";import{setupPrefetchObserver as f}from"./prefetch.js";import{navigateInternal as m}from"./navigate.js";import{findRouteWithParams as u}from"./router.js";import{createLayoutRenderer as p}from"./create-layouts.js";function g(){let t=null;const n=p();function i(){t&&t.abort()}async function o(r,l=!0){i();const e=new AbortController;t=e;try{await m({path:r,addToHistory:l,controller:e,layoutRenderer:n,onFinish:()=>{t===e&&(t=null)}})}catch(a){a.name!=="AbortError"&&console.error("Navigation error:",a)}}function c(){window.addEventListener("popstate",()=>{o(`${location.pathname}${location.search}${location.hash}`,!1)}),s(o),f(),n.reset();const{route:r}=u(location.pathname);r?.meta?.ssr||o(`${location.pathname}${location.search}${location.hash}`,!1)}return{navigate:o,initialize:c}}export{g as createNavigationRuntime};
@@ -1 +1 @@
1
- import{findRouteWithParams as l}from"./router.js";import{routes as m}from"../_routes.js";import{updateRouteParams as c}from"./use-route-params.js";import{renderPage as f}from"./render-page.js";import{renderSSRPage as d}from"./render-ssr.js";async function S({path:t,addToHistory:n,controller:i,layoutRenderer:r,onFinish:a}){c(t);const s=t.split("?")[0],{route:u}=l(s),e=u??m.find(o=>o.isNotFound)??null;n&&history.pushState({},"",t);try{if(e?.meta?.ssr){r.reset(),await d(t,i.signal);return}if(e?.meta?.requiresAuth&&!app.Store?.loggedIn){location.href="/account/login";return}if(e?.meta?.guestOnly&&app.Store?.loggedIn){location.href="/account";return}await f({route:e,layoutRenderer:r})}finally{a();const o=t.includes("#")?t.slice(t.indexOf("#")+1):null;o&&document.getElementById(o)?.scrollIntoView()}}export{S as navigateInternal};
1
+ import{findRouteWithParams as p}from"./router.js";import{routes as d}from"../_routes.js";import{updateRouteParams as P}from"./use-route-params.js";import{renderPage as w}from"./render-page.js";import{renderSSRPage as y}from"./render-ssr.js";async function H({path:i,addToHistory:s,controller:h,layoutRenderer:e,onFinish:c}){const u=`${location.pathname}${location.search}`,l=location.hash,t=new URL(i,window.location.origin),m=t.pathname,r=`${t.pathname}${t.search}`,n=`${r}${t.hash}`,f=u===r&&l!==t.hash;P(n);const{route:g}=p(m),a=g??d.find(o=>o.isNotFound)??null;s&&history.pushState({},"",n);try{if(f)return;if(a?.meta?.ssr){e.reset(),await y(n,h.signal);return}if(a?.meta?.requiresAuth&&!app.Store?.loggedIn){location.href="/account/login";return}if(a?.meta?.guestOnly&&app.Store?.loggedIn){location.href="/account";return}await w({route:a,layoutRenderer:e})}finally{c();const o=t.hash?t.hash.slice(1):null;o&&document.getElementById(o)?.scrollIntoView()}}export{H as navigateInternal};
@@ -1 +1 @@
1
- import{parseDocument,DomUtils}from"htmlparser2";import{render}from"dom-serializer";const fnCache=new Map;function getDataValue(expression,scope){const keys=Object.keys(scope),cacheKey=`${expression}::${keys.join(",")}`;fnCache.has(cacheKey)||fnCache.set(cacheKey,Function(...keys,`return (${expression})`));try{return fnCache.get(cacheKey)(...Object.values(scope))}catch{return""}}function isEmptyTextNode(node){return node.type==="text"&&/^\s*$/.test(node.data)}function parseHTMLToNodes(html){try{const dom=parseDocument(html,{xmlMode:!0});return DomUtils.getChildren(dom)}catch(error){return console.error("Error parsing HTML:",error),[]}}function processNode(node,scope,previousRendered=!1){if(node.type==="text")return node.data=node.data.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/(?<!\\)\{\{(.+?)\}\}/g,(_,expr)=>getDataValue(expr.trim(),scope)).replace(/\\\{\{/g,"{{"),node;if(node.type==="tag"){const attrs=node.attribs||{};for(const[attrName,attrValue]of Object.entries(attrs))typeof attrValue=="string"&&(attrs[attrName]=attrValue.replace(/(?<!\\)\{\{(.+?)\}\}/g,(_,expr)=>getDataValue(expr.trim(),scope)).replace(/\\\{\{/g,"{{"));if("x-if"in attrs){const show=getDataValue(attrs["x-if"],scope);if(delete attrs["x-if"],!show)return null}if("x-else-if"in attrs){const show=getDataValue(attrs["x-else-if"],scope);if(delete attrs["x-else-if"],previousRendered||!show)return null}if("x-else"in attrs&&(delete attrs["x-else"],previousRendered))return null;if("x-show"in attrs){const show=getDataValue(attrs["x-show"],scope);delete attrs["x-show"],show||(attrs.style=(attrs.style||"")+"display:none;")}if("x-for"in attrs){const exp=attrs["x-for"];delete attrs["x-for"];const match=exp.match(/(.+?)\s+in\s+(.+)/);if(!match)throw new Error("Invalid x-for format: "+exp);const itemName=match[1].trim(),listExpr=match[2].trim(),list=getDataValue(listExpr,scope);if(!Array.isArray(list))return null;const clones=[];for(const item of list){const cloned=structuredClone(node),newScope={...scope,[itemName]:item};clones.push(processNode(cloned,newScope))}return clones}for(const[name,value]of Object.entries({...attrs})){if(name.startsWith(":")){const isSuspenseFallback=name===":fallback"&&node.name==="Suspense",realName=name.slice(1);if(isSuspenseFallback)attrs[realName]=value;else{const val=getDataValue(value,scope);attrs[realName]=val!=null&&typeof val=="object"?JSON.stringify(val):String(val??"")}delete attrs[name]}if(name.startsWith("x-bind:")){const realName=name.slice(7),val=getDataValue(value,scope);attrs[realName]=val!=null&&typeof val=="object"?JSON.stringify(val):String(val??""),delete attrs[name]}}for(const[name]of Object.entries({...attrs}))(name.startsWith("@")||name.startsWith("x-on:"))&&delete attrs[name];if(node.children){const result=[];let isPreviousRendered=!1;const preserveWhitespace=node.name==="pre"||node.name==="textarea";for(const child of node.children){if(!preserveWhitespace&&isEmptyTextNode(child))continue;const processed=processNode(child,scope,isPreviousRendered);Array.isArray(processed)?(result.push(...processed),isPreviousRendered=processed.length>0):processed?(result.push(processed),isPreviousRendered=!0):isPreviousRendered=!1}node.children=result}return node}return node}const parsedTemplateCache=new Map;function compileTemplateToHTML(template,data={}){try{parsedTemplateCache.has(template)||parsedTemplateCache.set(template,parseHTMLToNodes(template));const processed=structuredClone(parsedTemplateCache.get(template)).map(n=>processNode(n,data)).flat().filter(Boolean);return render(processed,{encodeEntities:!1})}catch(error){throw console.error("Error compiling template:",error),error}}export{compileTemplateToHTML};
1
+ import{parseDocument,DomUtils}from"htmlparser2";import{render}from"dom-serializer";const fnCache=new Map;function getDataValue(expression,scope){const keys=Object.keys(scope),cacheKey=`${expression}::${keys.join(",")}`;fnCache.has(cacheKey)||fnCache.set(cacheKey,Function(...keys,`return (${expression})`));try{return fnCache.get(cacheKey)(...Object.values(scope))}catch{return""}}function isEmptyTextNode(node){return node.type==="text"&&/^\s*$/.test(node.data)}function parseHTMLToNodes(html){try{const dom=parseDocument(html,{xmlMode:!0});return DomUtils.getChildren(dom)}catch(error){return console.error("Error parsing HTML:",error),[]}}function processNode(node,scope,previousRendered=!1){if(node.type==="text")return node.data=node.data.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/(?<!\\)\{\{(.+?)\}\}/g,(_,expr)=>getDataValue(expr.trim(),scope)).replace(/\\\{\{/g,"{{"),node;if(node.type==="tag"){const attrs=node.attribs||{};for(const[attrName,attrValue]of Object.entries(attrs))typeof attrValue=="string"&&(attrs[attrName]=attrValue.replace(/(?<!\\)\{\{(.+?)\}\}/g,(_,expr)=>getDataValue(expr.trim(),scope)).replace(/\\\{\{/g,"{{"));if("x-if"in attrs){const show=getDataValue(attrs["x-if"],scope);if(delete attrs["x-if"],!show)return null}if("x-else-if"in attrs){const show=getDataValue(attrs["x-else-if"],scope);if(delete attrs["x-else-if"],previousRendered||!show)return null}if("x-else"in attrs&&(delete attrs["x-else"],previousRendered))return null;if("x-show"in attrs){const show=getDataValue(attrs["x-show"],scope);delete attrs["x-show"],show||(attrs.style=(attrs.style||"")+"display:none;")}if("x-for"in attrs){const exp=attrs["x-for"];delete attrs["x-for"];const match=exp.match(/(.+?)\s+in\s+(.+)/);if(!match)throw new Error("Invalid x-for format: "+exp);const itemName=match[1].trim(),listExpr=match[2].trim(),list=getDataValue(listExpr,scope);if(!Array.isArray(list))return null;const clones=[];for(const item of list){const cloned=structuredClone(node),newScope={...scope,[itemName]:item};clones.push(processNode(cloned,newScope))}return clones}for(const[name,value]of Object.entries({...attrs})){if(name.startsWith(":")){const isSuspenseFallback=name===":fallback"&&node.name==="Suspense",realName=name.slice(1);if(isSuspenseFallback)attrs[realName]=value;else{const val=getDataValue(value,scope);attrs[realName]=val!=null&&typeof val=="object"?JSON.stringify(val):String(val??"")}delete attrs[name]}if(name.startsWith("x-bind:")){const realName=name.slice(7),val=getDataValue(value,scope);attrs[realName]=val!=null&&typeof val=="object"?JSON.stringify(val):String(val??""),delete attrs[name]}}for(const[name]of Object.entries({...attrs}))(name.startsWith("@")||name.startsWith("x-on:"))&&delete attrs[name];if(node.children){const result=[];let isPreviousRendered=!1;const preserveWhitespace=node.name==="pre"||node.name==="textarea"||node.name==="code";for(const child of node.children){if(!preserveWhitespace&&isEmptyTextNode(child))continue;const processed=processNode(child,scope,isPreviousRendered);Array.isArray(processed)?(result.push(...processed),isPreviousRendered=processed.length>0):processed?(result.push(processed),isPreviousRendered=!0):isPreviousRendered=!1}node.children=result}return node}return node}const parsedTemplateCache=new Map;function compileTemplateToHTML(template,data={}){try{parsedTemplateCache.has(template)||parsedTemplateCache.set(template,parseHTMLToNodes(template));const processed=structuredClone(parsedTemplateCache.get(template)).map(n=>processNode(n,data)).flat().filter(Boolean);return render(processed,{encodeEntities:!1})}catch(error){throw console.error("Error compiling template:",error),error}}export{compileTemplateToHTML};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfdez11/vex",
3
- "version": "0.10.17",
3
+ "version": "0.10.19",
4
4
  "description": "A vanilla JavaScript meta-framework with file-based routing, SSR/CSR/SSG/ISR and Vue-like reactivity",
5
5
  "repository": {
6
6
  "type": "git",