vaderjs 1.3.3-alpha-151 → 1.3.3-alpha-160

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-alpha-151",
5
+ "version": "1.3.3-alpha-160",
6
6
  "bin": {
7
7
  "vader": "./vader.js"
8
8
  },
@@ -27,8 +27,9 @@
27
27
  "email": "malikwhitterb@gmail.com"
28
28
  },
29
29
  "dependencies": {
30
- "glob": "latest",
31
- "terser":"latest",
32
- "puppeteer":"latest"
30
+ "glob": "latest",
31
+ "puppeteer":"latest",
32
+ "dotenv":"latest"
33
+
33
34
  }
34
35
  }
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){(new DOMParser).parseFromString(this.render(),"text/html").body.querySelector(`[ref="${e}"]`),document.querySelector(`[ref="${e}"]`)}else{let e=this.key?document.querySelector(`[key="${this.key}"]`):null,t=(new DOMParser).parseFromString(this.render(),"text/html").body;if(t=document.createElement("div").appendChild(t),!e&&(e=document.querySelector(`[key="${t.attributes?.key?.value||null}"]`)),!e)return void console.error('Hydration failed, component not found got ensure you have set key="a value" on the component or this.key inside of function or render method body');t.querySelectorAll("*").forEach((e=>{e.hasAttribute("key")&&e.innerHTML!==document.querySelector(`[key="${e.attributes.key.value}"]`).innerHTML&&document.querySelector(`[key="${e.attributes.key.value}"]`).replaceWith(e)}))}}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){s=s+this.key||2022;let i={},o=(r=r.replace(/,,/g,",")).replaceAll(",,",",");for(var h in n){let e=n[h];i[o.split(",")[h]]=e}r=r.replace(",,",",");return this.functions.find((e=>e.ref===s))||document.addEventListener(`$dispatch_#id=${s}`,(t=>{let{name:r,event:n}=t.detail;if(r===s){let t=this.functions.find((e=>e.ref===s)).params;Object.keys(t).forEach((e=>{t[e]instanceof CustomEvent&&delete t[e],void 0===t[e]?delete t[e]:t[e]}));let r=n;console.log(r),e(n.detail.event)}})),window.callFunction=(e,t)=>{document.dispatchEvent(new CustomEvent(`$dispatch_#id=${e}`,{detail:{name:e,params:null,event:t}}))},!this.functions.find((e=>e.ref===s))&&this.functions.push({ref:s,params:i}),t?e:`((event)=>{event.target.ev = event; callFunction('${s}', event.target.ev)})(event)`}useState(e,t){this.state[e]||(this.state[e]=t);let s=()=>this.state[e],r=s();return[r,(t,n)=>{console.log(t),this.state[e]=t,r=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)=>{states[e]||(states[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.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 isClass(e){return"function"==typeof e&&/^class\s/.test(Function.prototype.toString.call(e))}let r=isClass(e)?new e(t):null;if(!e)throw new Error("Component must be defined");let i=new Component(t);if(isClass(e))r.props=t||{},r.props.children=s.join("")||[],r.props.children=s.join(""),r.parentNode=this,r.request=this.request,r.response=this.response,r.key=r.props.key?r.props.key:Math.random(),i=r;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=>{if(e.startsWith("$props")&&(r.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]}))}})),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(">,",">")),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];i&&n&&!i.isEqualNode(n)&&s.push({type:"replace",old:i,new:n.cloneNode(!0)})}return s}updateChangedElements(e){e.forEach((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("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{if(this.key?document.querySelector(`[key="${this.key}"]`):null){let e=(new DOMParser).parseFromString(this.render(),"text/html").body.querySelector(`[key="${this.key}"]`),t=document.querySelector(`[key="${this.key}"]`),s=this.domDifference(t.children,e.children);this.updateChangedElements(s)}}}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)=>{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)`}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 getValue=()=>this.state[e];let r=getValue();return[getValue(),(t,i)=>{const n=s(r,t)??t;this.state[e]=n,this.hydrate(i),r=getValue()}]}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.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){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};
package/vader.js CHANGED
@@ -4,20 +4,26 @@ import { glob, globSync, globStream, globStreamSync, Glob, } from 'glob'
4
4
  import puppeteer from 'puppeteer';
5
5
  import http from 'http'
6
6
  import { WebSocketServer } from 'ws'
7
- import { watch } from "fs";
7
+ import { watch } from "fs";
8
8
  import path from 'path'
9
- let config = await import('file://' + process.cwd() + '/vader.config.js').then((e) => e.default || e)
10
-
11
-
12
- let start = Date.now()
13
- let bundleSize = 0;
14
- let errorCodes = {
15
- "SyntaxError: Unexpected token '<'": "You forgot to enclose tags in a fragment <></>",
16
- }
17
- /**
18
- * define directories
19
- */
9
+ let config = await import('file://' + process.cwd() + '/vader.config.js').then((e) => e.default || e)
10
+ let writer = async (file, data) => {
11
+ globalThis.isWriting = file
12
+ switch (true) {
13
+ case !fs.existsSync(file):
14
+ fs.mkdirSync(file.split('/').slice(0, -1).join('/'), { recursive: true })
15
+ break;
16
+ }
17
+ if (globalThis.isWriting !== file) {
18
+ return
19
+ }
20
+ await fs.writeFileSync(file, data);
20
21
 
22
+ globalThis.isWriting = null
23
+ return { _written: true };
24
+ };
25
+
26
+ let bundleSize = 0;
21
27
 
22
28
  if (!fs.existsSync(process.cwd() + '/dist')) {
23
29
  fs.mkdirSync(process.cwd() + '/dist')
@@ -31,47 +37,16 @@ if (typeof process.env.isCloudflare !== "undefined" || !fs.existsSync(process.cw
31
37
  fs.writeFileSync(process.cwd() + "/dist/index.html", htmlFile)
32
38
  }
33
39
 
34
-
35
40
 
36
- function Compiler(func, file) {
37
- let string = func;
38
- // Remove block comments
39
41
 
42
+ function Compiler(func, file) {
43
+ let string = func;
40
44
  let returns = []
41
45
  let comments = string.match(/\{\s*\/\*.*\*\/\s*}/gs)?.map((comment) => comment.trim());
42
46
 
43
- let savedfuncnames = [];
44
- let functions = string.match(
45
- /(?:const|let)\s*([a-zA-Z0-9_-]+)\s*=\s*function\s*\(([^)]*)\)|function\s*([a-zA-Z0-9_-]+)\s*\(([^)]*)\)/gs
46
- )
47
- ?.map((match) => match.trim());
48
-
49
- let functionNames = [];
50
-
51
-
52
- functions && functions.forEach((func) => {
53
- if (
54
- !func.match(
55
- /(?:const|let)\s*([a-zA-Z0-9_-]+)\s*=\s*function\s*\(([^)]*)\)|function\s*([a-zA-Z0-9_-]+)\s*\(([^)]*)\)/gs
56
- )
57
- ) {
58
- return;
59
- }
60
-
61
- let name = func.split(" ")[1].split("(")[0].trim();
62
-
63
- let lines = string.match(/return\s*\<>.*\<\/>/gs);
64
-
65
- if (lines) {
66
- for (let i = 0; i < lines.length; i++) {
67
- let line = lines[i];
68
-
69
- if (!functionNames.includes(name)) {
70
- functionNames.push(name);
71
- }
72
- }
73
- }
74
- });
47
+
48
+
49
+
75
50
 
76
51
  // get all Obj({}) and parse to JSON.stringify
77
52
 
@@ -87,6 +62,51 @@ function Compiler(func, file) {
87
62
 
88
63
  let childs = [];
89
64
 
65
+ const spreadAttributeRegex = /\s*([a-zA-Z0-9_-]+)(\s*\$\s*=\s*{{((?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*)}})/gs;
66
+ let spreadMatch;
67
+ while ((spreadMatch = spreadAttributeRegex.exec(string)) !== null) {
68
+ let [, element, spread] = spreadMatch;
69
+ let isJSXComponent = element.match(/[A-Z]/) ? true : false;
70
+ if (isJSXComponent) {
71
+ continue
72
+
73
+ }
74
+ let old = spread;
75
+
76
+ // turn spread into attributes
77
+ spread = spread.replace(/\s*$\s*=\s*/, "");
78
+ spread = spread.replace(/{{/, "");
79
+ spread = spread.replace(/}}/, "");
80
+ spread = spread.replace(/\$\s*=\s*/, "");
81
+
82
+ // turn : into =
83
+
84
+ // do not split inner Objects ex: {color: 'red', background: {color: 'blue'}} -> {color: 'red', background: {color: 'blue'}}
85
+ let splitByCommas = spread.split(/,(?![^{]*})/g);
86
+ splitByCommas = splitByCommas.map((e) => e.trim())
87
+ splitByCommas = splitByCommas.map((e) => {
88
+ switch (true) {
89
+ case e.includes('function') || e.includes('=>'):
90
+ e = e.replace(/:(.*)/gs, '={$1}')
91
+ break;
92
+ case e.includes('style'):
93
+ e = e.replace(/:(.*)/gs, '="${this.parseStyle($1)}"')
94
+ break;
95
+ case e.includes('[') && e.includes(']'):
96
+ e = e.replace(/:(.*)/gs, '={$1.join(" ")}')
97
+ break;
98
+ default:
99
+ }
100
+
101
+ return e.trim()
102
+ })
103
+
104
+ let newSpread = `\t` + splitByCommas.join(' ') + `\t`
105
+
106
+ string = string.replace(old, newSpread);
107
+
108
+ }
109
+
90
110
 
91
111
 
92
112
  function extractAttributes(code) {
@@ -98,40 +118,40 @@ function Compiler(func, file) {
98
118
  const attributeRegex =
99
119
  /\s*([a-zA-Z0-9_-]+)(\s*=\s*("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|\{(?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*\}|(?:\([^)]*\)|\{[^}]*\}|()=>\s*(?:\{[^}]*\})?)|\[[^\]]*\]))?/gs;
100
120
 
101
- // only return elements with attribute {()=>{}} or if it also has parameters ex: onclick={(event)=>{console.log(event)}} also get muti line functions
121
+
122
+
123
+
102
124
  const functionAttributeRegex = /\s*([a-zA-Z0-9_-]+)(\s*=\s*{((?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*)})/gs;
103
125
 
104
126
  let attributesList = [];
105
127
 
106
- // handle functions
128
+ let spreadAttributes = [];
129
+ let spreadMatch;
130
+ /**
131
+ * @search - handle spread for html elements
132
+ * @keywords - spread, spread attributes, spread props, spread html attributes
133
+ */
134
+
135
+
136
+ /**
137
+ * @search - handle function parsing for html elements
138
+ * @keywords - function, function attributes, function props, function html attributes
139
+ *
140
+ */
107
141
  let functionAttributes = [];
108
142
  let functionMatch;
109
143
  while ((functionMatch = functionAttributeRegex.exec(code)) !== null) {
144
+
110
145
  let [, attributeName, attributeValue] = functionMatch;
111
146
  let attribute = {};
112
147
 
113
- if (attributeValue && attributeValue.includes("=>") || attributeValue && attributeValue.includes("function")) {
114
- let functionparams = [];
115
- // ref with no numbers
148
+ if (attributeValue && attributeValue.includes("=>") || attributeValue && attributeValue.includes("function")
149
+ && !spreadFunctions.includes(attributeValue)
150
+ ) {
151
+
116
152
  let ref = Math.random().toString(36).substring(2).split('').filter((e) => !Number(e)).join('')
117
153
  let old = `${attributeName}${attributeValue}`
118
- functionNames.forEach((name) => {
119
- string.split("\n").forEach((line) => {
120
- if (line.includes(name) && line.includes("function")) {
121
- line = line.trim();
122
- line = line.replace(/\s+/g, " ");
123
-
124
- let ps = line.split("(").slice(1).join("(").split(")")[0].trim();
125
-
126
- // remove comments
127
- ps = ps.match(/\/\*.*\*\//gs)
128
- ? ps.replace(ps.match(/\/\*.*\*\//gs)[0], "")
129
- : ps;
130
- functionparams.push({ ref: ref, name: name, params: ps });
131
-
132
- }
133
- });
134
- });
154
+
135
155
  let elementMatch = string.match(/<([a-zA-Z0-9_-]+)([^>]*)>/gs);
136
156
  let isJSXComponent = false;
137
157
  elementMatch.forEach((element) => {
@@ -143,7 +163,7 @@ function Compiler(func, file) {
143
163
  .split(" ")[0];
144
164
  isJSXComponent = elementTag.match(/^[A-Z]/) ? true : false;
145
165
  }
146
- });
166
+ });
147
167
  // add ; after newlines
148
168
 
149
169
 
@@ -185,18 +205,17 @@ function Compiler(func, file) {
185
205
  newvalue = newvalue.replace(/}\s*$/, '');
186
206
 
187
207
 
188
-
189
- functionparams.length > 0 ? params = params + ',' + functionparams.map((e) => e.name).join(',') : null
208
+
190
209
 
191
210
  newvalue = newvalue.replaceAll(',,', ',')
192
211
  let paramnames = params ? params.split(',').map((e) => e.trim()) : null
193
212
  paramnames = paramnames ? paramnames.filter((e) => e.length > 0) : null
194
213
  // remove comments
195
214
  paramnames = paramnames ? paramnames.map((e) => e.match(/\/\*.*\*\//gs) ? e.replace(e.match(/\/\*.*\*\//gs)[0], "") : e) : null
196
-
215
+
197
216
  // add ; after newlines
198
217
  newvalue = newvalue.replaceAll(/\n/g, ";\n")
199
-
218
+
200
219
  let bind = isJSXComponent ? `${attributeName}='function(${params}){${newvalue}}'` : `${attributeName}="\$\{this.bind(function(){${newvalue}}.bind(this), ${isJSXComponent}, "${ref}", "${paramnames ? paramnames.map((e, index) => {
201
220
  if (e.length < 1) return ''
202
221
  if (e.length > 0) {
@@ -204,11 +223,15 @@ function Compiler(func, file) {
204
223
  }
205
224
  return e
206
225
  }) : ''}" ${params ? params.split(',').map((e) => e.trim()).filter(Boolean).map((e) => `,${e}`).join('') : ''})}"`
207
-
226
+
208
227
  string = string.replace(old, bind);
209
228
  }
210
229
  }
211
230
 
231
+ /**
232
+ * @search - handle attributes for html elements
233
+ * @keywords - attributes, props, html attributes
234
+ */
212
235
  let match;
213
236
  while ((match = elementRegex.exec(code)) !== null) {
214
237
  let [, element, attributes] = match;
@@ -230,34 +253,46 @@ function Compiler(func, file) {
230
253
 
231
254
  function extractOuterReturn(code) {
232
255
  // match return [...]
233
- let returns = code.match(/return\s*\<>.*\<\/>/gs)
256
+ let returns = code.match(/return\s*\<>.*\<\/>|return\s*\(.*\)/gs);
234
257
 
235
258
  return returns || [];
236
- }
237
- // throw error if return is not wrapped in <></> if return is found and not wrapped in <></> or <div></div> and not <><div></div></>
238
- if (string.match(/return\s*\<>/gs) && !string.match(/return\s*\<>.*\<\/>/gs)
259
+ }
260
+ if (string.match(/return\s*\<>|return\s*\(.*\)/gs) && !string.match(/return\s*\<>.*\<\/>|return\s*\(.*\)/gs)
239
261
  || string.match(/return\s*\<[a-zA-Z0-9_-]+.*>/gs)
240
262
  ) {
241
- throw new SyntaxError("You forgot to enclose jsx in a fragment <></> at line " + string.split(/return\s*\<[a-zA-Z0-9_-]+.*>/gs)[0].split('\n').length + ' in file ' + file)
263
+
264
+ try {
265
+ throw new SyntaxError("You forgot to enclose jsx in a fragment return <> jsx html </> or return (<> jsx html </>) at line " + string.split(/return\s*\<[a-zA-Z0-9_-]+.*>/gs)[0].split('\n').length + ' in file ' + file)
266
+ } catch (error) {
267
+ console.error(error)
268
+ process.exit(1)
269
+ }
242
270
  }
243
271
 
244
272
  let outerReturn = extractOuterReturn(string);
245
273
  let contents = "";
246
274
  let updatedContents = "";
275
+ /**
276
+ * @search - handle return [...]
277
+ * @keywords - return, return jsx, return html, return [...]
278
+ */
247
279
  outerReturn.forEach((returnStatement) => {
248
280
 
249
281
  let lines = returnStatement.split("\n");
250
282
 
251
283
  for (let i = 0; i < lines.length; i++) {
252
284
  let line = lines[i];
253
- if (line.match(/return\s*\<>/gs)) {
285
+ if (line.match(/return\s*\<>|return\s*\(/gs)) {
254
286
  continue;
255
287
  }
256
288
  contents += line + "\n";
257
289
  }
290
+ let usesBraces = returnStatement.match(/return\s*\(/gs) ? true : false;
258
291
 
259
- // Remove trailing ']'
260
- contents = contents.trim().replace(/\]$/, "");
292
+
293
+ contents = contents.trim().replace(/\]$/, "")
294
+ contents = contents.replace(/\)$/, "");
295
+ usesBraces ? !contents.includes('<>') ? contents = `<>${contents}</>` : null : null
261
296
  updatedContents = contents;
262
297
  let attributes = extractAttributes(contents);
263
298
 
@@ -283,6 +318,7 @@ function Compiler(func, file) {
283
318
  ? value = `{this.parseStyle({${value.split('{{')[1].split('}}')[0]}})}` : null
284
319
 
285
320
 
321
+
286
322
  value = `="\$${value}",`;
287
323
  string = string.replace(oldvalue, value);
288
324
 
@@ -294,34 +330,13 @@ function Compiler(func, file) {
294
330
 
295
331
  }
296
332
  } else if (value && value.new) {
297
- string = string.replace(value.old, value.new);
333
+ string = string.replace(oldvalue, value.new);
298
334
  }
299
335
  }
300
336
  });
301
337
  });
302
338
 
303
- let retursnString = [];
304
- let outerReturnString = extractOuterReturn(string);
305
-
306
- outerReturnString.forEach((returnStatement) => {
307
- let lines = returnStatement.split("\n");
308
- let code = "";
309
- for (let i = 0; i < lines.length; i++) {
310
- let line = lines[i];
311
-
312
- code += line + "\n";
313
- }
314
-
315
- code = code.trim().replace(/\<\/\>$/, "");
316
- retursnString.push(code);
317
- });
318
-
319
- retursnString.forEach((returnStatement, index) => {
320
- let old = outerReturnString[index];
321
339
 
322
- let newReturn = `${returnStatement}</>`;
323
- string = string.replace(old, newReturn);
324
- });
325
340
 
326
341
  if (comments) {
327
342
  comments.forEach((comment) => {
@@ -358,9 +373,7 @@ function Compiler(func, file) {
358
373
  valuestate = valuestate.match(regex) ? valuestate.match(regex)[0].split("useState(")[1].split(")")[0].trim() : valuestate
359
374
 
360
375
 
361
- let newState = `${varType} [${key}, ${setKey}] = this.useState('${key}', ${valuestate}
362
-
363
- `;
376
+ let newState = `${varType} [${key}, ${setKey}] = this.useState('${key}', ${valuestate}`;
364
377
  string = string.replace(line, newState);
365
378
  break;
366
379
  case line.includes("useRef") && !line.includes("import"):
@@ -405,194 +418,100 @@ function Compiler(func, file) {
405
418
  string = string.replaceAll('../src', './src')
406
419
 
407
420
  function parseComponents(body, isChild) {
408
- let componentRegex = /<([A-Z][A-Za-z0-9_-]+)([^>]*)>(.*?)<\/\1>|<([A-Z][A-Za-z0-9_-]+)([^]*?)\/>/gs;
421
+ let componentRegex = /<([A-Z][A-Za-z0-9_-]+)\s*([^>]*)>\s*([\s\S]*?)\s*<\/\1>|<([A-Z][A-Za-z0-9_-]+)([^]*?)\/>/gs;
409
422
 
410
423
  let componentMatch = body.match(componentRegex);
411
424
  let topComponent = "";
412
425
  componentMatch?.forEach(async (component) => {
413
-
426
+
414
427
  let [, element, attributes] = component;
415
-
428
+ let before = component;
429
+ component = component.trim().replace(/\s+/g, " ");
416
430
 
417
431
  !isChild ? (topComponent = component) : null;
418
- let before = component;
432
+
419
433
 
420
434
  let myChildrens = [];
421
435
 
422
436
  let name = component.split("<")[1].split(">")[0].split(" ")[0].replace("/", "");
423
- // 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'}}
424
- // grab ...( spread props )
425
- const dynamicAttributesRegex = /(\w+)(?:="([^"]*)")?(?:='([^']*)')?(?:=\{([^}]*)\})?(?:=\{(.*?)\})?(?:={([^}]*)})?(?:{([^}]*)})?|(\{(?:[^{}]+|\{(?:[^{}]+|\{[^}]*\})*\})*\})/gs;
437
+ let componentAttributes = component.split("<")[1].split(">")[0].split(" ").join(" ").replace(name, "").trim();
438
+ const dynamicAttributesRegex = /(\w+)(?:="([^"]*)")?(?:='([^']*)')?(?:=\{([^}]*)\})?(?:=\{(.*?)\})?(?:={([^}]*)})?(?:{([^}]*)})?|(?:{(?:[^{}]+|\{(?:[^{}]+|\{[^}]*\})*\})*\})|(\.{3}\{(?:[^{}]+|\{(?:[^{}]+|\{[^}]*\})*\})*\})|\$=\{(?:[^{}]+|\{(?:[^{}]+|\{[^}]*\})*\})*\}/gs;
439
+
426
440
 
427
441
 
428
442
 
429
443
 
430
-
431
444
  let props = component.match(dynamicAttributesRegex)
432
-
433
- let filteredProps = [];
434
- let isWithinComponent = false;
435
- let componentName = name
436
-
437
- for (let prop of props) {
438
-
439
- if (prop.includes(componentName)) {
440
- // If the component is encountered, start collecting props
441
- isWithinComponent = true;
442
- filteredProps.push(prop);
443
- } else if (isWithinComponent && prop.includes('=')) {
444
-
445
- if(prop.includes('${')){
446
- // if it has an object inside of it then we should just do soemting:object else we should do something: `${object}`
447
-
448
- prop = prop.replace('="', ':').replace('}"', '}')
449
- if(prop.includes('${')){
450
- prop = prop.replace('="',':')
451
- prop = prop.replace('${', '')
452
- prop = prop.replace('}', '')
453
-
454
- }
455
- if(prop.includes('="${{')){
456
- prop = prop.replace('${{', '{')
457
- prop = prop.replace('}}', '}')
458
- prop = prop.replace('="', ':')
459
- prop = prop.replace('}"', '}')
460
- }
461
-
462
- }
463
- if(prop.startsWith('={')){
464
- prop = prop.replace('={', ':`${')
465
- prop.replace('} ', '}`')
466
- }
467
-
468
- if(prop.includes('function')){
469
- // parse 'function' to function
470
- prop = prop.replace("'", '')
471
-
472
- if(prop.endsWith("}'")){
473
- prop = prop.replace("}'", '}')
474
- console
475
-
476
- }
477
-
478
- prop = prop.replace('=function', ':function')
479
- }
480
445
 
481
- filteredProps.push(prop);
482
-
483
-
484
- }
485
- else if (isWithinComponent && prop.includes('...')) {
446
+ let filteredProps = [];
447
+ let isWithinComponent = false;
448
+ let componentName = name
449
+ let currentProps = []
450
+
451
+ let $_ternaryprops = []
486
452
 
487
-
488
-
489
- // Check if spread props are within curly braces
490
- if (prop.startsWith('{') && prop.endsWith('}')) {
491
- const spreadObject = prop
492
- const hasOtherObjects = spreadObject.split(',').filter((e) => e.includes(':')).length > 0;
493
-
494
- const isValidSpread = spreadObject.includes('...');
495
-
496
- let processedSpreadObject = '';
497
- if (isValidSpread) {
498
- // Split the spreadObject by commas and process each part individually
499
- const parts = spreadObject.split(',').map((part) => {
500
- if (part.trim().startsWith('{') && part.trim().endsWith('}')) {
501
- // Handle nested spreads within the object
502
- const nestedParts = part
503
- .trim()
504
- .slice(1, -1) // Remove outer {}
505
- .split(',')
506
- .map((nestedPart) => {
507
- return nestedPart.includes('...') ? nestedPart.trim().replace(/\.\.\./, '') : `...${nestedPart.trim()}`;
508
- });
509
- return `{${nestedParts.join(',')}}`;
510
- } else {
511
- return part.includes('...') ? part.trim() : part.trim().startsWith('{') ? `...${part.trim()}` : `${part.trim()}`;
512
- }
513
- });
514
- if(!parts.join(',').includes('{(')){
515
-
516
- processedSpreadObject = `${parts.join(',')}`
517
-
518
- }else{
519
- let prop = parts.join(',')
520
- prop = prop.replaceAll('{(', '(')
521
- prop = prop.replaceAll(')}', ')')
522
- processedSpreadObject = prop
523
- }
524
-
525
-
526
- } else {
527
- // Process nested structures within the object
528
- processedSpreadObject = `{...${spreadObject}}`;
529
- }
530
-
531
- const $propsKey = `$props_${Math.random().toString(36).substring(2)}`;
532
- filteredProps.push(`${$propsKey}:${processedSpreadObject}`);
533
- }
534
- }
535
-
536
- else{
537
- isWithinComponent = false;
538
- }
539
- }
453
+ component = component.replaceAll(/\s+/g, " ");
454
+
455
+ component = component.replace(componentAttributes, '')
456
+ $_ternaryprops.forEach((prop) => {
457
+ component = component.replace(prop, '')
458
+ })
459
+
460
+ let children = component.split(`<${name}`)[1].split(`</${name}>`)[0].trim().replace(/\s+/g, " ").trim().replace(/,$/, '').replace('>', '').replace(/\/$/, '').trim()
461
+
462
+
463
+ props = filteredProps.join(',').replace(/\s+/g, " ").trim().replace(/,$/, '')
540
464
 
541
- // get inner content of <Component>inner content</Component>
542
- let children = new RegExp(`<${name}[^>]*>(.*?)<\/${name}>`, "gs").exec(component) ? new RegExp(`<${name}[^>]*>(.*?)<\/${name}>`, "gs").exec(component)[1] : null;
543
-
544
- props = filteredProps.join(',')
545
-
546
465
  let savedname = name;
547
-
548
-
466
+
467
+
549
468
 
550
469
  name = name + Math.random().toString(36).substring(2);
551
470
  if (children && children.match(componentRegex)) {
552
471
  children = parseComponents(children, true);
553
472
  childs.push({ parent: name, children: children });
554
473
  } else {
555
-
474
+
556
475
  children ? childs.push({ parent: name, children: children }) : null;
557
476
  }
558
477
 
559
478
  childs.forEach((child) => {
560
479
  if (child.parent == name) {
561
480
  let html = child.children.match(
562
- /<([a-zA-Z0-9_-]+)([^>]*)>(.*?)<\/\1>/gs
481
+ /<([A-Z][A-Za-z0-9_-]+)([^>]*)>(.*?)<\/\1>|<([A-Z][A-Za-z0-9_-]+)([^]*?)\/>/gs
563
482
  );
564
483
  if (html) {
565
484
  html = html.map((h) => h.trim().replace(/\s+/g, " ")).join(" ");
566
- let before = child.children;
567
485
  child.children = child.children.replaceAll(html, `${html}`);
568
486
  // remove duplicate quotes
569
487
  }
570
488
 
571
489
  myChildrens.push(child.children);
490
+ childs = childs.filter((e) => e.parent !== name);
572
491
  }
573
492
  });
574
493
 
575
494
 
576
495
 
577
-
578
- props = props.replaceAll(`,${savedname}`, '').replaceAll(savedname, '')
579
- if(props.startsWith(',')){
580
- props = props.replace(',', '')
581
- }
582
- props = props .replaceAll("='", ":'")
583
- .replaceAll('=`', ':`')
496
+
497
+ props = props.replaceAll(`,${savedname}`, '').replaceAll(savedname, '')
498
+ if (props.startsWith(',')) {
499
+ props = props.replace(',', '')
500
+ }
501
+ props = props.replaceAll("='", ":'")
502
+ .replaceAll('=`', ':`')
584
503
  .replaceAll('="', ':"')
585
- .replaceAll(`={\``, ':`')
586
- .replaceAll('`}', '`')
587
- .replaceAll('={', ":`")
588
-
504
+ .replaceAll('={', ':')
505
+
589
506
 
590
507
  /**
591
508
  * @memoize - memoize a component to be remembered on each render and replace the old jsx
592
509
  */
593
510
 
594
- let replace = "";
595
- replace = `\${this.memoize(this.createComponent(${savedname}, {${props}}, [\`${myChildrens.join(" ")}\`]))}`;
511
+
512
+ let replace = "";
513
+ replace = `\${this.memoize(this.createComponent(${savedname}, {${props}}, [\`${myChildrens.join('')}\`]))}`;
514
+
596
515
 
597
516
  body = body.replace(before, replace);
598
517
  });
@@ -600,12 +519,61 @@ function Compiler(func, file) {
600
519
  return body;
601
520
  }
602
521
 
522
+ string = string.replaceAll('vaderjs/client', '/vader.js')
523
+
524
+ const importRegex = /import\s*([^\s,]+|\{[^}]+\})\s*from\s*(['"])(.*?)\2/g;
525
+ const imports = string.match(importRegex);
526
+ let replaceMents = [];
603
527
 
604
528
 
605
- // replace all comments
529
+ for (let match of imports) {
530
+ let path = match.split('from')[1].trim().replace(/'/g, '').replace(/"/g, '').trim()
531
+ switch (true) {
532
+ case path && !path.includes('./') && !path.includes('/vader.js') && !path.includes('/vaderjs/client') && !path.startsWith('src') && !path.startsWith('public') && !path.includes('http') && !path.includes('https'):
533
+ let componentFolder = fs.existsSync(process.cwd() + '/node_modules/' + path) ? process.cwd() + '/node_modules/' + path : process.cwd() + '/node_modules/' + path.split('/')[0]
534
+ componentFolder = componentFolder.split(process.cwd())[1]
535
+ if (!fs.existsSync(process.cwd() + componentFolder)) {
536
+ throw new Error('Could not find ' + path + ' at ' + match + ' in file ' + file)
537
+ }
606
538
 
607
- string = string.replaceAll('vaderjs/client', '/vader.js')
608
- // replace ${... with ${
539
+ if (!fs.existsSync(process.cwd() + '/dist/src/' + componentFolder.split('/').slice(2).join('/'))) {
540
+ fs.mkdirSync(process.cwd() + '/dist/src/' + componentFolder.split('/').slice(2).join('/'), { recursive: true })
541
+ }
542
+
543
+ let baseFolder = componentFolder.split('node_modules')[1].split('/')[1]
544
+ let glp = globSync('**/**/**/**.{jsx,js}', {
545
+ cwd: process.cwd() + '/node_modules/' + baseFolder + '/',
546
+ absolute: true,
547
+ recursive: true
548
+ })
549
+ for (let file of glp) {
550
+ let text = fs.readFileSync(file, "utf8");
551
+ if (!file.endsWith('.js') && file.endsWith('.jsx')) {
552
+ text = Compiler(text, file);
553
+
554
+ }
555
+ let dest = file.split('node_modules')[1]
556
+ dest = dest.split(baseFolder)[1]
557
+ // write to dist
558
+ writer(process.cwd() + '/dist/src/' + baseFolder + dest, text)
559
+ let importname = match.split('import')[1].split('from')[0].trim()
560
+ let oldImportstring = match.split('from')[1].trim().replace(/'/g, '').replace(/"/g, '').trim()
561
+ let newImport = `/src/${baseFolder + dest}`
562
+ newImport = newImport.replaceAll('.jsx', '.js').replaceAll('\\', '/')
563
+ replaceMents.push({ match: oldImportstring, replace: newImport })
564
+ console.log(`📦 imported Node Package ${baseFolder} `)
565
+ }
566
+
567
+
568
+ break;
569
+ default:
570
+ break;
571
+ }
572
+ }
573
+
574
+ for (let replace of replaceMents) {
575
+ string = string.replaceAll(replace.match, replace.replace)
576
+ }
609
577
 
610
578
  string = string.replaceAll(/\$\{[^{]*\.{3}/gs, (match) => {
611
579
  if (match.includes('...')) {
@@ -658,7 +626,8 @@ function Compiler(func, file) {
658
626
  if (line.includes('import')) {
659
627
  // Regular expression for matching import() statements
660
628
  let asyncimportMatch = line.match(/import\s*\((.*)\)/gs);
661
- let regularimportMatch = line.match(/import\s*([a-zA-Z0-9_-]+)?\s*from\s*('(.*)'|"(.*)")|import\s*('(.*)'|"(.*)")/gs);
629
+ // handle import { Component } from 'vaderjs/runtime/vader.js'
630
+ 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);
662
631
 
663
632
  if (asyncimportMatch) {
664
633
  asyncimportMatch.forEach(async (match) => {
@@ -737,6 +706,10 @@ function Compiler(func, file) {
737
706
  case path && path.includes('.css'):
738
707
  string = string.replace(beforeimport, '')
739
708
  newImport = ``
709
+ break;
710
+ case path && !path.startsWith('./') && !path.includes('/vader.js') && !path.startsWith('src') && !path.startsWith('public') &&
711
+ path.match(/^[A-Za-z0-9_-]+$/gs) && !path.includes('http') && !path.includes('https'):
712
+
740
713
  break;
741
714
  default:
742
715
  let beforePath = path
@@ -781,6 +754,12 @@ function Compiler(func, file) {
781
754
 
782
755
  globalThis.isBuilding = false
783
756
  globalThis.isWriting = null
757
+ const glb = await glob("**/**/**/**.{jsx,js}", {
758
+ ignore: ["node_modules/**/*", "dist/**/*"],
759
+ cwd: process.cwd() + '/pages/',
760
+ absolute: true,
761
+ recursive: true
762
+ });
784
763
  async function Build() {
785
764
  globalThis.isBuilding = true
786
765
  console.log('Compiling......')
@@ -789,33 +768,7 @@ async function Build() {
789
768
  return text;
790
769
  };
791
770
 
792
- let writer = async (file, data) => {
793
- globalThis.isWriting = file
794
- switch (true) {
795
- case !fs.existsSync(file):
796
- fs.mkdirSync(file.split('/').slice(0, -1).join('/'), { recursive: true })
797
- break;
798
- }
799
- if (globalThis.isWriting !== file) {
800
- return
801
- }
802
- await fs.writeFileSync(file, data);
803
-
804
- globalThis.isWriting = null
805
- return { _written: true };
806
- };
807
-
808
-
809
- const glb = await glob("**/**/**/**.{jsx,js}", {
810
- ignore: ["node_modules/**/*", "dist/**/*"],
811
- cwd: process.cwd() + '/pages/',
812
- absolute: true,
813
- recursive: true
814
- });
815
-
816
- // Process files in the 'pages' directory
817
- let appjs = '';
818
- let hasWritten = []
771
+
819
772
  function ssg(routes = []) {
820
773
  globalThis.isBuilding = true
821
774
  console.log(`Generating html files for ${routes.length} routes`)
@@ -823,10 +776,10 @@ async function Build() {
823
776
  if (route.url.includes(':')) {
824
777
  console.log('Route ' + route.url + ' is a dynamic route and will not be generated')
825
778
  return
826
- }
827
- let equalparamroute = routes.map((e) => {
779
+ }
780
+ let equalparamroute = routes.map((e) => {
828
781
  if (e.url.includes(':')) {
829
- let url = e.url.split('/:')[0]
782
+ let url = e.url.split('/:')[0]
830
783
  if (url && route.url === url) {
831
784
  return e
832
785
  } else {
@@ -910,14 +863,14 @@ async function Build() {
910
863
  res.render(module, req, res, module.$metadata)
911
864
  })
912
865
  ${equalparamroute.length > 0 ? equalparamroute.map((e) => {
913
-
914
-
915
-
916
- return `router.get('${e.url}', async (req, res) => {
866
+
867
+
868
+
869
+ return `router.get('${e.url}', async (req, res) => {
917
870
  let module = await import('/${e.fileName.replace('.jsx', '.js')}')
918
871
  res.render(module, req, res, module.$metadata)
919
872
  })\n`
920
- }) : ''}
873
+ }) : ''}
921
874
  router.listen(3000)
922
875
 
923
876
  </script>
@@ -934,8 +887,8 @@ async function Build() {
934
887
  let port = Math.floor(Math.random() * (65535 - 49152 + 1) + 49152)
935
888
 
936
889
  const server = http.createServer((req, res) => {
937
-
938
- if (req.url === '/') {
890
+
891
+ if (req.url === '/') {
939
892
  res.writeHead(200, { 'Content-Type': 'text/html' });
940
893
  res.end(document);
941
894
  } else {
@@ -967,38 +920,54 @@ async function Build() {
967
920
 
968
921
  globalThis.listen = true;
969
922
 
970
- const browser = await puppeteer.launch({
971
- headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'],
923
+ const browser = await puppeteer.launch({
924
+ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'],
972
925
  warning: false,
973
926
  })
974
-
927
+
928
+ const browserPID = browser.process().pid
975
929
  try {
976
-
930
+
977
931
  route.url = route.url.replaceAll(/\/:[a-zA-Z0-9_-]+/gs, '')
978
932
  let page = await browser.newPage();
979
- await page.goto(`http://localhost:${port}/` , { waitUntil: 'networkidle2' });
980
- await page.waitForSelector('#root');
933
+ await page.goto(`http://localhost:${port}/`, { waitUntil: 'networkidle2' });
934
+ await page.on('console', msg => console.log('PAGE LOG:', msg.text()));
935
+ await page.on('error', err => console.log('PAGE LOG:', err));
936
+ await page.on('pageerror', err => console.log('PAGE LOG:', err));
937
+ await page.evaluate(() => {
938
+ window.onerror = function (msg, url, lineNo, columnNo, error) {
939
+ console.log(msg, url, lineNo, columnNo, error)
940
+ }
941
+ })
942
+ await page.waitForSelector('#root', { timeout: 10000 })
981
943
  await page.evaluate(() => {
982
944
  document.getElementById('meta').remove()
983
945
  document.querySelector('#isServer').innerHTML = 'window.isServer = false'
984
- if(document.head.getAttribute('prerender') === 'false'){
946
+ if (document.head.getAttribute('prerender') === 'false') {
985
947
  document.querySelector('#root').innerHTML = ''
948
+ console.log(`Disabled prerendering for ${window.location.pathname}`)
986
949
  }
987
- })
950
+ })
988
951
  const html = await page.content();
989
-
952
+
990
953
  await page.close();
991
954
  await writer(process.cwd() + '/dist/' + (route.url === '/' ? 'index.html' : `${route.url}/` + 'index.html'), html)
992
955
  await browser.close();
956
+ server.close()
957
+
993
958
  } catch (error) {
994
- console.log(error)
959
+ server.close()
995
960
  await browser.close();
996
961
  }
997
- finally{
962
+ finally {
998
963
  await browser.close();
999
964
  server.close()
1000
965
  }
1001
-
966
+ try {
967
+ process.kill(browserPID)
968
+ } catch (error) {
969
+ }
970
+
1002
971
 
1003
972
  })
1004
973
 
@@ -1006,6 +975,7 @@ async function Build() {
1006
975
  globalThis.isBuilding = false
1007
976
  clearTimeout(timeout)
1008
977
  }, 1000)
978
+ console.log(`Generated ${routes.length} html files for ${routes.length} routes`)
1009
979
  }
1010
980
 
1011
981
  globalThis.routes = []
@@ -1040,78 +1010,74 @@ async function Build() {
1040
1010
  data = Compiler(data, origin);
1041
1011
 
1042
1012
 
1043
-
1044
1013
  await writer(process.cwd() + "/dist/" + fileName.replace('.jsx', '.js'), data).then(async () => {
1045
1014
 
1046
- let { minify } = await import('terser')
1047
-
1048
- try {
1049
- let minified = await minify(data, {
1050
- toplevel: true,
1051
- ecma: 2016,
1052
- enclose: false,
1053
- module: true,
1054
- compress: true,
1055
- keep_fnames: true,
1056
1015
 
1057
- })
1058
1016
 
1059
- minified.code += `\n\n window.params = ${JSON.stringify(obj.url.split('/').filter((e) => e.includes(':')).map((e) => e.split(':')[1]))}`
1017
+ await writer(process.cwd() + "/dist/" + fileName.replace('.jsx', '.js'), data)
1060
1018
 
1061
- await writer(process.cwd() + "/dist/" + fileName.replace('.jsx', '.js'), minified.code)
1062
- } catch (error) {
1063
- console.log(error)
1064
- }
1065
1019
  })
1066
1020
 
1021
+ // configure routing for each page
1067
1022
 
1068
1023
  obj.compiledPath = process.cwd() + "/dist/pages/" + fileName.replace('.jsx', '.js')
1069
- let providerRedirects = {cloudflare: '_redirects', vercel: 'vercel.json', netlify:'_redirects'}
1070
- switch(true){
1024
+ let providerRedirects = { cloudflare: '_redirects', vercel: 'vercel.json', netlify: '_redirects' }
1025
+ switch (true) {
1071
1026
  case config && config.host && !config.host['_redirect']:
1072
1027
  let host = config.host.provider
1073
-
1074
- let provider = providerRedirects[host]
1075
- if(provider){
1076
-
1077
- let redirectFile = fs.existsSync(process.cwd() + '/dist/' + provider) ? fs.readFileSync(process.cwd() + '/dist/' + provider, 'utf8') : ''
1078
- let type = provider === '_redirects' ? 'text/plain' : 'application/json'
1079
-
1080
- let root = obj.url.includes(':') ? obj.url.split(':')[0] : obj.url
1081
- switch(true){
1028
+
1029
+ let provider = providerRedirects[host]
1030
+ if (provider) {
1031
+
1032
+ let redirectFile = null
1033
+ switch (true) {
1034
+ case provider === '_redirects':
1035
+ redirectFile = fs.existsSync(process.cwd() + '/dist/' + provider) ? fs.readFileSync(process.cwd() + '/dist/' + provider, 'utf8') : ''
1036
+ break;
1037
+ case provider === 'vercel.json':
1038
+ redirectFile = fs.existsSync(process.cwd() + '/' + provider) ? fs.readFileSync(process.cwd() + '/' + provider, 'utf8') : ''
1039
+ break;
1040
+ default:
1041
+ break;
1042
+ }
1043
+ let type = provider === '_redirects' ? 'text/plain' : 'application/json'
1044
+
1045
+ let root = obj.url.includes(':') ? obj.url.split('/:')[0] : obj.url
1046
+ switch (true) {
1082
1047
  case root === '/':
1083
1048
  break;
1084
1049
  case type === 'text/plain' && !redirectFile.includes(obj.url) && obj.url.includes(':'):
1085
1050
  let page = obj.pathname.split('/pages/')[1].replace('.jsx', '.js')
1086
1051
  redirectFile += `\n/${page} /${page} 200\n${obj.url} ${root} 200\n`
1087
1052
  !redirectFile.includes('/404') ? redirectFile += `\n/404 /404 404` : null
1088
- fs.writeFileSync(process.cwd() + '/dist/' + provider, redirectFile)
1053
+ fs.writeFileSync(process.cwd() + '/dist/' + provider, redirectFile)
1089
1054
  console.log(`Added ${obj.url} ${obj.url} 200 to ${provider}`)
1090
1055
  break;
1091
- case type === 'application/json' && !redirectFile.includes(root):
1092
- let json = JSON.parse(redirectFile) || {}
1056
+ case type === 'application/json' && !redirectFile?.includes(`${obj.url}`):
1057
+ let json = redirectFile ? JSON.parse(redirectFile) : {}
1093
1058
  let isVercel = provider === 'vercel.json' ? true : false
1094
- if(isVercel){
1059
+ if (isVercel) {
1095
1060
  json['rewrites'] = json['rewrites'] || []
1096
- json['rewrites'].push({ "source": `${root}/*`, "destination": `/${root}` })
1061
+ json['rewrites'].push({ "source": obj.url, "destination": `${root}/index.html` })
1062
+ fs.writeFileSync(process.cwd() + '/' + provider, JSON.stringify(json, null, 2))
1063
+ console.log(`Added ${obj.url} ${root}/index.html to ${provider}`)
1097
1064
  }
1098
- fs.writeFileSync(process.cwd() + '/dist/' + provider, JSON.stringify(json))
1099
- console.log(`Added ${root}/* ${root} 200 to ${provider}`)
1065
+
1100
1066
  }
1101
1067
  }
1102
1068
  break;
1103
1069
  case config && config.host && config.host['_redirect']:
1104
- let file = config.host['_redirect']
1070
+ let file = config.host['_redirect']
1105
1071
  file = file.split('./').join('')
1106
- let redirectFile = fs.existsSync(process.cwd() + '/' + file) ? fs.readFileSync(process.cwd() + '/' + file, 'utf8') : ''
1072
+ let redirectFile = fs.existsSync(process.cwd() + '/' + file) ? fs.readFileSync(process.cwd() + '/' + file, 'utf8') : ''
1107
1073
  fs.writeFileSync(process.cwd() + '/dist/' + file, redirectFile)
1108
1074
  console.log(`Using ${file} for redirects`)
1109
1075
  default:
1110
1076
  break;
1111
1077
 
1112
1078
  }
1113
-
1114
-
1079
+
1080
+
1115
1081
  globalThis.routes.push({ fileName: fileName, url: obj.url, html: '/' + (isBasePath ? 'index.html' : `${obj.url}/` + 'index.html') })
1116
1082
 
1117
1083
 
@@ -1153,18 +1119,7 @@ async function Build() {
1153
1119
  data = Compiler(data, process.cwd() + "/src/" + name);
1154
1120
 
1155
1121
  await writer(process.cwd() + "/dist/src/" + name.split('.jsx').join('.js'), data).then(async () => {
1156
- let { minify } = await import('terser')
1157
- try {
1158
- let minified = await minify(data, {
1159
- ecma: " 2016",
1160
- module: true,
1161
- compress: true,
1162
- keep_fnames: true,
1163
- })
1164
- await writer(process.cwd() + "/dist/src/" + name.replace('.jsx', '.js'), minified.code)
1165
- } catch (error) {
1166
- console.log(error)
1167
- }
1122
+ await writer(process.cwd() + "/dist/src/" + name.replace('.jsx', '.js'), data)
1168
1123
 
1169
1124
  })
1170
1125
  return
@@ -1217,6 +1172,7 @@ async function Build() {
1217
1172
  console.log(`📦 Build completed: Build Size -> ${Math.round(bundleSize / 1000)}kb`)
1218
1173
 
1219
1174
  bundleSize = 0;
1175
+
1220
1176
  return true
1221
1177
  }
1222
1178
  const s = () => {
@@ -1236,18 +1192,17 @@ const s = () => {
1236
1192
 
1237
1193
  fs.readFile(filePath, (err, data) => {
1238
1194
  if (err) {
1239
- console.log(err)
1240
1195
  res.writeHead(404, { 'Content-Type': 'text/html' });
1241
1196
  res.end(fs.existsSync(process.cwd() + '/dist/404') ? fs.readFileSync(process.cwd() + '/dist/404/index.html') : '404');
1242
1197
  } else {
1243
- const contentType = getContentType(filePath);
1198
+ const contentType = getContentType(filePath);
1244
1199
  switch (true) {
1245
1200
  case contentType === 'text/html' && globalThis.devMode:
1246
1201
  data = data.toString() + `<script type="module">
1247
- let ws = new WebSocket('ws://localhost:3000')
1202
+ let ws = new WebSocket('ws://localhost:${process.env.PORT || 3000}')
1248
1203
  ws.onmessage = (e) => {
1249
1204
  if(e.data === 'reload'){
1250
- window.rehydrate()
1205
+ window.location.reload()
1251
1206
  }
1252
1207
  }
1253
1208
  </script>
@@ -1262,13 +1217,13 @@ const s = () => {
1262
1217
 
1263
1218
  const ws = new WebSocketServer({ server });
1264
1219
  ws.on('connection', (socket) => {
1265
- console.log('Client connected');
1266
- socket.on('close', () => console.log('Client disconnected'));
1220
+ console.log('WebSocket Hydration Client connected');
1221
+ socket.on('close', () => console.log('WebSocket Hydration Client disconnected'));
1267
1222
  });
1268
1223
 
1269
1224
 
1270
1225
  function getContentType(filePath) {
1271
- let ext = ['.js', '.css', '.mjs', '.cjs', '.html', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp4', '.webm', '.ogg'].includes(path.extname(filePath)) ? path.extname(filePath) : '.html'
1226
+ let ext = ['.js', '.css', '.mjs', '.cjs', '.html', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp4', '.webm', '.ogg'].includes(path.extname(filePath)) ? path.extname(filePath) : '.html'
1272
1227
  switch (ext) {
1273
1228
  case '.js':
1274
1229
  return 'text/javascript';
@@ -1310,21 +1265,20 @@ const s = () => {
1310
1265
  let i =
1311
1266
  setInterval(() => {
1312
1267
  if (globalThis.isBuilding && globalThis.devMode) {
1313
- // reload page
1314
- console.log(globalThis.isBuilding)
1315
- console.log('Reloading page...')
1268
+
1316
1269
  ws.clients.forEach((client) => {
1317
1270
  client.send('reload')
1318
- console.log('Reloaded page')
1319
1271
  })
1320
- }
1272
+ } else {
1273
+ clearInterval(i)
1274
+ }
1321
1275
  }, 120)
1322
1276
 
1323
1277
  }
1324
1278
 
1325
1279
 
1326
1280
  switch (true) {
1327
- case process.argv.includes('--watch'):
1281
+ case process.argv.includes('--watch') && !process.argv.includes('--build') && !process.argv.includes('--serve'):
1328
1282
 
1329
1283
  globalThis.devMode = true
1330
1284
  console.log(`
@@ -1348,13 +1302,15 @@ Vader.js v1.3.3
1348
1302
  }
1349
1303
  }).on('error', (err) => console.log(err))
1350
1304
  })
1305
+ let p = process.argv[process.argv.indexOf('--watch') + 1] || process.env.PORT || 3000
1351
1306
 
1307
+ process.env.PORT = p
1352
1308
  s()
1353
1309
 
1354
1310
  globalThis.listen = true;
1355
1311
 
1356
1312
  break;
1357
- case process.argv.includes('--build'):
1313
+ case process.argv.includes('--build') && !process.argv.includes('--watch') && !process.argv.includes('--serve'):
1358
1314
  globalThis.devMode = false
1359
1315
  console.log(`
1360
1316
  Vader.js v1.3.3
@@ -1363,7 +1319,7 @@ Building to ./dist
1363
1319
  Build()
1364
1320
 
1365
1321
  break;
1366
- case process.argv.includes('--serve'):
1322
+ case process.argv.includes('--serve') && !process.argv.includes('--watch') && !process.argv.includes('--build'):
1367
1323
  let port = process.argv[process.argv.indexOf('--serve') + 1] || 3000
1368
1324
  process.env.PORT = port
1369
1325
  globalThis.devMode = false
@@ -1377,19 +1333,19 @@ url: http://localhost:${port}
1377
1333
  default:
1378
1334
  console.log(`
1379
1335
  Vader.js is a reactive framework for building interactive applications for the web built ontop of bun.js!
1380
-
1336
+
1381
1337
  Usage: vader <command>
1382
-
1338
+
1383
1339
  Commands:
1384
- --watch Watch the pages folder for changes and recompile
1385
-
1386
- --build Build the project
1387
-
1388
- --serve Serve the project on a port (default 3000)
1389
-
1390
- Learn more about vader: https://vader-js.pages.dev/
1340
+ --watch (port) Watch the pages folder for changes with hot reloading
1391
1341
 
1392
- `)
1342
+ --build Build the project to ./dist
1343
+
1344
+ --serve (400) Serve the project on a port (default 3000 or process.env.PORT)
1345
+
1346
+ Learn more about vader: https://vader-js.pages.dev/
1347
+
1348
+ `)
1393
1349
  break;
1394
1350
 
1395
1351
  }