vaderjs 1.3.3-5b5a772ebe58 → 1.3.3-612-hotfix

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
@@ -14,7 +14,7 @@
14
14
  [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Postr-Inc/Vader.js/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/vaderjs.svg?style=flat)](https://www.npmjs.com/package/vaderjs)
15
15
 
16
16
 
17
-
17
+ > Do not use any alpha versions as these where changed multiple times any version under latest is considered lts and are deemed to be stable
18
18
  ## Get Started
19
19
 
20
20
  2. Install vaderjs
@@ -81,7 +81,7 @@ export default function(req, res){
81
81
 
82
82
  return <>
83
83
  <h1>${count}</h1>
84
- <button onClick={(count, setCount)=>{setCount(count + 1)}}>
84
+ <button onClick={(event)=>{setCount(count + 1)}}>
85
85
  </>
86
86
  }
87
87
 
@@ -126,9 +126,11 @@ export function Layout({title, keywords, description, children}){
126
126
 
127
127
  // pages/index.jsx
128
128
 
129
+ //$= is a ternary operator used for spread like nesting
130
+
129
131
  export default function (req, res){
130
132
  return <>
131
- <Layout {...{title:'home', description:'home page', keywords:'vader.js', logo:''}}>
133
+ <Layout $={{title:'home', description:'home page', keywords:'vader.js', logo:''}}>
132
134
  <h1> Hello World</h1>
133
135
  </Layout>
134
136
  </>
@@ -173,6 +175,8 @@ export default class MyApp extends Component{
173
175
  contructor(){
174
176
  super()
175
177
  this.key = 'static key for state changes'
178
+ // or
179
+ this.nokey // disable element generation
176
180
  }
177
181
 
178
182
  render(){
@@ -215,7 +219,7 @@ return <>
215
219
  </>
216
220
  ```
217
221
 
218
- and lower level invokes - these operate the same just allow you to pass items from top level to lower level: ex - I have a variable named car and i want the button to log it i can pass it as a parameter to allow it to be added to the buttons function scope
222
+ Low level invokes are considered top level and can access - any value above the scope !!
219
223
 
220
224
  ```jsx
221
225
  let car = {
@@ -223,7 +227,7 @@ let car = {
223
227
  price: 'toomiuch'
224
228
  }
225
229
  return <>
226
- <button onclick={(car)=>{
230
+ <button onclick={(event)=>{
227
231
  console.log(car.model)
228
232
  }}>Log</button>
229
233
  ```
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "vaderjs",
3
3
  "description": "A Reactive library aimed to helping you build reactive applications inspired by react.js",
4
4
  "module": "vader.js",
5
- "version": "1.3.3-5b5a772ebe58",
5
+ "version": "1.3.3-612-hotfix",
6
6
  "bin": {
7
7
  "vader": "./vader.js"
8
8
  },
@@ -29,7 +29,9 @@
29
29
  "dependencies": {
30
30
  "glob": "latest",
31
31
  "puppeteer":"latest",
32
- "dotenv":"latest"
32
+ "dotenv":"latest",
33
+ "prettier": "latest",
34
+ "source-map": "latest"
33
35
 
34
36
  }
35
37
  }
package/runtime/router.js CHANGED
@@ -1 +1 @@
1
- import{Component}from"./vader.js";let middlewares=[];class VaderRouter{constructor(e,t){this.routes=[],this.middlewares=[],this.errorMiddlewares=[],this.listeners=[],this.basePath=e}get(e,t){this.routes.push({path:e,handler:t,method:"get"})}use(e){this.middlewares.push(e)}listen(e,t){e||(e=Math.random().toString(36).substring(7)),window.onpopstate=async e=>{let t=window.location.pathname,r=`/${t.split("/")[1]}`;console.log("route",t),this.checkroute(t)||window.devMode||(t="/404");let n=(new DOMParser).parseFromString(await fetch(r,{cache:"reload"}).then((e=>e.text())),"text/html").documentElement;document.querySelector("#root").innerHTML=n.querySelector("#root").innerHTML,document.title=n.querySelector("title")?n.querySelector("title").innerHTML:document.title,document.querySelector('script[id="router"]').remove();let o=document.createElement("script");o.id="router",o.innerHTML=n.querySelector('script[id="router"]').innerHTML,o.setAttribute("type","module"),document.body.appendChild(o)},window.rehydrate=async()=>{window.location.reload()},this.listeners.push(e),1===this.listeners.length?this.handleRoute(window.location.pathname):this.listeners.pop(),t&&t()}extractParams(e,t){const r=e.split("/").filter((e=>""!==e)),n=t.split("/").filter((e=>""!==e)),o={};return r.forEach(((e,t)=>{if(e.startsWith(":")){const r=e.slice(1);o[r]=n[t]}else if(e.startsWith("*")){n.slice(t).forEach(((e,t)=>{o[t]=e}))}})),o}extractQueryParams(e){const t=e.split("?")[1];if(!t)return{};const r={};return t.split("&").forEach((e=>{const[t,n]=e.split("=");r[t]=n})),r}checkroute(e){return e=e.endsWith("/")?e.slice(0,-1):e,this.routes.find((t=>{if(t.path===e)return!0;if(""===e&&"/"===t.path)return!0;if(e.includes("?")&&(e=e.split("?")[0]),t.path.includes("*")||t.path.includes(":")){const r=t.path.split("/").filter((e=>""!==e)),n=e.split("/").filter((e=>""!==e));if(r.length!==n.length&&!t.path.endsWith("*"))return!1;for(let e=0;e<r.length;e++){const t=r[e],o=n[e];if(!t.startsWith(":")&&!t.startsWith("*")&&t!==o)return!1}return!0}const r=this.extractParams(t.path,e);return Object.keys(r).length>0}))}handleRoute(e){let t=200,r=e,n=this.checkroute(e);n||(n=window.routes.find((e=>!e.url.includes("/404")||this.error||window.devMode?!(this.error||!e.url.includes("/404"))||void 0:(window.history.pushState({},"","/404"),window.dispatchEvent(new Event("popstate")),this.error=!0,!1))),t=n?200:404);const o=this.extractQueryParams(r),s=n&&n.path?this.extractParams(n.path,r):{};Object.keys(s).forEach((e=>{s[e]=s[e].split("?")?s[e].split("?")[0]:s[e]}));const i={headers:{},params:s,query:o,path:e,fileUrl:window.location.href.split(window.location.origin)[1],url:window.location.href,method:n?n.method:"get",pause:!1,timestamp:Date.now()};window.$CURRENT_URL=i.path,window.$FULL_URL=window.location.href.replace("#","");const a={status:t,log:e=>{void 0===e?console.log(`${i.path} ${i.method} ${a.status} ${i.timestamp}`):console.table({"Request Path":i.path,"Request Method":n.method,"Response Status":a.status,"Request Timestamp":i.timestamp})},refresh:()=>{this.handleRoute(window.location.pathname)},redirect:e=>{!e.startsWith("/")&&(e=`/${e}`),window.history.pushState({},"",e),window.dispatchEvent(new Event("popstate"))},render:async(e,t,r,n)=>{function i(e){return"function"==typeof e&&/^class\s/.test(Function.prototype.toString.call(e))}try{let n=new Component;if(i(e.default)){let t=new e.default;n.state=t.state,n=t}else{if(e.default.toString().includes("this.key"))throw new Error('Using this.key is not supported in functional components use the attribute key="a value" instead');n.key=e.default.toString().split('key="')[1]?e.default.toString().split('key="')[1].split('"')[0]:null;let i={key:n.key,render:()=>e.default.apply(n,[t,r]),request:t,response:r,params:s,queryParams:o,reset:n.reset.bind(n),onMount:n.onMount.bind(n),useState:null,router:{use:n.router.use.bind(n)},bindMount:n.bindMount.bind(n),memoize:n.memoize.bind(n),createComponent:n.createComponent.bind(n),isChild:!1,useState:n.useState.bind(n),parseStyle:n.parseStyle.bind(n),bind:n.bind.bind(n),useRef:n.useRef.bind(n),useReducer:n.useReducer.bind(n),onMount:n.onMount.bind(n),onUnmount:n.onUnmount.bind(n),hydrate:n.hydrate.bind(n)};n.render=i.render,n=i}if(!document.querySelector("#root"))throw new Error("Root element not found, please add an element with id root");n.reset(),n.components={},n.request=t,n.response=r,n.router.use&&!n.isChild?await new Promise((async o=>{if(i(e.default))if(i(e.default))switch(await n.router.use(t,r),t.pause){case!0:console.log("pausing",t.pause);let e=setInterval((()=>{t.pause?console.log("still pausing",t.pause):(clearInterval(e),o())}),1e3);break;case!1:o()}else o();else switch(await e.default.apply(n,[t,r]),await n.router.use(t,r),t.pause){case!0:let e=setInterval((()=>{t.pause?console.log("still pausing request",t.url):(clearInterval(e),o())}),1e3);break;case!1:o()}})):n.router.use&&n.isChild&&console.warn("Router.use() is not supported in child components");const a=await n.render();document.querySelector("#root").innerHTML!==a&&(document.querySelector("#root").innerHTML=a),n.bindMount(),n.onMount()}catch(e){console.error(e)}},setQuery:e=>{let t="";Object.keys(e).forEach(((r,n)=>{t+=`${0===n?"?":"&"}${r}=${e[r]}`}));let r=window.location.hash.split("?")[0];t=t.replace("/","-").replaceAll("/","-"),window.location.hash=`${r}${t}`},send:e=>{document.querySelector("#root").innerHTML=e},json:e=>{const t=document.querySelector("#root");t.innerHTML="";const r=document.createElement("pre");r.textContent=JSON.stringify(e,null,2),t.appendChild(r)}};middlewares.forEach((e=>{e(i,a)})),n&&n.handler(i,a)}}window.VaderRouter=VaderRouter;export default VaderRouter;
1
+ import{Component}from"./vader.js";let middlewares=[];class VaderRouter{constructor(e,t){this.routes=[],this.middlewares=[],this.errorMiddlewares=[],this.listeners=[],this.basePath=e}get(e,t){this.routes.push({path:e,handler:t,method:"get"})}use(e){this.middlewares.push(e)}listen(e,t){e||(e=Math.random().toString(36).substring(7)),window.onpopstate=async e=>{let t=window.location.pathname,r=`/${t.split("/")[1]}`;console.log("route",t),this.checkroute(t)||window.devMode||(t="/404");let n=(new DOMParser).parseFromString(await fetch(r,{cache:"reload"}).then((e=>e.text())),"text/html").documentElement;document.querySelector("#root").innerHTML=n.querySelector("#root").innerHTML,document.title=n.querySelector("title")?n.querySelector("title").innerHTML:document.title,document.querySelector('script[id="router"]').remove();let o=document.createElement("script");o.id="router",o.innerHTML=n.querySelector('script[id="router"]').innerHTML,o.setAttribute("type","module"),document.body.appendChild(o)},window.rehydrate=async()=>{window.location.reload()},this.listeners.push(e),1===this.listeners.length?this.handleRoute(window.location.pathname):this.listeners.pop(),t&&t()}extractParams(e,t){const r=e.split("/").filter((e=>""!==e)),n=t.split("/").filter((e=>""!==e)),o={};return r.forEach(((e,t)=>{if(e.startsWith(":")){const r=e.slice(1);o[r]=n[t]}else if(e.startsWith("*")){n.slice(t).forEach(((e,t)=>{o[t]=e}))}})),o}extractQueryParams(e){const t=e.split("?")[1];if(!t)return{};const r={};return t.split("&").forEach((e=>{const[t,n]=e.split("=");r[t]=n})),r}checkroute(e){return e=e.endsWith("/")?e.slice(0,-1):e,this.routes.find((t=>{if(t.path===e)return!0;if(""===e&&"/"===t.path)return!0;if(e.includes("?")&&(e=e.split("?")[0]),t.path.includes("*")||t.path.includes(":")){const r=t.path.split("/").filter((e=>""!==e)),n=e.split("/").filter((e=>""!==e));if(r.length!==n.length&&!t.path.endsWith("*"))return!1;for(let e=0;e<r.length;e++){const t=r[e],o=n[e];if(!t.startsWith(":")&&!t.startsWith("*")&&t!==o)return!1}return!0}const r=this.extractParams(t.path,e);return Object.keys(r).length>0}))}handleRoute(e){let t=200,r=e,n=this.checkroute(e);n||(n=window.routes.find((e=>!e.url.includes("/404")||this.error||window.devMode?!(this.error||!e.url.includes("/404"))||void 0:(window.history.pushState({},"","/404"),window.dispatchEvent(new Event("popstate")),this.error=!0,!1))),t=n?200:404);const o=this.extractQueryParams(r),s=n&&n.path?this.extractParams(n.path,r):{};Object.keys(s).forEach((e=>{s[e]=s[e].split("?")?s[e].split("?")[0]:s[e]}));const i={headers:{},params:s,query:o,path:e,fileUrl:window.location.href.split(window.location.origin)[1],url:window.location.href,method:n?n.method:"get",pause:!1,timestamp:Date.now()};window.$CURRENT_URL=i.path,window.$FULL_URL=window.location.href.replace("#","");const a={status:t,log:e=>{void 0===e?console.log(`${i.path} ${i.method} ${a.status} ${i.timestamp}`):console.table({"Request Path":i.path,"Request Method":n.method,"Response Status":a.status,"Request Timestamp":i.timestamp})},refresh:()=>{this.handleRoute(window.location.pathname)},redirect:e=>{!e.startsWith("/")&&(e=`/${e}`),window.history.pushState({},"",e),window.dispatchEvent(new Event("popstate"))},render:async(e,t,r,n)=>{function isClass(e){return"function"==typeof e&&/^class\s/.test(Function.prototype.toString.call(e))}try{let n=new Component;if(isClass(e.default)){let t=new e.default;n.state=t.state,n=t}else{e.default.toString();n.key=e.default.toString().split('key="')[1]?e.default.toString().split('key="')[1].split('"')[0]:null;let i=e.default.toString().split("this.key")[1]?e.default.toString().split("this.key")[1].split("=")[1].split(";")[0].trim().replaceAll('"',""):null;i&&(n.key=i),console.log("key",n.key);let a={key:n.key,render:()=>{let o=document.createElement("div");return n.key&&o.setAttribute("key",n.key),o.innerHTML=e.default.apply(n,[t,r]),o.outerHTML},request:t,response:r,params:s,queryParams:o,reset:n.reset.bind(n),onMount:n.onMount.bind(n),useState:null,router:{use:n.router.use.bind(n)},bindMount:n.bindMount.bind(n),memoize:n.memoize.bind(n),createComponent:n.createComponent.bind(n),isChild:!1,useState:n.useState.bind(n),parseStyle:n.parseStyle.bind(n),bind:n.bind.bind(n),useRef:n.useRef.bind(n),useReducer:n.useReducer.bind(n),onMount:n.onMount.bind(n),onUnmount:n.onUnmount.bind(n),hydrate:n.hydrate.bind(n)};n.render=a.render,n=a}if(!document.querySelector("#root"))throw new Error("Root element not found, please add an element with id root");n.reset(),n.components={},n.request=t,n.response=r,n.router.use&&!n.isChild?await new Promise((async o=>{if(isClass(e.default))if(isClass(e.default))switch(await n.router.use(t,r),t.pause){case!0:console.log("pausing",t.pause);let e=setInterval((()=>{t.pause?console.log("still pausing",t.pause):(clearInterval(e),o())}),1e3);break;case!1:o()}else o();else switch(await e.default.apply(n,[t,r]),await n.router.use(t,r),t.pause){case!0:let e=setInterval((()=>{t.pause?console.log("still pausing request",t.url):(clearInterval(e),o())}),1e3);break;case!1:o()}})):n.router.use&&n.isChild&&console.warn("Router.use() is not supported in child components");const i=await n.render();document.querySelector("#root").innerHTML!==i&&(document.querySelector("#root").innerHTML=i),n.bindMount(),n.onMount()}catch(e){console.error(e)}},setQuery:e=>{let t="";Object.keys(e).forEach(((r,n)=>{t+=`${0===n?"?":"&"}${r}=${e[r]}`}));let r=window.location.hash.split("?")[0];t=t.replace("/","-").replaceAll("/","-"),window.location.hash=`${r}${t}`},send:e=>{document.querySelector("#root").innerHTML=e},json:e=>{const t=document.querySelector("#root");t.innerHTML="";const r=document.createElement("pre");r.textContent=JSON.stringify(e,null,2),t.appendChild(r)}};middlewares.forEach((e=>{e(i,a)})),n&&n.handler(i,a)}}window.VaderRouter=VaderRouter;export default VaderRouter;
package/runtime/vader.js CHANGED
@@ -1 +1 @@
1
- window.Vader={version:"1.3.3"},window.componentRegistry={};let errors={"SyntaxError: Unexpected token '<'":"You forgot to enclose tags in a fragment <></>"},mounts=[],hasRan=[];export const strictMount=(e,t)=>{let s=setInterval((()=>{document.querySelector(`[key="${e}"]`)&&!hasRan.includes(e)&&(clearInterval(s),t(),hasRan.push(e))}),120)};export class Component{constructor(){this.state={},this.key=null,this.components={},this.mounted=!1,this.checkIFMounted(),this.memoizes=[],this.functions=[],this.children=[],this.parentNode={},this.request={headers:{},method:"GET",params:{},path:"",query:{}},this.response={json:e=>{},send:e=>{},redirect:e=>{},render:async e=>{},log:e=>{},setQuery:e=>{}},this.router={use:e=>{}}}createComponent(e,t,s){function r(e){return"function"==typeof e&&/^class\s/.test(Function.prototype.toString.call(e))}let n=r(e)?new e(t):null;if(!e)throw new Error("Component must be defined");let i=new Component(t);if(r(e))n.props=t||{},n.props.children=s.join("")||[],n.props.children=s.join(""),n.parentNode=this,n.request=this.request,n.response=this.response,n.key=n.props.key?n.props.key:Math.random(),i=n;else{e.toString();i.key=e.toString().split('key="')[1]?e.toString().split('key="')[1].split('"')[0]:null;let r=[];Object.keys(t).forEach((e=>{e.startsWith("$props")&&(r.push(t[e]),delete t[e])})),t=t?{...t,...r.reduce(((e,t)=>({...e,...t})),{}),children:s.join("")||[]}:{children:s.join("")||[]};let n={key:i.key?i.key:Math.random(),isUnique:!!i.key,render:()=>e.apply(i,[t]),request:this.request,isChild:!0,response:this.response,params:this.request.params,queryParams:this.request.query,reset:i.reset.bind(i),onMount:i.onMount.bind(i),useState:null,router:{use:i.router.use.bind(i)},bindMount:i.bindMount.bind(i),memoize:i.memoize.bind(i),createComponent:i.createComponent.bind(i),isChild:!1,useState:i.useState.bind(i),parseStyle:i.parseStyle.bind(i),bind:i.bind.bind(i),useRef:i.useRef.bind(i),request:this.request,response:this.response,useReducer:i.useReducer.bind(i),hydrate:i.hydrate.bind(i),onUnmount:i.onUnmount.bind(i),parentNoe:this,props:{...t,children:s.join("")||[]}};i.render=n.render,i=n,i.props.children=s.join("")||[]}return this.components[i.key]||(this.components[i.key]=i),!this.children.includes(i)&&this.children.push(i),this.components[i.key]}reset(){Object.keys(this.components).forEach((e=>{this.components[e].onUnmount(),delete this.components[e]})),this.state={},this.children=[]}memoize(e){if(!0==!this.memoizes.includes(e.key))this.memoizes.push(e.key),this.components[e.key]=e;let t=this.components[e.key];t.bindMount(),t.parentNode=this,t.props=e.props,t.request=this.request,t.response=this.response,t.onMount=e.onMount.bind(e),t.onUnmount=e.onUnmount.bind(e);let s=t.render();return s&&s.split(">,").length>1&&(s=s.replaceAll(">,",">")),`<span key="${e.key}" >${s}</span>`}parseStyle(e){let t="";return Object.keys(e).forEach((s=>{let r=e[s];s=s.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,"$1-$2").toLowerCase(),t+=`${s}:${r};`})),t}bindMount(){mounts.push(this)}domDifference(e,t){let s=[];for(let r=0;r<e.length;r++){let n=e[r],i=t[r];n&&i&&!n.isEqualNode(i)&&s.push({type:"replace",old:n,new:i.cloneNode(!0)})}return s}updateChangedElements(e){e.forEach((e=>{switch(e.type){case"replace":e.old.parentNode.replaceChild(e.new,e.old);break;case"remove":e.old.remove();break;case"add":e.old.appendChild(e.new.cloneNode(!0))}}))}hydrate(e){if(e){console.log("hydrating");let t=(new DOMParser).parseFromString(this.render(),"text/html").body.querySelector(`[ref="${e}"]`);document.querySelector(`[ref="${e}"]`);document.querySelector(`[ref="${e}"]`).replaceWith(t)}else{let e=this.key?document.querySelector(`[key="${this.key}"]`):null;e&&(e.innerHTML=this.render())}}patch(e,t){const s=this.domDifference(e,t);this.updateChangedElements(s)}handleObject(obj){try{obj=JSON.parse(obj)}catch(e){}return eval(obj)}bind(e,t,s,r,...n){return s+=this.key,window["callFunctions"+this.key]=(e,t)=>{console.log("called");let s=this.functions.find((t=>t.ref===e));s&&s.func(t)},this.functions.find((e=>e.ref===s))||this.functions.push({ref:s,func:e}),t?e:`((event)=>{callFunctions${this.key} ? callFunctions${this.key}('${s}', event) : null})(event)`}useState(e,t){this.state.hasOwnProperty(e)||(this.state[e]=t);return[(()=>this.state[e])(),(t,s)=>{this.state[e]=t,this.hydrate(s)}]}useRef(e=null,t){this.state[e]||(this.state[e]=t);return{bind:e+this.key,current:(()=>document.querySelector(`[ref="${e+this.key}"]`)||t)()}}useReducer(e=null,t,s=null){this.state[e]||(this.state[e]=t);const r=()=>this.state[e];let n=r();return[r(),(t,i)=>{const o=s(n,t)??t;this.state[e]=o,this.hydrate(i),n=r()}]}render(){}checkIFMounted(){new MutationObserver((e=>{e.forEach((e=>{e.target.querySelector(`[key="${this.key}"]`)&&!this.mounted&&(this.onMount(),this.mounted=!0),Array.from(e.removedNodes).find((e=>e.attributes&&e.attributes.key&&e.attributes.key.value===this.key))&&(this.onUnmount(),this.reset())}))})).observe(document.body,{childList:!0,subtree:!0})}onMount(){}onUnmount(){}}export const useState=(e,t)=>{this.state[e]||(this.state[e]=t);return[states[e],(t,s)=>{states[e]=t,this.hydrate(s)}]};export const useReducer=(e,t)=>[e,e=>{}];export const useRef=e=>({current:e,bind:""});export class Link extends Component{constructor(e){super(e),this.props=e,this.link=document.createElement("a"),this.key=e.href+Math.random()}render(){return this.link.innerHTML=this.props.children,this.link.setAttribute("id",this.props?.href),this.link.style=this.props?.style,this.link.setAttribute("class",this.props?.class),this.link.setAttribute("onclick",`window.history.pushState({}, '', '${this.props?.href}'); window.dispatchEvent(new Event('popstate'));`),this.link.outerHTML}}export class Image extends Component{constructor(e){super(e),this.props={src:e.src,class:e.class,style:e.style,blur:e.blur,width:e.width,height:e.height,optimize:e.optimize||!0,loader:e.loader||!0,alt:e.alt||"image",ref:e.ref||null},this.key=e.src+Math.random(),this.img=document.createElement("img"),this.placeholder=document.createElement("div")}render(){if(window.isServer)return"";let[e,t]=this.useState("loaded",!1),s=this.useRef("hookref",null),r=this.props.width?this.props.width:window.innerWidth/2,n=this.props.height?this.props.height:window.innerHeight/2;if(!this.props.src)throw new Error("Image src is required");return this.img.setAttribute("src",this.props.src),this.img.setAttribute("class",this.props.class),this.img.setAttribute("style",this.props.style?this.props.style:""),this.img.setAttribute("width",r),this.img.setAttribute("ref",s.bind),this.img.referrerPolicy="no-referrer",this.img.setAttribute("height",n),this.img.setAttribute("loading","lazy"),this.img.setAttribute("alt",this.props.alt),this.props.blur&&this.img.setAttribute("style",`filter: blur(${this.props.blur}px);`),this.props.optimize&&this.img.setAttribute("style",`image-rendering: -webkit-optimize-contrast; object-fit: cover; object-position: center; ${this.props.style?this.props.style:""}`),!this.props.loader||e||window.isServer||(this.placeholder.setAttribute("style",`width: ${r}px; height: ${n}px; background: #eee;`),this.placeholder.setAttribute("class","vader-image-placeholder"),this.placeholder.innerHTML=this.props.loader,window.isServer)?void 0:(this.img.onload=()=>{t(!0,s.bind)},`<span ref="${s.bind}">${e?this.img.outerHTML:this.placeholder.outerHTML}</span>`)}}export class Html extends Component{constructor(e){super(e),this.props={children:e.children,lang:e.lang||"en",attributes:e.attributes||{}},this.key="html",this.html=document.createElement("div")}render(){return window.isServer?(this.html.innerHTML=this.props.children,this.html.setAttribute("lang",this.props.lang?this.props.lang:"en"),this.props.attributes&&Object.keys(this.props.attributes).forEach((e=>{this.html.setAttribute(e,this.props.attributes[e])})),this.html.innerHTML):this.props.children}onMount(){console.log("Document Has Been Mounted")}}export class Head extends Component{constructor(e){super(e),this.props=e,this.key="head",this.head=document.createElement("head")}render(){return""}onMount(){if(!this.state.hasMounted&&window.isServer){document.head.innerHTML=this.props.children+document.head.innerHTML,document.querySelectorAll("script[eager]").forEach((async e=>{if(!e.getAttribute("src"))throw new Error("Eager scripts must be external");e.remove();let t=e.getAttribute("src"),s=document.createElement("script"),r=await fetch(t).then((e=>e.text()));s.innerHTML=r,s.setAttribute("srcid",t),document.querySelector(`[srcid="${t}"]`)||document.head.prepend(s)})),this.state.hasMounted=!0}}}export class Script extends Component{constructor(e){super(e),this.props={children:e.children},this.key="script",this.script=document.createElement("script")}render(){return this.script.innerHTML=this.props.children.split("\n").join(";\n"),this.script.outerHTML}onMount(){document.head.appendChild(this.script),document.body.querySelector(`[key="${this.key}"]`).remove()}}export default{Component:Component,useRef:useRef,useReducer:useReducer,useState:useState,strictMount:strictMount,Link:Link,Image:Image,Head:Head,Script:Script,Html:Html};
1
+ window.Vader={version:"1.3.3"},window.componentRegistry={};let errors={"SyntaxError: Unexpected token '<'":"You forgot to enclose tags in a fragment <></>"},mounts=[],hasRan=[];export const strictMount=(e,t)=>{let s=setInterval((()=>{document.querySelector(`[key="${e}"]`)&&!hasRan.includes(e)&&(clearInterval(s),t(),hasRan.push(e))}),120)};export class Component{constructor(){this.$_vaderElement=!0,this.state={},this.key=null,this.components={},this.mounted=!1,this.checkIFMounted(),this.memoizes=[],this.functions=[],this.children=[],this.noKey=!1,this.parentNode={},this.request={headers:{},method:"GET",params:{},path:"",query:{}},this.response={json:e=>{},send:e=>{},redirect:e=>{},render:async e=>{},log:e=>{},setQuery:e=>{}},this.router={use:e=>{}}}createComponent(e,t,s){function r(e){return"function"==typeof e&&/^class\s/.test(Function.prototype.toString.call(e))}let i=r(e)?new e(t):null;if(!e)throw new Error("Component must be defined");let n=[];Object.keys(t).forEach((e=>{if(e.startsWith("$props")&&(n.push(t[e]),delete t[e]),e.startsWith("$_ternary")){let s=t[e];delete t[e],Object.keys(s).forEach((e=>{t[e]=s[e]}))}}));let o=new Component(t);if(r(e))i.props=t||{},i.props.children=s.join("")||[],i.props.children=s.join(""),i.parentNode=this,i.type="class ",i.request=this.request,i.response=this.response,i.key=i.props.key?i.props.key:Math.random(),o=i;else{e.toString();o.key=e.toString().split('key="')[1]?e.toString().split('key="')[1].split('"')[0]:null,t=t?{...t,...n.reduce(((e,t)=>({...e,...t})),{}),children:s.join("")||[]}:{children:s.join("")||[]};let r={key:o.key?o.key:Math.random(),isUnique:!!o.key,render:()=>e.apply(o,[t]),request:this.request,isChild:!0,response:this.response,params:this.request.params,queryParams:this.request.query,reset:o.reset.bind(o),onMount:o.onMount.bind(o),useState:null,router:{use:o.router.use.bind(o)},bindMount:o.bindMount.bind(o),memoize:o.memoize.bind(o),createComponent:o.createComponent.bind(o),isChild:!1,useState:o.useState.bind(o),parseStyle:o.parseStyle.bind(o),bind:o.bind.bind(o),useRef:o.useRef.bind(o),request:this.request,response:this.response,useReducer:o.useReducer.bind(o),hydrate:o.hydrate.bind(o),onUnmount:o.onUnmount.bind(o),type:"function",parentNoe:this,props:{...t,children:s.join("")||[]}};o.render=r.render,o=r,o.props.children=s.join("")||[]}return this.components[o.key]||(this.components[o.key]=o),!this.children.includes(o)&&this.children.push(o),this.components[o.key]}reset(){Object.keys(this.components).forEach((e=>{this.components[e].onUnmount(),delete this.components[e]})),this.state={},this.children=[]}memoize(e){if(!0==!this.memoizes.includes(e.key))this.memoizes.push(e.key),this.components[e.key]=e;let t=this.components[e.key];"class"!==t.type||t.$_vaderElement||console.error(`class ${t.constructor.name} must extend Component`),t.bindMount(),t.parentNode=this,t.props=e.props,t.request=this.request,t.response=this.response,t.onMount=e.onMount.bind(e),t.onUnmount=e.onUnmount.bind(e);let s=t.render();return s&&s.split(">,").length>1&&(s=s.replaceAll(">,",">")),t.nokey||t.noKey?s:`<div key="${t.key}">${s}</div>`}parseStyle(e){let t="";return Object.keys(e).forEach((s=>{let r=e[s];s=s.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,"$1-$2").toLowerCase(),t+=`${s}:${r};`})),t}bindMount(){mounts.push(this)}domDifference(e,t){let s=[];for(let r=0;r<e.length;r++){let i=e[r],n=t[r];if(i&&n&&i.childNodes.length>0&&n.childNodes.length>0){console.log("equal");let e=this.domDifference(Array.from(i.childNodes),Array.from(n.childNodes));s.push(...e)}i&&n&&0===i.childNodes.length&&0===n.childNodes.length?i.isEqualNode(n)||s.push({type:"replace",old:i,new:n}):i&&n&&Array.from(i.attributes).length!==Array.from(n.attributes).length&&s.push({type:"replace",old:i,new:n})}return s}updateChangedElements(e){e.forEach((e=>{if(e)switch(e.type){case"replace":e.old.replaceWith(e.new);break;case"remove":e.old.remove();break;case"add":e.old.appendChild(e.new.cloneNode(!0))}}))}hydrate(e){if(e){console.log(e.bind);let t=(new DOMParser).parseFromString(this.render(),"text/html").body.querySelectorAll(`[ref="${e.bind?e.bind:e}"]`),s=document.querySelectorAll(`[ref="${e?e.bind:e}"]`);t.forEach((e=>{let t=Array.from(s).find((t=>t.getAttribute("ref")===e.getAttribute("ref")));if(t){let s=this.domDifference(Array.from(t.childNodes),Array.from(e.childNodes))[0];this.updateChangedElements([s])}}))}else{let e=this.key?document.querySelector(`[key="${this.key}"]`):null;if(console.log(e),e){let t=(new DOMParser).parseFromString(this.render(),"text/html").body.querySelector(`[key="${this.key}"]`),s=e,r=this.domDifference([s],[t]);this.updateChangedElements(r)}}}patch(e,t){const s=this.domDifference(e,t);this.updateChangedElements(s)}handleObject(obj){try{obj=JSON.parse(obj)}catch(e){}return eval(obj)}bind(e,t,s,r,...i){return s+=this.key,window["callFunctions"+this.key]=(e,t)=>{let s=this.functions.find((t=>t.ref===e));s&&s.func(t)},this.functions.find((e=>e.ref===s))||this.functions.push({ref:s,func:e}),t?e:`((event)=>{callFunctions${this.key} ? callFunctions${this.key}('${s}', event) : null})(event)`}setState(e,t){this.state=e,this.hydrate(t)}useState(e,t){this.state.hasOwnProperty(e)||(this.state[e]=t);return[(()=>this.state[e])(),(t,s)=>{this.state[e]=t,this.hydrate(s)}]}useRef(e=null,t){this.state[e]||(this.state[e]=t);return{bind:e+this.key,current:(()=>document.querySelector(`[ref="${e+this.key}"]`)||t)()}}useReducer(e=null,t,s=null){this.state[e]||(this.state[e]=t);const r=()=>this.state[e];let i=r();return[r(),(t,n)=>{const o=s(i,t)??t;this.state[e]=o,this.hydrate(n),i=r()}]}render(){}checkIFMounted(){new MutationObserver((e=>{e.forEach((e=>{e.target.querySelector(`[key="${this.key}"]`)&&!this.mounted&&(this.onMount(),this.mounted=!0),Array.from(e.removedNodes).find((e=>e.attributes&&e.attributes.key&&e.attributes.key.value===this.key))&&(this.onUnmount(),this.reset())}))})).observe(document.body,{childList:!0,subtree:!0})}onMount(){}onUnmount(){}}export const useState=(e,t)=>{this.state[e]||(this.state[e]=t);return[states[e],(t,s)=>{states[e]=t,this.hydrate(s)}]};export const useReducer=(e,t)=>[e,e=>{}];export const useRef=e=>({current:e,bind:""});export class Link extends Component{constructor(e){super(e),this.props=e,this.link=document.createElement("a"),this.key=e.href+Math.random(),this.nokey=!0}render(){return this.link.innerHTML=this.props.children,this.link.setAttribute("id",this.props?.href),this.link.style=this.props?.style,this.link.setAttribute("class",this.props?.class||this.props?.className),this.link.setAttribute("onclick",`window.history.pushState({}, '', '${this.props?.href}'); window.dispatchEvent(new Event('popstate'));`),this.link.outerHTML}}export class Image extends Component{constructor(e){super(e),this.props={src:e.src,class:e.class,style:e.style,blur:e.blur,width:e.width,height:e.height,optimize:e.optimize||!0,loader:e.loader||!0,alt:e.alt||"image",ref:e.ref||null},this.key=e.src+Math.random(),this.img=document.createElement("img"),this.placeholder=document.createElement("div")}render(){if(window.isServer)return"";let[e,t]=this.useState("loaded",!1),s=this.useRef("hookref",null),r=this.props.width?this.props.width:window.innerWidth/2,i=this.props.height?this.props.height:window.innerHeight/2;if(!this.props.src)throw new Error("Image src is required");return this.img.setAttribute("src",this.props.src),this.img.setAttribute("class",this.props.class),this.img.setAttribute("style",this.props.style?this.props.style:""),this.img.setAttribute("width",r),this.img.setAttribute("ref",s.bind),this.img.referrerPolicy="no-referrer",this.img.setAttribute("height",i),this.img.setAttribute("loading","lazy"),this.img.setAttribute("alt",this.props.alt),this.props.blur&&this.img.setAttribute("style",`filter: blur(${this.props.blur}px);`),this.props.optimize&&this.img.setAttribute("style",`image-rendering: -webkit-optimize-contrast; object-fit: cover; object-position: center; ${this.props.style?this.props.style:""}`),!this.props.loader||e||window.isServer||(this.placeholder.setAttribute("style",`width: ${r}px; height: ${i}px; background: #eee;`),this.placeholder.setAttribute("class","vader-image-placeholder"),this.placeholder.innerHTML=this.props.loader,window.isServer)?void 0:(this.img.onload=()=>{t(!0,s.bind)},`<span ref="${s.bind}">${e?this.img.outerHTML:this.placeholder.outerHTML}</span>`)}}export class Html extends Component{constructor(e){super(e),this.props={children:e.children,lang:e.lang||"en",attributes:e.attributes||{}},this.key="html",this.html=document.createElement("div")}render(){return window.isServer?(this.html.innerHTML=this.props.children,this.html.setAttribute("lang",this.props.lang?this.props.lang:"en"),this.props.attributes&&Object.keys(this.props.attributes).forEach((e=>{this.html.setAttribute(e,this.props.attributes[e])})),this.html.innerHTML):this.props.children}onMount(){console.log("Document Has Been Mounted")}}export class Head extends Component{constructor(e){super(e),this.props=e,this.key="head",this.head=document.createElement("head")}render(){return""}onMount(){if(!this.state.hasMounted&&window.isServer){console.log(this.props.children),document.head.innerHTML=this.props.children+document.head.innerHTML,document.querySelectorAll("script[eager]").forEach((async e=>{if(!e.getAttribute("src"))throw new Error("Eager scripts must be external");e.remove();let t=e.getAttribute("src"),s=document.createElement("script"),r=null;await fetch(t).then((e=>{if(404===e.status)throw document.documentElement.setAttribute("error",JSON.stringify({error:`File ${t} not found`,status:404})),new Error(`File ${t} not found`);return e.text()})).then((e=>{r=e})).catch((e=>{throw e})),s.innerHTML=r,s.setAttribute("srcid",t),document.querySelector(`[srcid="${t}"]`)||document.head.prepend(s)})),this.state.hasMounted=!0}}}export class Script extends Component{constructor(e){super(e),this.props={children:e.children},this.key="script",this.script=document.createElement("script")}render(){return this.script.innerHTML=this.props.children.split("\n").join(";\n"),this.script.outerHTML}onMount(){document.head.appendChild(this.script),document.body.querySelector(`[key="${this.key}"]`).remove()}}export default{Component:Component,useRef:useRef,useReducer:useReducer,useState:useState,strictMount:strictMount,Link:Link,Image:Image,Head:Head,Script:Script,Html:Html};
package/vader.js CHANGED
@@ -1,11 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "fs";
3
3
  import { glob, globSync, globStream, globStreamSync, Glob, } from 'glob'
4
- import puppeteer from 'puppeteer';
4
+ import puppeteer from 'puppeteer';
5
5
  import http from 'http'
6
+ import { SourceMapGenerator } from 'source-map'
7
+
6
8
  import { WebSocketServer } from 'ws'
9
+ import prettier from 'prettier'
7
10
  import { watch } from "fs";
8
11
  import path from 'path'
12
+
13
+ globalThis.compiledFiles = []
14
+
15
+ const sourceMapGen = (data, code) => {
16
+ let { origin, fileName } = data
17
+ const sourceMap = new SourceMapGenerator({ file: '/src/' + fileName.replace('.jsx', '.js') });
18
+
19
+ const lines = fs.readFileSync(origin, "utf8").split("\n");
20
+ let line = 1;
21
+ let column = 0;
22
+ for (const l of lines) {
23
+ sourceMap.addMapping({
24
+ source: origin,
25
+ sourceRoot: '/src',
26
+ original: { line: line, column: 0 },
27
+ generated: { line: line, column: 0 },
28
+ });
29
+ line++;
30
+ }
31
+
32
+ sourceMap.setSourceContent(origin, fs.readFileSync(origin, "utf8"));
33
+
34
+ code = code + `\n//# sourceMappingURL=./src/maps/${fileName.replace('.jsx', '.js')}.map \n //#sourceURL=/src/maps/${fileName.replace('.jsx', '.js')}.map`
35
+ return { code, sourceMap };
36
+ }
37
+
9
38
  let config = await import('file://' + process.cwd() + '/vader.config.js').then((e) => e.default || e)
10
39
  let writer = async (file, data) => {
11
40
  globalThis.isWriting = file
@@ -23,15 +52,7 @@ let writer = async (file, data) => {
23
52
  return { _written: true };
24
53
  };
25
54
 
26
- let start = Date.now()
27
55
  let bundleSize = 0;
28
- let errorCodes = {
29
- "SyntaxError: Unexpected token '<'": "You forgot to enclose tags in a fragment <></>",
30
- }
31
- /**
32
- * define directories
33
- */
34
-
35
56
 
36
57
  if (!fs.existsSync(process.cwd() + '/dist')) {
37
58
  fs.mkdirSync(process.cwd() + '/dist')
@@ -41,112 +62,74 @@ if (!fs.existsSync(process.cwd() + '/dist')) {
41
62
 
42
63
 
43
64
  if (typeof process.env.isCloudflare !== "undefined" || !fs.existsSync(process.cwd() + '/dist/index.html')) {
44
- let htmlFile = fs.readFileSync(process.cwd() + "/node_modules/vaderjs/runtime/index.html", 'utf8')
45
- fs.writeFileSync(process.cwd() + "/dist/index.html", htmlFile)
65
+ fs.writeFileSync(process.cwd() + "/dist/index.html", '')
46
66
  }
47
67
 
48
68
 
49
69
 
50
70
  function Compiler(func, file) {
51
71
  let string = func;
52
- // Remove block comments
53
-
54
72
  let returns = []
55
73
  let comments = string.match(/\{\s*\/\*.*\*\/\s*}/gs)?.map((comment) => comment.trim());
56
74
 
57
- let savedfuncnames = [];
58
- let functions = string.match(
59
- /(?:const|let)\s*([a-zA-Z0-9_-]+)\s*=\s*function\s*\(([^)]*)\)|function\s*([a-zA-Z0-9_-]+)\s*\(([^)]*)\)/gs
60
- )
61
- ?.map((match) => match.trim());
62
75
 
63
- let functionNames = [];
64
76
 
65
77
 
66
- functions && functions.forEach((func) => {
67
- if (
68
- !func.match(
69
- /(?:const|let)\s*([a-zA-Z0-9_-]+)\s*=\s*function\s*\(([^)]*)\)|function\s*([a-zA-Z0-9_-]+)\s*\(([^)]*)\)/gs
70
- )
71
- ) {
72
- return;
73
- }
74
-
75
- let name = func.split(" ")[1].split("(")[0].trim();
76
-
77
- let lines = string.match(/return\s*\<>.*\<\/>/gs);
78
-
79
- if (lines) {
80
- for (let i = 0; i < lines.length; i++) {
81
- let line = lines[i];
82
-
83
- if (!functionNames.includes(name)) {
84
- functionNames.push(name);
85
- }
86
- }
87
- }
88
- });
89
78
 
90
- // get all Obj({}) and parse to JSON.stringify
91
79
 
92
- let objects = string.match(/Obj\({.*}\)/gs);
93
-
94
- objects && objects.forEach((obj) => {
95
- let key = obj.split("Obj")[1].split("(")[1].split(")")[0].trim();
96
- let newobj = obj.replaceAll(`Obj(${key})`, `${key}`);
97
- // let newobj = obj.replaceAll(`Obj(${key})`, `JSON.parse('${key}')`)
98
- string = string.replaceAll(obj, `this.handleObject('${newobj}')`);
99
- });
80
+ let childs = [];
100
81
 
101
82
 
102
- let childs = [];
83
+ const spreadAttributeRegex = /\s*([a-zA-Z0-9_-]+)\s*(\$\s*=\s*\{\s*\{[^]*?\}\s*\})/gs;
84
+
103
85
 
104
86
 
105
87
 
106
88
  function extractAttributes(code) {
107
- // Match elements with opening tags
108
- const elementRegex = /<([a-zA-Z0-9_-]+)([^>]*)>/gs;
89
+ // grab $={...} and ={...}
90
+ const elementRegex = /<([a-zA-Z0-9_-]+)([^>]*)>/gs;
109
91
 
110
92
  // Match attributes in an opening tag, including those with ={}
111
93
  // Match attributes in an opening tag, including those with ={...}
112
94
  const attributeRegex =
113
- /\s*([a-zA-Z0-9_-]+)(\s*=\s*("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|\{(?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*\}|(?:\([^)]*\)|\{[^}]*\}|()=>\s*(?:\{[^}]*\})?)|\[[^\]]*\]))?/gs;
95
+ /\s*([a-zA-Z0-9_-]+)(\s*=\s*("(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)'|\{(?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*\}|(?:\([^)]*\)|\{[^}]*\}|()=>\s*(?:\{[^}]*\})?)|\[[^\]]*\]))?/gs;
96
+
97
+
114
98
 
115
- // only return elements with attribute {()=>{}} or if it also has parameters ex: onclick={(event)=>{console.log(event)}} also get muti line functions
116
- const functionAttributeRegex = /\s*([a-zA-Z0-9_-]+)(\s*=\s*{((?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*)})/gs;
99
+ const functionAttributeRegex = /\s*([a-zA-Z0-9_-]+)\s*(=\s*{((?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*)})/gs;
117
100
 
118
101
  let attributesList = [];
119
102
 
120
- // handle functions
103
+ let spreadAttributes = [];
104
+
105
+ /**
106
+ * @search - handle spread for html elements
107
+ * @keywords - spread, spread attributes, spread props, spread html attributes
108
+ */
109
+
110
+
111
+ /**
112
+ * @search - handle function parsing for html elements
113
+ * @keywords - function, function attributes, function props, function html attributes
114
+ *
115
+ */
121
116
  let functionAttributes = [];
117
+ let spreadFunctions = [];
122
118
  let functionMatch;
123
119
  while ((functionMatch = functionAttributeRegex.exec(code)) !== null) {
124
- let [, attributeName, attributeValue] = functionMatch;
125
120
 
121
+ let [, attributeName, attributeValue] = functionMatch;
126
122
  let attribute = {};
127
123
 
128
- if (attributeValue && attributeValue.includes("=>") || attributeValue && attributeValue.includes("function")) {
129
- let functionparams = [];
130
- // ref with no numbers
124
+ if (attributeValue && attributeValue.includes("=>")
125
+ && !attributeValue.includes("this.bind")
126
+ || attributeValue && attributeValue.includes("function")
127
+ && !spreadFunctions.includes(attributeValue)
128
+ ) {
129
+
131
130
  let ref = Math.random().toString(36).substring(2).split('').filter((e) => !Number(e)).join('')
132
131
  let old = `${attributeName}${attributeValue}`
133
- functionNames.forEach((name) => {
134
- string.split("\n").forEach((line) => {
135
- if (line.includes(name) && line.includes("function")) {
136
- line = line.trim();
137
- line = line.replace(/\s+/g, " ");
138
132
 
139
- let ps = line.split("(").slice(1).join("(").split(")")[0].trim();
140
-
141
- // remove comments
142
- ps = ps.match(/\/\*.*\*\//gs)
143
- ? ps.replace(ps.match(/\/\*.*\*\//gs)[0], "")
144
- : ps;
145
- functionparams.push({ ref: ref, name: name, params: ps });
146
-
147
- }
148
- });
149
- });
150
133
  let elementMatch = string.match(/<([a-zA-Z0-9_-]+)([^>]*)>/gs);
151
134
  let isJSXComponent = false;
152
135
  elementMatch.forEach((element) => {
@@ -164,6 +147,7 @@ function Compiler(func, file) {
164
147
 
165
148
  let newvalue = attributeValue.includes('=>') ? attributeValue.split("=>").slice(1).join("=>").trim() : attributeValue.split("function").slice(1).join("function").trim()
166
149
 
150
+ // add ; after newlines
167
151
 
168
152
 
169
153
  newvalue = newvalue.trim();
@@ -201,7 +185,6 @@ function Compiler(func, file) {
201
185
 
202
186
 
203
187
 
204
- functionparams.length > 0 ? params = params + ',' + functionparams.map((e) => e.name).join(',') : null
205
188
 
206
189
  newvalue = newvalue.replaceAll(',,', ',')
207
190
  let paramnames = params ? params.split(',').map((e) => e.trim()) : null
@@ -211,6 +194,8 @@ function Compiler(func, file) {
211
194
 
212
195
  // add ; after newlines
213
196
  newvalue = newvalue.replaceAll(/\n/g, ";\n")
197
+ // remove () from newvalue
198
+ newvalue = newvalue.replace(/\(\s*=>/gs, '=>').replace(/\function\s*\([^\)]*\)\s*\{/gs, '{')
214
199
 
215
200
  let bind = isJSXComponent ? `${attributeName}='function(${params}){${newvalue}}'` : `${attributeName}="\$\{this.bind(function(){${newvalue}}.bind(this), ${isJSXComponent}, "${ref}", "${paramnames ? paramnames.map((e, index) => {
216
201
  if (e.length < 1) return ''
@@ -224,10 +209,15 @@ function Compiler(func, file) {
224
209
  }
225
210
  }
226
211
 
212
+ /**
213
+ * @search - handle attributes for html elements
214
+ * @keywords - attributes, props, html attributes
215
+ */
227
216
  let match;
228
217
  while ((match = elementRegex.exec(code)) !== null) {
229
218
  let [, element, attributes] = match;
230
219
 
220
+
231
221
  let attributesMatch;
232
222
  let elementAttributes = {};
233
223
 
@@ -239,6 +229,81 @@ function Compiler(func, file) {
239
229
 
240
230
  attributesList.push({ element, attributes: elementAttributes });
241
231
  }
232
+
233
+ let spreadMatch;
234
+ while ((spreadMatch = spreadAttributeRegex.exec(string)) !== null) {
235
+ let [, element, spread] = spreadMatch;
236
+
237
+ let isJSXComponent = element.match(/[A-Z]/) ? true : false;
238
+ if (isJSXComponent) {
239
+ continue
240
+
241
+ }
242
+ let old = spread;
243
+ // turn spread into attributes
244
+ spread = spread.replace(/\s*$\s*=\s*/, "");
245
+ spread = spread.replace(/\$\s*=\s*/, "");
246
+ spread = spread.replace('{{', '')
247
+ spread = spread.replace(/,$/, '');
248
+
249
+
250
+
251
+ let splitByCommas = spread.split(/,(?![^{}]*})/gs).map((e) => {
252
+
253
+
254
+
255
+ switch (true) {
256
+ case e.includes('function') || e.includes('=>'):
257
+ e = e.replace(/\,\s*$/, '');
258
+ // replace commas
259
+ e = e.replace(/,\s*/gs, ',');
260
+ // remove space between (.*) =>
261
+ e = e.replace(/\(\s*=>/gs, '=>');
262
+ e = e.replace(/\s*=>\s*\{/gs, '=>{');
263
+
264
+ // add ; after newlines
265
+ e = e.replace(/\s*(\w+)\s*=>\s*\{/gs, '$1=>{');
266
+ // turn (e) => {e} into function(e){e}
267
+ e = e.replace(/\(([^)]*)\)\s*=>\s*\{/gs, 'function($1){\n');
268
+ e = e.replace(/;/g, ';\n ');
269
+ e = e.replace(/,\s*}\s*$/gs, '}');
270
+
271
+
272
+
273
+ e = e.replace(/:(.*)/gs, '="\${this.bind($1, ' + false + ', "' + Math.random().toString(36).substring(2).split('').filter((e) => !Number(e)).join('') + '", "")}"');
274
+
275
+ break;
276
+ case e.includes('style'):
277
+ e = e.replace(/,\s*$/gs, '');
278
+ e = e.replace(/}\s*$/gs, '');
279
+ e = e.replaceAll(/{\s*$/gs, '');
280
+ e = e.replaceAll(/}\s*$/gs, '');
281
+ e = e.replace(/style:(.*)/gs, 'style="\${this.parseStyle($1)}"');
282
+
283
+
284
+ break;
285
+ case e.includes('[') && e.includes(']'):
286
+ e = e.replace(/:(.*)/gs, '={$1.join(" ")}');
287
+ break;
288
+ default:
289
+ e = e.replace(/:(.*)/gs, '="${$1}"');
290
+ e = e.replace(/,\s*$/gs, '');
291
+ e = e.replace(/}\s*$/gs, '');
292
+ e = e.replaceAll(/{\s*$/gs, '');
293
+
294
+ break;
295
+ }
296
+
297
+ return e.trim();
298
+ });
299
+
300
+ let newSpread = splitByCommas.join(' ').trim().replace(/,$/, '');
301
+
302
+ // remove trailing }
303
+ newSpread = newSpread.replace(/}\s*$/, '')
304
+ newSpread = newSpread.trim().replace(/{\s*$/gs, '')
305
+ string = string.replace(old, newSpread);
306
+ }
242
307
 
243
308
  return attributesList;
244
309
  }
@@ -249,7 +314,6 @@ function Compiler(func, file) {
249
314
 
250
315
  return returns || [];
251
316
  }
252
- // throw error if return is not wrapped in <></> or
253
317
  if (string.match(/return\s*\<>|return\s*\(.*\)/gs) && !string.match(/return\s*\<>.*\<\/>|return\s*\(.*\)/gs)
254
318
  || string.match(/return\s*\<[a-zA-Z0-9_-]+.*>/gs)
255
319
  ) {
@@ -265,6 +329,10 @@ function Compiler(func, file) {
265
329
  let outerReturn = extractOuterReturn(string);
266
330
  let contents = "";
267
331
  let updatedContents = "";
332
+ /**
333
+ * @search - handle return [...]
334
+ * @keywords - return, return jsx, return html, return [...]
335
+ */
268
336
  outerReturn.forEach((returnStatement) => {
269
337
 
270
338
  let lines = returnStatement.split("\n");
@@ -279,34 +347,40 @@ function Compiler(func, file) {
279
347
  let usesBraces = returnStatement.match(/return\s*\(/gs) ? true : false;
280
348
 
281
349
  let attributes = extractAttributes(contents);
282
- // Remove trailing ']' or trailing )
283
350
  contents = contents.trim().replace(/\]$/, "")
284
351
  contents = contents.replace(/\)$/, "");
285
352
  usesBraces ? !contents.includes('<>') ? contents = `<>${contents}</>` : null : null
286
353
  updatedContents = contents;
287
-
354
+
288
355
 
289
356
  let newAttributes = [];
290
357
  let oldAttributes = [];
291
358
  attributes.forEach((attribute) => {
292
359
  const { element, attributes } = attribute;
360
+ // make sure it isnt a jsx component
361
+ let isJSXComponent = element.match(/[A-Z]/) ? true : false;
362
+ if (isJSXComponent) {
363
+ return;
364
+ }
293
365
  if (Object.keys(attributes).length === 0) return;
294
366
 
295
367
 
296
368
  newAttributes.push(attribute);
297
369
  for (let key in attributes) {
298
370
 
299
- let value = attributes[key];
371
+ let value = attributes[key];
300
372
  let oldvalue = value;
301
373
  if (value && !value.new) {
374
+
302
375
  if (value && value.includes("={")) {
376
+
303
377
  value = value.replace("=", "");
304
378
  value == "undefined" ? (value = '"') : (value = value);
305
379
 
306
380
  key == 'style'
307
381
  && value.includes("{{")
308
382
  ? value = `{this.parseStyle({${value.split('{{')[1].split('}}')[0]}})}` : null
309
-
383
+
310
384
 
311
385
 
312
386
  value = `="\$${value}",`;
@@ -363,9 +437,7 @@ function Compiler(func, file) {
363
437
  valuestate = valuestate.match(regex) ? valuestate.match(regex)[0].split("useState(")[1].split(")")[0].trim() : valuestate
364
438
 
365
439
 
366
- let newState = `${varType} [${key}, ${setKey}] = this.useState('${key}', ${valuestate}
367
-
368
- `;
440
+ let newState = `${varType} [${key}, ${setKey}] = this.useState('${key}', ${valuestate}`;
369
441
  string = string.replace(line, newState);
370
442
  break;
371
443
  case line.includes("useRef") && !line.includes("import"):
@@ -410,54 +482,61 @@ function Compiler(func, file) {
410
482
  string = string.replaceAll('../src', './src')
411
483
 
412
484
  function parseComponents(body, isChild) {
413
- let componentRegex = /<([A-Z][A-Za-z0-9_-]+)([^>]*)>(.*?)<\/\1>|<([A-Z][A-Za-z0-9_-]+)([^]*?)\/>/gs;
485
+ let componentRegex = /<([A-Z][A-Za-z0-9_-]+)\s*([^>]*)>\s*([\s\S]*?)\s*<\/\1>|<([A-Z][A-Za-z0-9_-]+)([^]*?)\/>/gs;
414
486
 
415
487
  let componentMatch = body.match(componentRegex);
416
488
  let topComponent = "";
417
489
  componentMatch?.forEach(async (component) => {
418
490
 
419
491
  let [, element, attributes] = component;
420
-
492
+ let before = component;
493
+ component = component.trim().replace(/\s+/g, " ");
421
494
 
422
495
  !isChild ? (topComponent = component) : null;
423
- let before = component;
496
+
424
497
 
425
498
  let myChildrens = [];
426
499
 
427
500
  let name = component.split("<")[1].split(">")[0].split(" ")[0].replace("/", "");
428
- // some components will have props that have html inside of them we need to only get the props ex: <Header title={<h1>hello</h1>}></Header> -> title={<h1>hello</h1>} // also spread props ex: <Header {...props}></Header> -> {...props} or {...props, title: 'hello'} or {...props, color:{color: 'red'}}
429
- // grab ...( spread props )
430
- const dynamicAttributesRegex = /(\w+)(?:="([^"]*)")?(?:='([^']*)')?(?:=\{([^}]*)\})?(?:=\{(.*?)\})?(?:={([^}]*)})?(?:{([^}]*)})?|(?:{(?:[^{}]+|\{(?:[^{}]+|\{[^}]*\})*\})*\})|(\.{3}\{(?:[^{}]+|\{(?:[^{}]+|\{[^}]*\})*\})*\})/gs;
431
-
432
-
433
-
501
+ let componentAttributes = component.split("<")[1].split(">")[0].split(" ").join(" ").replace(name, "").trim();
502
+ const dynamicAttributesRegex = /(\w+)(?:="([^"]*?)"|='([^']*?)'|(?:=\{([^}]*?)\})?|(?:=\{(.*?)*\})?|(?:={([^}]*?)})?|(?:{([^}]*?)})?|(?:}))?|\$=\s*\{\s*\{\s*([^]*?)\s*\}\s*\}/gs;
434
503
 
435
504
 
436
505
 
437
506
  let props = component.match(dynamicAttributesRegex)
438
-
507
+
439
508
  let filteredProps = [];
440
509
  let isWithinComponent = false;
441
510
  let componentName = name
511
+ let currentProps = []
512
+
513
+ let $_ternaryprops = []
442
514
 
443
515
  for (let prop of props) {
444
516
 
445
517
  if (prop === componentName) {
446
518
 
447
- // If the component is encountered, start collecting props
448
519
  isWithinComponent = true;
449
520
  filteredProps.push(prop);
450
521
  } else if (isWithinComponent && prop.includes('=')) {
522
+
523
+ if (prop.startsWith('$=')) {
524
+ let old = prop
525
+ prop = prop.replace(/\$\s*=\s*\{\s*\{/, '').replace(/\}\s*\}/, '')
526
+
527
+ component = component.replace(old, prop)
528
+ componentAttributes = componentAttributes.replace(old, prop)
451
529
 
452
- if (prop.includes('${')) {
453
- // if it has an object inside of it then we should just do soemting:object else we should do something: `${object}`
530
+ $_ternaryprops.push(prop)
454
531
 
455
- prop = prop.replace('="', ':').replace('}"', '}')
456
- if (prop.includes('${')) {
532
+ }
533
+ else if (prop.includes('${')) {
534
+
535
+
536
+ prop = prop.replace('="', ':')
537
+ if (prop.includes('${')) {
457
538
  prop = prop.replace('="', ':')
458
- prop = prop.replace('${', '')
459
- prop = prop.replace('}', '')
460
-
539
+ prop = prop.replace('${', '')
461
540
  }
462
541
  if (prop.includes('="${{')) {
463
542
  prop = prop.replace('${{', '{')
@@ -467,12 +546,21 @@ function Compiler(func, file) {
467
546
  }
468
547
 
469
548
  }
470
- if (prop.startsWith('={')) {
471
- prop = prop.replace('={', ':`${')
472
- prop.replace('} ', '}`')
549
+ if (prop.includes('={')) {
550
+ let value = prop.split('={')
551
+ let isObj = value[1].match(/^{.*}$/gs) ? true : false
552
+ if (!isObj) {
553
+ // remove trailing }
554
+ value[1] = value[1].replace(/}\s*$/, '')
555
+ }
556
+
557
+ if(value[0] == 'style' && isObj){
558
+ value[1] = `this.parseStyle(${value[1]})`
559
+ }
560
+ prop = `${value[0]}:${value[1]}`
473
561
  }
474
562
 
475
- if (prop.includes('function')) {
563
+ if (prop.includes('function') || prop.includes('=>')){
476
564
  // parse 'function' to function
477
565
  prop = prop.replace("'", '')
478
566
 
@@ -481,82 +569,36 @@ function Compiler(func, file) {
481
569
 
482
570
  }
483
571
 
484
- prop = prop.replace('=function', ':function')
572
+ prop = prop.replace('=function', ':function')
485
573
  }
486
-
574
+
487
575
  filteredProps.push(prop);
488
576
 
489
577
 
490
- }
491
- else if (isWithinComponent && prop.includes('...')) {
492
-
493
-
494
-
495
- // Check if spread props are within curly braces
496
- if (prop.startsWith('{') && prop.endsWith('}')) {
497
- const spreadObject = prop
498
- const hasOtherObjects = spreadObject.split(',').filter((e) => e.includes(':')).length > 0;
499
-
500
- const isValidSpread = spreadObject.includes('...');
501
-
502
- let processedSpreadObject = '';
503
- if (isValidSpread) {
504
- // Split the spreadObject by commas and process each part individually
505
- const parts = spreadObject.split(',').map((part) => {
506
- if (part.trim().startsWith('{') && part.trim().endsWith('}')) {
507
- const nestedParts = part
508
- .trim()
509
- .slice(1, -1) // Remove outer {}
510
- .split(',')
511
- .map((nestedPart) => {
512
- return nestedPart.includes('...') ? nestedPart.trim().replace(/\.\.\./, '') : `...${nestedPart.trim()}`;
513
- });
514
- return `{${nestedParts.join(',')}}`;
515
- } else {
516
- return part.includes('...') ? part.trim() : part.trim().startsWith('{') ? `...${part.trim()}` : `${part.trim()}`;
517
- }
518
- });
519
- if (!parts.join(',').includes('{(')) {
520
-
521
- processedSpreadObject = `${parts.join(',')}`
522
-
523
- } else {
524
- let prop = parts.join(',')
525
- prop = prop.replaceAll('{(', '(')
526
- prop = prop.replaceAll(')}', ')')
527
- processedSpreadObject = prop
528
- }
529
- if (prop.includes('{{')) {
530
- let prop = parts.join(',')
531
- prop = prop.replaceAll('{{', '{')
532
- prop = prop.replaceAll('}}', '}')
533
- processedSpreadObject = prop
534
- }
535
-
536
578
 
537
- } else {
538
- // Process nested structures within the object
539
- processedSpreadObject = `{...${spreadObject}}`;
540
- }
541
-
542
- const $propsKey = `$props_${Math.random().toString(36).substring(2)}`;
543
- filteredProps.push(`${$propsKey}:${processedSpreadObject}`);
544
- }
579
+ }else if (isWithinComponent && prop.includes('}')) {
580
+
545
581
  }
546
582
 
583
+
547
584
  else {
548
585
  isWithinComponent = false;
549
586
  }
550
587
  }
588
+ component = component.replaceAll(/\s+/g, " ");
551
589
 
552
- // get inner content of <Component>inner content</Component>
553
- let children = new RegExp(`<${name}[^>]*>(.*?)<\/${name}>`, "gs").exec(component) ? new RegExp(`<${name}[^>]*>(.*?)<\/${name}>`, "gs").exec(component)[1] : null;
590
+ component = component.replace(componentAttributes, '')
591
+ $_ternaryprops.forEach((prop) => {
592
+ component = component.replace(prop, '')
593
+ })
554
594
 
555
- props = filteredProps.join(',')
595
+ let children = new RegExp(`<${name}[^>]*>([^]*)<\/${name}>`, 'gs').exec(component)?.[1] || null;
556
596
 
557
- let savedname = name;
597
+ props = filteredProps.join(',').replace(/\s+/g, " ").trim().replace(/,$/, '')
558
598
 
599
+ let savedname = name;
559
600
 
601
+
560
602
 
561
603
  name = name + Math.random().toString(36).substring(2);
562
604
  if (children && children.match(componentRegex)) {
@@ -573,12 +615,13 @@ function Compiler(func, file) {
573
615
  /<([A-Z][A-Za-z0-9_-]+)([^>]*)>(.*?)<\/\1>|<([A-Z][A-Za-z0-9_-]+)([^]*?)\/>/gs
574
616
  );
575
617
  if (html) {
576
- html = html.map((h) => h.trim().replace(/\s+/g, " ")).join(" ");
618
+ html = html.map((h) => h.trim().replace(/\s+/g, " ")).join(" ");
577
619
  child.children = child.children.replaceAll(html, `${html}`);
578
620
  // remove duplicate quotes
579
621
  }
580
622
 
581
623
  myChildrens.push(child.children);
624
+ childs = childs.filter((e) => e.parent !== name);
582
625
  }
583
626
  });
584
627
 
@@ -599,8 +642,10 @@ function Compiler(func, file) {
599
642
  * @memoize - memoize a component to be remembered on each render and replace the old jsx
600
643
  */
601
644
 
645
+
602
646
  let replace = "";
603
- replace = `\${this.memoize(this.createComponent(${savedname}, {${props}}, [\`${myChildrens.join(" ")}\`]))}`;
647
+ replace = `\${this.memoize(this.createComponent(${savedname}, {${props}}, [\`${myChildrens.join('')}\`]))}`;
648
+
604
649
 
605
650
  body = body.replace(before, replace);
606
651
  });
@@ -613,55 +658,55 @@ function Compiler(func, file) {
613
658
  const importRegex = /import\s*([^\s,]+|\{[^}]+\})\s*from\s*(['"])(.*?)\2/g;
614
659
  const imports = string.match(importRegex);
615
660
  let replaceMents = [];
616
-
617
-
618
- for (let match of imports) {
661
+
662
+
663
+ for (let match of imports) {
619
664
  let path = match.split('from')[1].trim().replace(/'/g, '').replace(/"/g, '').trim()
620
665
  switch (true) {
621
666
  case path && !path.includes('./') && !path.includes('/vader.js') && !path.includes('/vaderjs/client') && !path.startsWith('src') && !path.startsWith('public') && !path.includes('http') && !path.includes('https'):
622
- let componentFolder = fs.existsSync(process.cwd() + '/node_modules/' + path) ? process.cwd() + '/node_modules/' + path : process.cwd() + '/node_modules/' + path.split('/')[0]
623
- componentFolder = componentFolder.split(process.cwd())[1]
624
- if(!fs.existsSync(process.cwd() + componentFolder)){
667
+ let componentFolder = fs.existsSync(process.cwd() + '/node_modules/' + path) ? process.cwd() + '/node_modules/' + path : process.cwd() + '/node_modules/' + path.split('/')[0]
668
+ componentFolder = componentFolder.split(process.cwd())[1]
669
+ if (!fs.existsSync(process.cwd() + componentFolder)) {
625
670
  throw new Error('Could not find ' + path + ' at ' + match + ' in file ' + file)
626
- }
627
-
628
- if(!fs.existsSync(process.cwd() + '/dist/src/' + componentFolder.split('/').slice(2).join('/'))){
629
- fs.mkdirSync(process.cwd() + '/dist/src/' + componentFolder.split('/').slice(2).join('/'), { recursive: true })
671
+ }
672
+
673
+ if (!fs.existsSync(process.cwd() + '/dist/src/' + componentFolder.split('/').slice(2).join('/'))) {
674
+ fs.mkdirSync(process.cwd() + '/dist/src/' + componentFolder.split('/').slice(2).join('/'), { recursive: true })
675
+ }
676
+
677
+ let baseFolder = componentFolder.split('node_modules')[1].split('/')[1]
678
+ let glp = globSync('**/**/**/**.{jsx,js}', {
679
+ cwd: process.cwd() + '/node_modules/' + baseFolder + '/',
680
+ absolute: true,
681
+ recursive: true
682
+ })
683
+ for (let file of glp) {
684
+ let text = fs.readFileSync(file, "utf8");
685
+ if (!file.endsWith('.js') && file.endsWith('.jsx')) {
686
+ text = Compiler(text, file);
687
+
630
688
  }
631
-
632
- let baseFolder = componentFolder.split('node_modules')[1].split('/')[1]
633
- let glp = globSync('**/**/**/**.{jsx,js}', {
634
- cwd: process.cwd() + '/node_modules/' + baseFolder + '/',
635
- absolute: true,
636
- recursive: true
637
- })
638
- for (let file of glp) {
639
- let text = fs.readFileSync(file, "utf8");
640
- if(!file.endsWith('.js') && file.endsWith('.jsx')){
641
- text = Compiler(text, file);
642
- }
643
- let dest = file.split('node_modules')[1]
644
- dest = dest.split(baseFolder)[1]
645
- // write to dist
646
- writer(process.cwd() + '/dist/src/' + baseFolder + dest, text)
647
- let importname = match.split('import')[1].split('from')[0].trim()
648
- let oldImportstring = match.split('from')[1].trim().replace(/'/g, '').replace(/"/g, '').trim()
649
- let newImport = `/src/${baseFolder + dest}`
650
- newImport = newImport.replaceAll('.jsx', '.js').replaceAll('\\', '/')
651
- replaceMents.push({ match: oldImportstring, replace: newImport })
652
- console.log(`📦 imported Node Package ${baseFolder} `)
653
- }
654
-
689
+ let dest = file.split('node_modules')[1]
690
+ dest = dest.split(baseFolder)[1]
691
+ writer(process.cwd() + '/dist/src/' + baseFolder + dest, text)
692
+ let importname = match.split('import')[1].split('from')[0].trim()
693
+ let oldImportstring = match.split('from')[1].trim().replace(/'/g, '').replace(/"/g, '').trim()
694
+ let newImport = `/src/${baseFolder + dest}`
695
+ newImport = newImport.replaceAll('.jsx', '.js').replaceAll('\\', '/')
696
+ replaceMents.push({ match: oldImportstring, replace: newImport })
697
+ console.log(`📦 imported Node Package ${baseFolder} `)
698
+ }
699
+
655
700
 
656
701
  break;
657
702
  default:
658
703
  break;
659
704
  }
660
- }
661
-
662
- for(let replace of replaceMents){
705
+ }
706
+
707
+ for (let replace of replaceMents) {
663
708
  string = string.replaceAll(replace.match, replace.replace)
664
- }
709
+ }
665
710
 
666
711
  string = string.replaceAll(/\$\{[^{]*\.{3}/gs, (match) => {
667
712
  if (match.includes('...')) {
@@ -716,7 +761,7 @@ function Compiler(func, file) {
716
761
  let asyncimportMatch = line.match(/import\s*\((.*)\)/gs);
717
762
  // handle import { Component } from 'vaderjs/runtime/vader.js'
718
763
  let regularimportMatch = line.match(/import\s*([A-Za-z0-9_-]+)\s*from\s*([A-Za-z0-9_-]+)|import\s*([A-Za-z0-9_-]+)\s*from\s*(".*")|import\s*([A-Za-z0-9_-]+)\s*from\s*('.*')|import\s*([A-Za-z0-9_-]+)\s*from\s*(\{.*\})/gs);
719
-
764
+
720
765
  if (asyncimportMatch) {
721
766
  asyncimportMatch.forEach(async (match) => {
722
767
  let beforeimport = match
@@ -770,7 +815,7 @@ function Compiler(func, file) {
770
815
 
771
816
  let newImport = ''
772
817
  let name = match.split('import')[1].split('from')[0].trim()
773
-
818
+
774
819
 
775
820
  switch (true) {
776
821
  case path && path.includes('json'):
@@ -796,9 +841,9 @@ function Compiler(func, file) {
796
841
  newImport = ``
797
842
  break;
798
843
  case path && !path.startsWith('./') && !path.includes('/vader.js') && !path.startsWith('src') && !path.startsWith('public') &&
799
- path.match(/^[A-Za-z0-9_-]+$/gs) && !path.includes('http') && !path.includes('https'):
800
- console.log(path)
801
- break;
844
+ path.match(/^[A-Za-z0-9_-]+$/gs) && !path.includes('http') && !path.includes('https'):
845
+
846
+ break;
802
847
  default:
803
848
  let beforePath = path
804
849
  let deep = path.split('/').length - 1
@@ -850,26 +895,20 @@ const glb = await glob("**/**/**/**.{jsx,js}", {
850
895
  });
851
896
  async function Build() {
852
897
  globalThis.isBuilding = true
853
- console.log('Compiling......')
898
+ console.log(globalThis.isProduction ? 'Creating Optimized Production Build\n' : '')
899
+ let str = `Page \t\t\t\t Size\n`
900
+ globalThis.isProduction ? console.log('\x1b[32m%s\x1b[0m', str) : null
854
901
  let reader = async (file) => {
855
902
  let text = await fs.readFileSync(file, "utf8");
856
903
  return text;
857
904
  };
858
905
 
859
-
860
-
861
-
862
906
 
863
-
864
- // Process files in the 'pages' directory
865
- let appjs = '';
866
- let hasWritten = []
907
+
867
908
  function ssg(routes = []) {
868
- globalThis.isBuilding = true
869
- console.log(`Generating html files for ${routes.length} routes`)
909
+ globalThis.isBuilding = true
870
910
  routes.forEach(async (route) => {
871
- if (route.url.includes(':')) {
872
- console.log('Route ' + route.url + ' is a dynamic route and will not be generated')
911
+ if (route.url.includes(':')) {
873
912
  return
874
913
  }
875
914
  let equalparamroute = routes.map((e) => {
@@ -897,65 +936,24 @@ async function Build() {
897
936
  <meta charset="UTF-8">
898
937
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
899
938
  <script type="module" id="meta">
900
- window.history.pushState({}, '', '${route.url}')
901
- window.module = await import('/${route.fileName.replace('.jsx', '.js')}')
902
- let metadata = await module.$metadata
903
- if(metadata && metadata.title){
904
- document.head.innerHTML += '<title>' + metadata.title + '</title>'
905
- }
906
- if(metadata && metadata.description){
907
- document.head.innerHTML += '<meta name="description" content="' + metadata.description + '">'
908
- }
909
- if(metadata && metadata.keywords){
910
- document.head.innerHTML += '<meta name="keywords" content="' + metadata.keywords + '">'
911
- }
912
- if(metadata && metadata.author){
913
- document.head.innerHTML += '<meta name="author" content="' + metadata.author + '">'
914
- }
915
- if(metadata && metadata.image){
916
- let image = metadata.image.file
917
- let type = metadata.image.type
918
-
919
- document.head.innerHTML += '<meta property="og:image" content="' + image + '">'
920
- document.head.innerHTML += '<meta property="og:image:type" content="' + type + '">'
921
- }
922
- if(metadata && metadata.url){
923
- document.head.innerHTML += '<meta property="og:url" content="' + metadata.url + '">'
924
- }
939
+ window.history.pushState({}, '', '${route.url}')
925
940
 
926
- if(metadata && metadata.robot){
927
- document.head.innerHTML += '<meta name="robots" content="' + metadata.robot + '">'
928
- }
929
- if(metadata && metadata.manifest){
930
- document.head.innerHTML += '<link rel="manifest" href="' + metadata.manifest + '">'
931
- }
932
- if(metadata && metadata.tags){
933
- metadata.tags.forEach(tag => {
934
- document.head.innerHTML += tag
935
- })
936
- }
937
-
938
- if(metadata && metadata.styles){
939
- metadata.styles.forEach(style => {
940
- style = style.replaceAll('./', '/')
941
- style = style.replaceAll('../', '/')
942
- style = style.replace("'", '')
943
- document.head.innerHTML += '<link rel="stylesheet" href="' + style + '">'
944
- })
945
- }
946
- if(metadata && metadata.icon){
947
- document.head.innerHTML += '<link rel="icon" href="' + metadata.icon + '">'
948
- }
949
941
  </script>
950
942
  <script type="module" id="router">
951
943
  import VaderRouter from '/router.js'
952
944
  const router = new VaderRouter('${route.url}', 3000)
953
945
  router.get('${route.url}', async (req, res) => {
954
- let module = await import('/${route.fileName.replace('.jsx', '.js')}')
955
- if(Object.keys(module).includes('$prerender') && !module.$prerender){
956
- document.head.setAttribute('prerender', 'false')
946
+ try{
947
+ let module = await import('/${route.fileName.replace('.jsx', '.js')}')
948
+ if(Object.keys(module).includes('$prerender') && !module.$prerender){
949
+ document.head.setAttribute('prerender', 'false')
950
+ }
951
+ res.render(module, req, res, module.$metadata)
952
+ }
953
+ catch(error){
954
+ document.documentElement.setAttribute('error', JSON.stringify({stack: error.stack, message: error.message, at: '${route.fileName}', line: error.lineNumber}))
955
+ throw new Error({stack: error.stack, message: error.message})
957
956
  }
958
- res.render(module, req, res, module.$metadata)
959
957
  })
960
958
  ${equalparamroute.length > 0 ? equalparamroute.map((e) => {
961
959
 
@@ -1016,52 +1014,76 @@ async function Build() {
1016
1014
  globalThis.listen = true;
1017
1015
 
1018
1016
  const browser = await puppeteer.launch({
1019
- headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'],
1017
+ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'],
1020
1018
  warning: false,
1021
- })
1022
-
1023
- const browserPID = browser.process().pid
1019
+ })
1024
1020
  try {
1025
1021
 
1026
1022
  route.url = route.url.replaceAll(/\/:[a-zA-Z0-9_-]+/gs, '')
1027
1023
  let page = await browser.newPage();
1028
- await page.goto(`http://localhost:${port}/`, { waitUntil: 'networkidle2' });
1029
- await page.waitForSelector('#root', { timeout: 10000 })
1024
+ // Handle browser errors
1025
+ page.on('error', (err) => {
1026
+ console.error('BROWSER ERROR:', err);
1027
+ });
1028
+ await page.evaluate(() => {
1029
+ window.onerror = function (msg, url, lineNo, columnNo, error) {
1030
+ console.log(msg, url, lineNo, columnNo, error)
1031
+ }
1032
+ })
1033
+ try {
1034
+ page.on('pageerror', async err => {
1035
+ let errorObj = await page.evaluate(() => document.documentElement.getAttribute('error'))
1036
+ console.log('\x1b[31m%s\x1b[0m', 'PAGE ERROR:', errorObj);
1037
+
1038
+ });
1039
+ } catch (error) {
1040
+ browser.close()
1041
+ }
1042
+ // Handle page crashes
1043
+ page.on('crash', () => {
1044
+ console.error('PAGE CRASHED');
1045
+ });
1046
+ page.on('requestfailed', request => {
1047
+ console.error('REQUEST FAILED:', request.url(), request.failure().errorText);
1048
+ });
1049
+ await page.goto(`http://localhost:${port}/`, { waitUntil: 'networkidle2' });
1050
+
1051
+
1052
+
1053
+
1054
+
1055
+
1056
+
1030
1057
  await page.evaluate(() => {
1031
1058
  document.getElementById('meta').remove()
1032
1059
  document.querySelector('#isServer').innerHTML = 'window.isServer = false'
1033
1060
  if (document.head.getAttribute('prerender') === 'false') {
1034
1061
  document.querySelector('#root').innerHTML = ''
1062
+ console.log(`Disabled prerendering for ${window.location.pathname}`)
1035
1063
  }
1036
- })
1037
- const html = await page.content();
1064
+ })
1065
+ let html = await page.content();
1038
1066
 
1039
- await page.close();
1067
+ html = await prettier.format(html, { parser: "html" })
1068
+
1069
+
1040
1070
  await writer(process.cwd() + '/dist/' + (route.url === '/' ? 'index.html' : `${route.url}/` + 'index.html'), html)
1041
- await browser.close();
1042
- server.close()
1043
1071
 
1072
+
1044
1073
  } catch (error) {
1045
- server.close()
1046
- await browser.close();
1047
- }
1074
+ console.log(error)
1075
+ }
1076
+
1048
1077
  finally {
1049
- await browser.close();
1078
+ browser.close()
1050
1079
  server.close()
1051
1080
  }
1052
- try {
1053
- process.kill(browserPID )
1054
- } catch (error) {
1055
- }
1056
-
1057
-
1058
1081
  })
1059
1082
 
1060
1083
  let timeout = setTimeout(() => {
1061
1084
  globalThis.isBuilding = false
1062
1085
  clearTimeout(timeout)
1063
- }, 1000)
1064
- console.log(`Generated ${routes.length} html files for ${routes.length} routes`)
1086
+ }, 1000)
1065
1087
  }
1066
1088
 
1067
1089
  globalThis.routes = []
@@ -1093,17 +1115,14 @@ async function Build() {
1093
1115
 
1094
1116
 
1095
1117
  let data = await fs.readFileSync(origin, "utf8");
1096
- data = Compiler(data, origin);
1097
-
1098
-
1099
-
1100
- await writer(process.cwd() + "/dist/" + fileName.replace('.jsx', '.js'), data).then(async () => {
1101
-
1102
-
1103
-
1104
- await writer(process.cwd() + "/dist/" + fileName.replace('.jsx', '.js'), data)
1105
-
1106
- })
1118
+
1119
+ // gen sourcemap if not production
1120
+ let size = fs.statSync(origin).size;
1121
+ if(!globalThis.isProduction){
1122
+ let { sourceMap } = sourceMapGen({origin:origin, fileName:fileName}, await Compiler(data, origin))
1123
+ await writer(process.cwd() + "/dist/src/maps/" + fileName.replace('.jsx', '.js.map'), JSON.stringify(sourceMap, null, 2))
1124
+ }
1125
+ await writer(process.cwd() + "/dist/" + fileName.replace('.jsx', '.js'), await Compiler(data, origin))
1107
1126
 
1108
1127
  // configure routing for each page
1109
1128
 
@@ -1167,8 +1186,18 @@ async function Build() {
1167
1186
 
1168
1187
  globalThis.routes.push({ fileName: fileName, url: obj.url, html: '/' + (isBasePath ? 'index.html' : `${obj.url}/` + 'index.html') })
1169
1188
 
1170
-
1171
-
1189
+
1190
+ let stats = {route: obj.url.padEnd(30),
1191
+ size: Math.round(size / 1000) + 'kb',
1192
+ letParentFolder: obj.url.split('/').slice(0, -1).join('/'),
1193
+ isChildRoute: obj.url.split('/').slice(0, -1).join('/').includes(':') ? true : false,
1194
+ parentRoute: obj.url.split('/').slice(0, -1).join('/').split(':')[0],
1195
+
1196
+ }
1197
+ stats.isChildRoute ? stats.route = `? ${obj.url}` : null
1198
+ let string = `${isBasePath ? '+' : '+'} ${stats.route.padEnd(30)} ${stats.size}`
1199
+
1200
+ globalThis.isProduction ? console.log(string) : null
1172
1201
  }
1173
1202
 
1174
1203
  ssg(globalThis.routes)
@@ -1205,10 +1234,7 @@ async function Build() {
1205
1234
  if (name.includes('.jsx')) {
1206
1235
  data = Compiler(data, process.cwd() + "/src/" + name);
1207
1236
 
1208
- await writer(process.cwd() + "/dist/src/" + name.split('.jsx').join('.js'), data).then(async () => {
1209
- await writer(process.cwd() + "/dist/src/" + name.replace('.jsx', '.js'), data)
1210
-
1211
- })
1237
+ await writer(process.cwd() + "/dist/src/" + name.split('.jsx').join('.js'), data)
1212
1238
  return
1213
1239
  }
1214
1240
  bundleSize += fs.statSync(process.cwd() + "/src/" + name).size;
@@ -1256,17 +1282,17 @@ async function Build() {
1256
1282
  }
1257
1283
 
1258
1284
  globalThis.isBuilding = false
1259
- console.log(`📦 Build completed: Build Size -> ${Math.round(bundleSize / 1000)}kb`)
1285
+ console.log(`\nTotal bundle size: ${Math.round(bundleSize / 1000)}kb`)
1260
1286
 
1261
1287
  bundleSize = 0;
1262
-
1288
+
1263
1289
  return true
1264
1290
  }
1265
1291
  const s = () => {
1266
1292
 
1267
1293
  const server = http.createServer((req, res) => {
1268
1294
 
1269
- const validExtensions = ['.js', '.css', '.mjs', '.cjs', '.html', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp4', '.webm', '.ogg'];
1295
+ const validExtensions = ['.js', '.css', '.mjs', '.cjs', '.html', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp4', '.webm', '.ogg', '.map']
1270
1296
 
1271
1297
  if (!validExtensions.some(ext => req.url.endsWith(ext))) {
1272
1298
  req.url = req.url !== '/' ? req.url.split('/')[1] : req.url;
@@ -1289,6 +1315,7 @@ const s = () => {
1289
1315
  let ws = new WebSocket('ws://localhost:${process.env.PORT || 3000}')
1290
1316
  ws.onmessage = (e) => {
1291
1317
  if(e.data === 'reload'){
1318
+ console.log('Reloading...')
1292
1319
  window.location.reload()
1293
1320
  }
1294
1321
  }
@@ -1310,7 +1337,7 @@ const s = () => {
1310
1337
 
1311
1338
 
1312
1339
  function getContentType(filePath) {
1313
- let ext = ['.js', '.css', '.mjs', '.cjs', '.html', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp4', '.webm', '.ogg'].includes(path.extname(filePath)) ? path.extname(filePath) : '.html'
1340
+ let ext = ['.js', '.css', '.mjs', '.cjs', '.html', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp4', '.webm', '.ogg', '.map'].includes(path.extname(filePath)) ? path.extname(filePath) : '.html'
1314
1341
  switch (ext) {
1315
1342
  case '.js':
1316
1343
  return 'text/javascript';
@@ -1322,6 +1349,8 @@ const s = () => {
1322
1349
  return 'text/javascript';
1323
1350
  case '.html':
1324
1351
  return 'text/html';
1352
+ case '.map':
1353
+ return 'application/json';
1325
1354
  case '.json':
1326
1355
  return 'application/json';
1327
1356
  case '.png':
@@ -1348,26 +1377,18 @@ const s = () => {
1348
1377
  const PORT = process.env.PORT || 3000;
1349
1378
  server.listen(PORT, () => {
1350
1379
  console.log(`Server is running on port ${PORT}`);
1380
+ globalThis.ws = ws
1351
1381
  });
1352
- let i =
1353
- setInterval(() => {
1354
- if (globalThis.isBuilding && globalThis.devMode) {
1355
-
1356
- ws.clients.forEach((client) => {
1357
- client.send('reload')
1358
- })
1359
- } else {
1360
- clearInterval(i)
1361
- }
1362
- }, 120)
1382
+
1363
1383
 
1364
1384
  }
1365
1385
 
1366
1386
 
1367
1387
  switch (true) {
1368
- case process.argv.includes('--watch') && !process.argv.includes('--build') && !process.argv.includes('--serve'):
1388
+ case process.argv.includes('dev') && !process.argv.includes('build') && !process.argv.includes('start'):
1369
1389
 
1370
1390
  globalThis.devMode = true
1391
+ globalThis.isProduction = false
1371
1392
  console.log(`
1372
1393
  Vader.js v1.3.3
1373
1394
  - Watching for changes in ./pages
@@ -1384,12 +1405,19 @@ Vader.js v1.3.3
1384
1405
  if (event == 'change'
1385
1406
  && !globalThis.isBuilding
1386
1407
  ) {
1408
+ if(globalThis.ws && !globalThis.isWriting){
1409
+ globalThis.ws.clients.forEach((client) => {
1410
+ console.log('Reloading...')
1411
+ client.send('reload')
1412
+ })
1413
+ }
1387
1414
 
1415
+ globalThis.isBuilding = true
1388
1416
  Build()
1389
1417
  }
1390
1418
  }).on('error', (err) => console.log(err))
1391
1419
  })
1392
- let p = process.argv[process.argv.indexOf('--watch') + 1] || process.env.PORT || 3000
1420
+ let p = process.argv[process.argv.indexOf('dev') + 1] || 3000
1393
1421
 
1394
1422
  process.env.PORT = p
1395
1423
  s()
@@ -1397,16 +1425,21 @@ Vader.js v1.3.3
1397
1425
  globalThis.listen = true;
1398
1426
 
1399
1427
  break;
1400
- case process.argv.includes('--build') && !process.argv.includes('--watch') && !process.argv.includes('--serve'):
1428
+ case process.argv.includes('build') && !process.argv.includes('dev') && !process.argv.includes('start'):
1401
1429
  globalThis.devMode = false
1430
+ globalThis.isProduction = true
1431
+ globalThis.routeStates = []
1402
1432
  console.log(`
1403
1433
  Vader.js v1.3.3
1404
1434
  Building to ./dist
1405
1435
  `)
1436
+ if(fs.existsSync(process.cwd() + '/dist/src/maps')){
1437
+ fs.rmSync(process.cwd() + '/dist/src/maps', { recursive: true })
1438
+ }
1406
1439
  Build()
1407
1440
 
1408
1441
  break;
1409
- case process.argv.includes('--serve') && !process.argv.includes('--watch') && !process.argv.includes('--build'):
1442
+ case process.argv.includes('start') && !process.argv.includes('dev') && !process.argv.includes('build'):
1410
1443
  let port = process.argv[process.argv.indexOf('--serve') + 1] || 3000
1411
1444
  process.env.PORT = port
1412
1445
  globalThis.devMode = false
@@ -1418,17 +1451,19 @@ url: http://localhost:${port}
1418
1451
  s()
1419
1452
  break;
1420
1453
  default:
1421
- console.log(`
1454
+ // add color
1455
+ console.log(`
1422
1456
  Vader.js is a reactive framework for building interactive applications for the web built ontop of bun.js!
1423
1457
 
1424
1458
  Usage: vader <command>
1425
1459
 
1426
1460
  Commands:
1427
- --watch (port) Watch the pages folder for changes with hot reloading
1461
+
1462
+ vaderjs dev Start the development server
1428
1463
 
1429
- --build Build the project to ./dist
1464
+ vaderjs build Build the project to ./dist
1430
1465
 
1431
- --serve (400) Serve the project on a port (default 3000 or process.env.PORT)
1466
+ vaderjs start <port> Production Mode (default 3000 or process.env.PORT)
1432
1467
 
1433
1468
  Learn more about vader: https://vader-js.pages.dev/
1434
1469