vaderjs 1.3.3-alpha-111 → 1.3.3-alpha-113
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/package.json +1 -1
- package/runtime/router.js +1 -444
- package/runtime/vader.js +1 -656
- package/vader.js +3 -4
package/package.json
CHANGED
package/runtime/router.js
CHANGED
|
@@ -1,444 +1 @@
|
|
|
1
|
-
import { Component }
|
|
2
|
-
|
|
3
|
-
let middlewares = [];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @class VaderRouter
|
|
9
|
-
* @description - creates an instance of Vader Express Router
|
|
10
|
-
*
|
|
11
|
-
* @param {String} path
|
|
12
|
-
* @param {Function} handler
|
|
13
|
-
* @param {object} req request object
|
|
14
|
-
* @param {object} res response object
|
|
15
|
-
* @returns {Object} Express
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
class VaderRouter{
|
|
19
|
-
/**
|
|
20
|
-
* @constructor
|
|
21
|
-
* @param {*} basePath
|
|
22
|
-
*
|
|
23
|
-
*/
|
|
24
|
-
constructor(/**@type {string}**/basePath, /**@type {number}**/port) {
|
|
25
|
-
this.routes = [];
|
|
26
|
-
this.middlewares = [];
|
|
27
|
-
this.errorMiddlewares = [];
|
|
28
|
-
this.listeners = [];
|
|
29
|
-
|
|
30
|
-
this.basePath = basePath;
|
|
31
|
-
console.log(this.basePath)
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @method get
|
|
37
|
-
* @param {String} path
|
|
38
|
-
* @param {Function} handler
|
|
39
|
-
* @param {{a:b}} req request object
|
|
40
|
-
* @description This method is used to register a get route
|
|
41
|
-
* @returns {void}
|
|
42
|
-
* @memberof Express
|
|
43
|
-
*/
|
|
44
|
-
get(path, handler) {
|
|
45
|
-
|
|
46
|
-
this.routes.push({
|
|
47
|
-
path,
|
|
48
|
-
handler,
|
|
49
|
-
method: 'get',
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* @method use
|
|
55
|
-
* @description This method allows you to use middlewares
|
|
56
|
-
* @param {Function} middleware
|
|
57
|
-
*/
|
|
58
|
-
|
|
59
|
-
use(/* path, */ middleware) {
|
|
60
|
-
this.middlewares.push(middleware);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @method listen
|
|
65
|
-
* @param {String} port - unique id for the listener
|
|
66
|
-
* @param {Function} callback - callback function
|
|
67
|
-
* @description This method is used to start listening to the routes
|
|
68
|
-
* @returns {void}
|
|
69
|
-
*
|
|
70
|
-
*/
|
|
71
|
-
|
|
72
|
-
listen(port, callback) {
|
|
73
|
-
if(!port){
|
|
74
|
-
port = Math.random().toString(36).substring(7);
|
|
75
|
-
}
|
|
76
|
-
this.listeners.push(port);
|
|
77
|
-
if (this.listeners.length === 1) {
|
|
78
|
-
this.handleRoute(window.location.hash.length > 0 ? window.location.hash : window.location.pathname);
|
|
79
|
-
}else{
|
|
80
|
-
this.listeners.pop();
|
|
81
|
-
}
|
|
82
|
-
if (callback) {
|
|
83
|
-
callback();
|
|
84
|
-
}
|
|
85
|
-
window.onpopstate = async (e) => {
|
|
86
|
-
let route = window.location.pathname
|
|
87
|
-
console.log(route)
|
|
88
|
-
let baseRoute = `/${route.split('/')[1]}`
|
|
89
|
-
if(!routes.find((route)=>route.url.includes(baseRoute))){
|
|
90
|
-
baseRoute = '/404'
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
let html = new DOMParser().parseFromString(await fetch(baseRoute, {
|
|
94
|
-
cache: 'reload'
|
|
95
|
-
}).then((res)=>res.text()), 'text/html').documentElement
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
document.querySelector('#root').innerHTML = html.querySelector('#root').innerHTML;
|
|
99
|
-
document.title = html.querySelector('title').innerHTML;
|
|
100
|
-
document.querySelector('script[id="router"]').remove();
|
|
101
|
-
let newscript = document.createElement('script');
|
|
102
|
-
newscript.id = 'router';
|
|
103
|
-
newscript.innerHTML = html.querySelector('script[id="router"]').innerHTML;
|
|
104
|
-
newscript.setAttribute('type', 'module');
|
|
105
|
-
document.body.appendChild(newscript);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* @method extractParams
|
|
110
|
-
* @description This method is used to extract parameters from the route path
|
|
111
|
-
* @param {*} routePath
|
|
112
|
-
* @param {*} hash
|
|
113
|
-
* @returns {Object} params
|
|
114
|
-
* @memberof Express
|
|
115
|
-
*/
|
|
116
|
-
|
|
117
|
-
extractParams(routePath, hash) {
|
|
118
|
-
const routeParts = routePath.split('/');
|
|
119
|
-
const hashParts = hash.split('/');
|
|
120
|
-
const params = {};
|
|
121
|
-
routeParts.forEach((part, index) => {
|
|
122
|
-
if (part.startsWith(':')) {
|
|
123
|
-
const paramName = part.slice(1);
|
|
124
|
-
params[paramName] = hashParts[index];
|
|
125
|
-
}else if(part.startsWith('*')){
|
|
126
|
-
let array = hashParts.slice(index)
|
|
127
|
-
array.forEach((i, index)=>{
|
|
128
|
-
params[index] = i
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
return params;
|
|
133
|
-
}
|
|
134
|
-
extractQueryParams(hash){
|
|
135
|
-
|
|
136
|
-
const queryParams = hash.split('?')[1];
|
|
137
|
-
if(!queryParams){
|
|
138
|
-
return {};
|
|
139
|
-
}
|
|
140
|
-
const params = {};
|
|
141
|
-
queryParams.split('&').forEach((param)=>{
|
|
142
|
-
const [key, value] = param.split('=');
|
|
143
|
-
params[key] = value;
|
|
144
|
-
})
|
|
145
|
-
return params;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @method handleRoute
|
|
150
|
-
* @param {String} hash
|
|
151
|
-
* @description This method is used to handle the route
|
|
152
|
-
*/
|
|
153
|
-
|
|
154
|
-
handleRoute(hash) {
|
|
155
|
-
hash = hash.includes('#') ? hash.slice(1) : hash;
|
|
156
|
-
let status = 200;
|
|
157
|
-
let paramsCatchall = {}
|
|
158
|
-
let hashBefore = hash;
|
|
159
|
-
let route = this.routes.find((route) => {
|
|
160
|
-
if (route.path === hash) {
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if(hash === '' && route.path === '/'){
|
|
165
|
-
return true
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if(hash.includes('?')){
|
|
169
|
-
hash = hash.split('?')[0]
|
|
170
|
-
}
|
|
171
|
-
if (route.path.includes('*') || route.path.includes(':')) {
|
|
172
|
-
const routeParts = route.path.split('/');
|
|
173
|
-
const hashParts = hash.split('/');
|
|
174
|
-
|
|
175
|
-
if (routeParts.length !== hashParts.length && !route.path.endsWith('*')) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
for (let index = 0; index < routeParts.length; index++) {
|
|
180
|
-
const routePart = routeParts[index];
|
|
181
|
-
const hashPart = hashParts[index];
|
|
182
|
-
|
|
183
|
-
if (routePart.startsWith(':') || routePart.startsWith('*')) {
|
|
184
|
-
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (routePart !== hashPart) {
|
|
189
|
-
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const params = this.extractParams(route.path, hashBefore);
|
|
198
|
-
return Object.keys(params).length > 0;
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (!route) {
|
|
203
|
-
console.log(window.routes)
|
|
204
|
-
route = window.routes.find((errorRoute) => {
|
|
205
|
-
if (errorRoute.url.includes('/404')){
|
|
206
|
-
window.history.replaceState({}, '', '/404');
|
|
207
|
-
|
|
208
|
-
} else if (!this.error && errorRoute.url.includes('/404')){
|
|
209
|
-
return true
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
status = route ? 200 : 404;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const queryParams = this.extractQueryParams(hashBefore);
|
|
217
|
-
const params = route && route.path ? this.extractParams(route.path, hashBefore) : {};
|
|
218
|
-
|
|
219
|
-
// remove queryparams fromparam
|
|
220
|
-
Object.keys(params).forEach((key)=>{
|
|
221
|
-
params[key] = params[key].split('?') ? params[key].split('?')[0] : params[key];
|
|
222
|
-
})
|
|
223
|
-
const req = {
|
|
224
|
-
headers: {},
|
|
225
|
-
params: params,
|
|
226
|
-
query: queryParams,
|
|
227
|
-
path: hash,
|
|
228
|
-
fileUrl: window.location.href.split(window.location.origin)[1],
|
|
229
|
-
url: window.location.href,
|
|
230
|
-
method: route ? route.method : 'get',
|
|
231
|
-
pause: false,
|
|
232
|
-
timestamp: Date.now(),
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// @ts-ignore
|
|
236
|
-
window.$CURRENT_URL = req.path
|
|
237
|
-
|
|
238
|
-
// @ts-ignore
|
|
239
|
-
window.$FULL_URL = window.location.href.replace('#', '')
|
|
240
|
-
|
|
241
|
-
const res = {
|
|
242
|
-
status: status,
|
|
243
|
-
/**
|
|
244
|
-
* @method log
|
|
245
|
-
* @param {String} type
|
|
246
|
-
* @description This method is used to log the request and response
|
|
247
|
-
*/
|
|
248
|
-
log: (type) => {
|
|
249
|
-
if(type === undefined){
|
|
250
|
-
console.log(`${req.path} ${req.method} ${res.status} ${req.timestamp}`);
|
|
251
|
-
}else{
|
|
252
|
-
console.table({
|
|
253
|
-
'Request Path': req.path,
|
|
254
|
-
'Request Method': route.method,
|
|
255
|
-
'Response Status': res.status,
|
|
256
|
-
'Request Timestamp': req.timestamp,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
refresh: () => {
|
|
261
|
-
this.handleRoute(window.location.hash);
|
|
262
|
-
},
|
|
263
|
-
redirect: (path) => {
|
|
264
|
-
!path.startsWith('/') ? path = `/${path}` : null;
|
|
265
|
-
window.location.hash = `#${path}`;
|
|
266
|
-
},
|
|
267
|
-
render: async (/**@type {Component} */ component, req, res, metadata) => {
|
|
268
|
-
function isClass(funcOrClass) {
|
|
269
|
-
return typeof funcOrClass === 'function' &&
|
|
270
|
-
/^class\s/.test(Function.prototype.toString.call(funcOrClass));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
let c = new Component();
|
|
275
|
-
if(!isClass(component.default)){
|
|
276
|
-
let render = component.default.toString();
|
|
277
|
-
if(render.includes('this.key')){
|
|
278
|
-
throw new Error('Using this.key is not supported in functional components use the attribute key="a value" instead')
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
c.key = component.default.toString().split('key="')[1] ? component.default.toString().split('key="')[1].split('"')[0] : null;
|
|
284
|
-
|
|
285
|
-
let comp = {
|
|
286
|
-
key: c.key,
|
|
287
|
-
render: () => {
|
|
288
|
-
return component.default.apply(c, [req, res])
|
|
289
|
-
},
|
|
290
|
-
request: req,
|
|
291
|
-
response: res,
|
|
292
|
-
params: params,
|
|
293
|
-
queryParams: queryParams,
|
|
294
|
-
reset: c.reset.bind(c),
|
|
295
|
-
onMount: c.onMount.bind(c),
|
|
296
|
-
useState: null,
|
|
297
|
-
router: {
|
|
298
|
-
use: c.router.use.bind(c),
|
|
299
|
-
},
|
|
300
|
-
bindMount: c.bindMount.bind(c),
|
|
301
|
-
memoize: c.memoize.bind(c),
|
|
302
|
-
createComponent: c.createComponent.bind(c),
|
|
303
|
-
isChild: false,
|
|
304
|
-
useState: c.useState.bind(c),
|
|
305
|
-
parseStyle: c.parseStyle.bind(c),
|
|
306
|
-
bind: c.bind.bind(c),
|
|
307
|
-
useRef: c.useRef.bind(c),
|
|
308
|
-
useReducer: c.useReducer.bind(c),
|
|
309
|
-
onMount: c.onMount.bind(c),
|
|
310
|
-
onUnmount: c.onUnmount.bind(c),
|
|
311
|
-
hydrate: c.hydrate.bind(c),
|
|
312
|
-
}
|
|
313
|
-
c.render = comp.render;
|
|
314
|
-
c = comp;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
console.log(req)
|
|
319
|
-
}else{
|
|
320
|
-
let comp = new component.default();
|
|
321
|
-
c.state = comp.state;
|
|
322
|
-
c = comp;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
// Check if the root element exists
|
|
334
|
-
if (!document.querySelector('#root')) {
|
|
335
|
-
throw new Error('Root element not found, please add an element with id root');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
c.reset();
|
|
339
|
-
c.components = {};
|
|
340
|
-
c.request = req;
|
|
341
|
-
c.response = res;
|
|
342
|
-
if (c.router.use && !c.isChild) {
|
|
343
|
-
await new Promise(async (resolve) => {
|
|
344
|
-
if(!isClass(component.default) ){
|
|
345
|
-
await component.default.apply(c, [req, res])
|
|
346
|
-
await c.router.use(req, res)
|
|
347
|
-
switch(req.pause){
|
|
348
|
-
case true:
|
|
349
|
-
let timer = setInterval(() => {
|
|
350
|
-
if (!req.pause) {
|
|
351
|
-
clearInterval(timer);
|
|
352
|
-
resolve();
|
|
353
|
-
}else{
|
|
354
|
-
console.log('still pausing request', req.url)
|
|
355
|
-
}
|
|
356
|
-
}, 1000);
|
|
357
|
-
break;
|
|
358
|
-
case false:
|
|
359
|
-
resolve();
|
|
360
|
-
break;
|
|
361
|
-
}
|
|
362
|
-
}else if(isClass(component.default)){
|
|
363
|
-
|
|
364
|
-
await c.router.use(req, res)
|
|
365
|
-
switch(req.pause){
|
|
366
|
-
case true:
|
|
367
|
-
console.log('pausing', req.pause)
|
|
368
|
-
let timer = setInterval(() => {
|
|
369
|
-
if (!req.pause) {
|
|
370
|
-
clearInterval(timer);
|
|
371
|
-
resolve();
|
|
372
|
-
}else{
|
|
373
|
-
console.log('still pausing', req.pause)
|
|
374
|
-
}
|
|
375
|
-
}, 1000);
|
|
376
|
-
break;
|
|
377
|
-
case false:
|
|
378
|
-
resolve();
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
381
|
-
}else{
|
|
382
|
-
resolve();
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} else if (c.router.use && c.isChild) {
|
|
388
|
-
console.warn('Router.use() is not supported in child components');
|
|
389
|
-
}
|
|
390
|
-
const renderedContent = await c.render();
|
|
391
|
-
if( document.querySelector('#root').innerHTML !== renderedContent){
|
|
392
|
-
console.log('rendering')
|
|
393
|
-
document.querySelector('#root').innerHTML = renderedContent;
|
|
394
|
-
}
|
|
395
|
-
c.bindMount();
|
|
396
|
-
c.onMount();
|
|
397
|
-
|
|
398
|
-
} catch (error) {
|
|
399
|
-
console.error(error);
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
setQuery: (query) => {
|
|
403
|
-
let queryString = '';
|
|
404
|
-
Object.keys(query).forEach((key, index) => {
|
|
405
|
-
queryString += `${index === 0 ? '?' : '&'}${key}=${query[key]}`;
|
|
406
|
-
});
|
|
407
|
-
let route = window.location.hash.split('?')[0];
|
|
408
|
-
queryString = queryString.replace('/', '-').replaceAll('/', '-')
|
|
409
|
-
window.location.hash = `${route}${queryString}`;
|
|
410
|
-
},
|
|
411
|
-
send: (data) => {
|
|
412
|
-
document.querySelector('#root').innerHTML = data;
|
|
413
|
-
},
|
|
414
|
-
json: (data) => {
|
|
415
|
-
const rootElement = document.querySelector('#root');
|
|
416
|
-
|
|
417
|
-
// Clear existing content in #root
|
|
418
|
-
rootElement.innerHTML = '';
|
|
419
|
-
|
|
420
|
-
// Create a <pre> element
|
|
421
|
-
const preElement = document.createElement('pre');
|
|
422
|
-
|
|
423
|
-
// Set the text content of the <pre> element with formatted JSON
|
|
424
|
-
preElement.textContent = JSON.stringify(data, null, 2);
|
|
425
|
-
|
|
426
|
-
// Append the <pre> element to the #root element
|
|
427
|
-
rootElement.appendChild(preElement);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
};
|
|
431
|
-
middlewares.forEach((middleware) => {
|
|
432
|
-
middleware(req, res);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
route ? route.handler(req, res) : null;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
window.VaderRouter = VaderRouter;
|
|
442
|
-
|
|
443
|
-
export default VaderRouter;
|
|
444
|
-
|
|
1
|
+
import{Component}from"./vader.js";let middlewares=[];class VaderRouter{constructor(e,t){this.routes=[],this.middlewares=[],this.errorMiddlewares=[],this.listeners=[],this.basePath=e,console.log(this.basePath)}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)),this.listeners.push(e),1===this.listeners.length?this.handleRoute(window.location.pathname):this.listeners.pop(),t&&t(),window.onpopstate=async e=>{let t=window.location.pathname;console.log(t);let n=`/${t.split("/")[1]}`;routes.find((e=>e.url.includes(n)))||(n="/404");let s=(new DOMParser).parseFromString(await fetch(n,{cache:"reload"}).then((e=>e.text())),"text/html").documentElement;document.querySelector("#root").innerHTML=s.querySelector("#root").innerHTML,document.title=s.querySelector("title").innerHTML,document.querySelector('script[id="router"]').remove();let o=document.createElement("script");o.id="router",o.innerHTML=s.querySelector('script[id="router"]').innerHTML,o.setAttribute("type","module"),document.body.appendChild(o)}}extractParams(e,t){const n=e.split("/"),s=t.split("/"),o={};return n.forEach(((e,t)=>{if(e.startsWith(":")){const n=e.slice(1);o[n]=s[t]}else if(e.startsWith("*")){s.slice(t).forEach(((e,t)=>{o[t]=e}))}})),o}extractQueryParams(e){const t=e.split("?")[1];if(!t)return{};const n={};return t.split("&").forEach((e=>{const[t,s]=e.split("=");n[t]=s})),n}handleRoute(e){let t=200,n=e,s=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 n=t.path.split("/"),s=e.split("/");if(n.length!==s.length&&!t.path.endsWith("*"))return!1;for(let e=0;e<n.length;e++){const t=n[e],o=s[e];if(!t.startsWith(":")&&!t.startsWith("*")&&t!==o)return!1}return!0}const s=this.extractParams(t.path,n);return Object.keys(s).length>0}));s||(console.log(window.routes),s=window.routes.find((e=>{if(e.url.includes("/404"))window.history.replaceState({},"","/404");else if(!this.error&&e.url.includes("/404"))return!0})),t=s?200:404);const o=this.extractQueryParams(n),r=s&&s.path?this.extractParams(s.path,n):{};Object.keys(r).forEach((e=>{r[e]=r[e].split("?")?r[e].split("?")[0]:r[e]}));const i={headers:{},params:r,query:o,path:e,fileUrl:window.location.href.split(window.location.origin)[1],url:window.location.href,method:s?s.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":s.method,"Response Status":a.status,"Request Timestamp":i.timestamp})},refresh:()=>{this.handleRoute(window.location.hash)},redirect:e=>{!e.startsWith("/")&&(e=`/${e}`),window.location.hash=`#${e}`},render:async(e,t,n,s)=>{function isClass(e){return"function"==typeof e&&/^class\s/.test(Function.prototype.toString.call(e))}try{let s=new Component;if(isClass(e.default)){let t=new e.default;s.state=t.state,s=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');s.key=e.default.toString().split('key="')[1]?e.default.toString().split('key="')[1].split('"')[0]:null;let i={key:s.key,render:()=>e.default.apply(s,[t,n]),request:t,response:n,params:r,queryParams:o,reset:s.reset.bind(s),onMount:s.onMount.bind(s),useState:null,router:{use:s.router.use.bind(s)},bindMount:s.bindMount.bind(s),memoize:s.memoize.bind(s),createComponent:s.createComponent.bind(s),isChild:!1,useState:s.useState.bind(s),parseStyle:s.parseStyle.bind(s),bind:s.bind.bind(s),useRef:s.useRef.bind(s),useReducer:s.useReducer.bind(s),onMount:s.onMount.bind(s),onUnmount:s.onUnmount.bind(s),hydrate:s.hydrate.bind(s)};s.render=i.render,s=i,console.log(t)}if(!document.querySelector("#root"))throw new Error("Root element not found, please add an element with id root");s.reset(),s.components={},s.request=t,s.response=n,s.router.use&&!s.isChild?await new Promise((async o=>{if(isClass(e.default))if(isClass(e.default))switch(await s.router.use(t,n),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(s,[t,n]),await s.router.use(t,n),t.pause){case!0:let e=setInterval((()=>{t.pause?console.log("still pausing request",t.url):(clearInterval(e),o())}),1e3);break;case!1:o()}})):s.router.use&&s.isChild&&console.warn("Router.use() is not supported in child components");const i=await s.render();document.querySelector("#root").innerHTML!==i&&(console.log("rendering"),document.querySelector("#root").innerHTML=i),s.bindMount(),s.onMount()}catch(e){console.error(e)}},setQuery:e=>{let t="";Object.keys(e).forEach(((n,s)=>{t+=`${0===s?"?":"&"}${n}=${e[n]}`}));let n=window.location.hash.split("?")[0];t=t.replace("/","-").replaceAll("/","-"),window.location.hash=`${n}${t}`},send:e=>{document.querySelector("#root").innerHTML=e},json:e=>{const t=document.querySelector("#root");t.innerHTML="";const n=document.createElement("pre");n.textContent=JSON.stringify(e,null,2),t.appendChild(n)}};middlewares.forEach((e=>{e(i,a)})),s&&s.handler(i,a)}}window.VaderRouter=VaderRouter;export default VaderRouter;
|
package/runtime/vader.js
CHANGED
|
@@ -1,656 +1 @@
|
|
|
1
|
-
window.Vader = {
|
|
2
|
-
version: "1.3.3",
|
|
3
|
-
};
|
|
4
|
-
window.componentRegistry = {};
|
|
5
|
-
let errors = {
|
|
6
|
-
"SyntaxError: Unexpected token '<'": "You forgot to enclose tags in a fragment <></>",
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
let mounts = [];
|
|
10
|
-
let hasRan = []
|
|
11
|
-
/**
|
|
12
|
-
* @method strictMount
|
|
13
|
-
* @description This method allows you to await until the component is mounted before running a callback
|
|
14
|
-
* @param {*} key
|
|
15
|
-
* @param {*} callback
|
|
16
|
-
*/
|
|
17
|
-
export const strictMount = (key, callback) => {
|
|
18
|
-
let timer = setInterval(() => {
|
|
19
|
-
if (document.querySelector(`[key="${key}"]`) && !hasRan.includes(key)) {
|
|
20
|
-
clearInterval(timer);
|
|
21
|
-
callback();
|
|
22
|
-
hasRan.push(key)
|
|
23
|
-
}
|
|
24
|
-
}, 120);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Represents a component in the Vader framework.
|
|
31
|
-
*/
|
|
32
|
-
export class Component {
|
|
33
|
-
/**
|
|
34
|
-
* Creates an instance of Component.
|
|
35
|
-
*/
|
|
36
|
-
constructor() {
|
|
37
|
-
this.state = {};
|
|
38
|
-
this.key = null;
|
|
39
|
-
this.components = {};
|
|
40
|
-
this.mounted = false;
|
|
41
|
-
this.checkIFMounted();
|
|
42
|
-
this.memoizes = []
|
|
43
|
-
this.functions = []
|
|
44
|
-
this.children = []
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Parent of the current component.
|
|
49
|
-
* @type {Component}
|
|
50
|
-
*/
|
|
51
|
-
this.parentNode = {}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Request object.
|
|
55
|
-
*/
|
|
56
|
-
this.request = {
|
|
57
|
-
/**
|
|
58
|
-
* @type {string}
|
|
59
|
-
* @description The headers for the current route
|
|
60
|
-
*/
|
|
61
|
-
headers:{},
|
|
62
|
-
/**
|
|
63
|
-
* @type {string}
|
|
64
|
-
* @description The method for the current route
|
|
65
|
-
*/
|
|
66
|
-
method: "GET",
|
|
67
|
-
/**
|
|
68
|
-
* @type {string}
|
|
69
|
-
* @description params for the given route /:id/:name etc
|
|
70
|
-
*/
|
|
71
|
-
params: {},
|
|
72
|
-
/**
|
|
73
|
-
* @type {string}
|
|
74
|
-
* @description path: current route path
|
|
75
|
-
*/
|
|
76
|
-
path: "",
|
|
77
|
-
/**
|
|
78
|
-
* @type {string}
|
|
79
|
-
* @description query: query object for the current route ?name=hello -> {name: 'hello'}
|
|
80
|
-
*/
|
|
81
|
-
query: {},
|
|
82
|
-
},
|
|
83
|
-
/**
|
|
84
|
-
* @type {string}
|
|
85
|
-
* @description The response object for the current route
|
|
86
|
-
*/
|
|
87
|
-
this.response = {
|
|
88
|
-
/**
|
|
89
|
-
* @method json
|
|
90
|
-
* @description This method allows you to send json data to the client
|
|
91
|
-
* @param {*} data
|
|
92
|
-
*/
|
|
93
|
-
json: (data) => {},
|
|
94
|
-
/**
|
|
95
|
-
* @method send
|
|
96
|
-
* @description This method allows you to send text data to the client
|
|
97
|
-
* @param {*} data
|
|
98
|
-
*/
|
|
99
|
-
send: (data) => {},
|
|
100
|
-
/**
|
|
101
|
-
* @method redirect
|
|
102
|
-
* @description This method allows you to redirect the client to a new route
|
|
103
|
-
* @param {*} path
|
|
104
|
-
*/
|
|
105
|
-
redirect: (path) => {},
|
|
106
|
-
/**
|
|
107
|
-
* @method render
|
|
108
|
-
* @description render a new component to the client
|
|
109
|
-
* @param {*} Component
|
|
110
|
-
*/
|
|
111
|
-
render: async (Component) => {},
|
|
112
|
-
/**
|
|
113
|
-
* @method log
|
|
114
|
-
* @description This method is used to log the request and response
|
|
115
|
-
* @param {String} type
|
|
116
|
-
*/
|
|
117
|
-
log: (type) => {},
|
|
118
|
-
/**
|
|
119
|
-
* @method setQuery
|
|
120
|
-
* @description This method is used to set the query object for the current route
|
|
121
|
-
*/
|
|
122
|
-
setQuery: (query) => {},
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* @method router
|
|
127
|
-
* @description use router methods directly from the parent component
|
|
128
|
-
*/
|
|
129
|
-
|
|
130
|
-
this.router = {
|
|
131
|
-
/**
|
|
132
|
-
* @method use
|
|
133
|
-
* @description add a middleware to the current route
|
|
134
|
-
* @param {Function} middleware
|
|
135
|
-
* @returns {void}
|
|
136
|
-
*/
|
|
137
|
-
use: (/**@type {Function} */ middleware) => {},
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
createComponent(/**@type {Component}**/component, props, children) {
|
|
142
|
-
|
|
143
|
-
function isClass(funcOrClass) {
|
|
144
|
-
return typeof funcOrClass === 'function' &&
|
|
145
|
-
/^class\s/.test(Function.prototype.toString.call(funcOrClass));
|
|
146
|
-
}
|
|
147
|
-
let comp = !isClass(component) ? null : new component(props);
|
|
148
|
-
if (!component) {
|
|
149
|
-
throw new Error("Component must be defined");
|
|
150
|
-
}
|
|
151
|
-
let c = new Component(props);
|
|
152
|
-
if(!isClass(component)){
|
|
153
|
-
let render = component.toString();
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
c.key = component.toString().split('key="')[1] ? component.toString().split('key="')[1].split('"')[0] : null;
|
|
159
|
-
|
|
160
|
-
let comp = {
|
|
161
|
-
key: c.key,
|
|
162
|
-
render: () => {
|
|
163
|
-
return component.apply(c, [props])
|
|
164
|
-
},
|
|
165
|
-
request: this.request,
|
|
166
|
-
isChild: true,
|
|
167
|
-
response: this.response,
|
|
168
|
-
params: this.request.params,
|
|
169
|
-
queryParams: this.request.query,
|
|
170
|
-
reset: c.reset.bind(c),
|
|
171
|
-
onMount: c.onMount.bind(c),
|
|
172
|
-
useState: null,
|
|
173
|
-
router: {
|
|
174
|
-
use: c.router.use.bind(c),
|
|
175
|
-
},
|
|
176
|
-
bindMount: c.bindMount.bind(c),
|
|
177
|
-
memoize: c.memoize.bind(c),
|
|
178
|
-
createComponent: c.createComponent.bind(c),
|
|
179
|
-
isChild: false,
|
|
180
|
-
useState: c.useState.bind(c),
|
|
181
|
-
parseStyle: c.parseStyle.bind(c),
|
|
182
|
-
bind: c.bind.bind(c),
|
|
183
|
-
useRef: c.useRef.bind(c),
|
|
184
|
-
request: this.request,
|
|
185
|
-
response: this.response,
|
|
186
|
-
useReducer: c.useReducer.bind(c),
|
|
187
|
-
hydrate: c.hydrate.bind(c),
|
|
188
|
-
onUnmount: c.onUnmount.bind(c),
|
|
189
|
-
parentNoe: this,
|
|
190
|
-
}
|
|
191
|
-
c.render = comp.render;
|
|
192
|
-
c = comp;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
} else{
|
|
196
|
-
comp['props'] = props;
|
|
197
|
-
comp.children = children;
|
|
198
|
-
comp.props.children = children.join('')
|
|
199
|
-
comp.parentNode = this;
|
|
200
|
-
comp.request = this.request;
|
|
201
|
-
comp.response = this.response;
|
|
202
|
-
comp.key = props.key || null;
|
|
203
|
-
c = comp;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if(!this.components[props.key]){
|
|
210
|
-
this.components[props.key] = c
|
|
211
|
-
}
|
|
212
|
-
!this.children.includes(c) ? this.children.push(c) : null
|
|
213
|
-
return this.components[props.key]
|
|
214
|
-
}
|
|
215
|
-
reset(){
|
|
216
|
-
Object.keys(this.components).forEach((key) => {
|
|
217
|
-
this.components[key].onUnmount()
|
|
218
|
-
delete this.components[key]
|
|
219
|
-
})
|
|
220
|
-
this.state = {}
|
|
221
|
-
this.children = []
|
|
222
|
-
}
|
|
223
|
-
memoize(/**@type {Component}**/component){
|
|
224
|
-
|
|
225
|
-
switch(true){
|
|
226
|
-
case !this.memoizes.includes(component.key):
|
|
227
|
-
this.memoizes.push(component.key)
|
|
228
|
-
this.components[component.key] = component;
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
let comp = this.components[component.key];
|
|
233
|
-
comp.bindMount();
|
|
234
|
-
comp.parentNode = this;
|
|
235
|
-
comp.props = component.props;
|
|
236
|
-
comp.request = this.request;
|
|
237
|
-
comp.response = this.response;
|
|
238
|
-
comp.onMount = component.onMount.bind(component);
|
|
239
|
-
comp.onUnmount = component.onUnmount.bind(component);
|
|
240
|
-
let h = comp.render()
|
|
241
|
-
|
|
242
|
-
if(h && h.split('>,').length > 1){
|
|
243
|
-
h = h.replaceAll('>,', '>')
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return `<span key="${component.key}" >${h}</span>`
|
|
247
|
-
}
|
|
248
|
-
parseStyle(styles){
|
|
249
|
-
let css = ''
|
|
250
|
-
Object.keys(styles).forEach((key) => {
|
|
251
|
-
let value = styles[key]
|
|
252
|
-
key = key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase()
|
|
253
|
-
css += `${key}:${value};`
|
|
254
|
-
})
|
|
255
|
-
return css
|
|
256
|
-
}
|
|
257
|
-
bindMount(){
|
|
258
|
-
mounts.push(this)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Hydrates the component by updating the HTML content if it has changed.
|
|
263
|
-
* @private
|
|
264
|
-
*/
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Hydrates the component by updating the HTML content if it has changed.
|
|
268
|
-
* @private
|
|
269
|
-
*/
|
|
270
|
-
|
|
271
|
-
domDifference(oldDom, newDom) {
|
|
272
|
-
let diff = [];
|
|
273
|
-
|
|
274
|
-
for (let i = 0; i < oldDom.length; i++) {
|
|
275
|
-
let oldEl = oldDom[i];
|
|
276
|
-
let newEl = newDom[i];
|
|
277
|
-
|
|
278
|
-
if (oldEl && newEl && !oldEl.isEqualNode(newEl)) {
|
|
279
|
-
diff.push({
|
|
280
|
-
type: 'replace',
|
|
281
|
-
old: oldEl,
|
|
282
|
-
new: newEl.cloneNode(true),
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return diff;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
updateChangedElements(diff) {
|
|
291
|
-
diff.forEach((change) => {
|
|
292
|
-
switch (change.type) {
|
|
293
|
-
case 'replace':
|
|
294
|
-
change.old.parentNode.replaceChild(change.new, change.old);
|
|
295
|
-
break;
|
|
296
|
-
case 'remove':
|
|
297
|
-
change.old.remove();
|
|
298
|
-
break;
|
|
299
|
-
case 'add':
|
|
300
|
-
change.old.appendChild(change.new.cloneNode(true));
|
|
301
|
-
break;
|
|
302
|
-
default:
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
hydrate(hook) {
|
|
308
|
-
|
|
309
|
-
if (hook) {
|
|
310
|
-
let newDom = new DOMParser().parseFromString(this.render(), 'text/html').body.querySelector(`[ref="${hook}"]`);
|
|
311
|
-
let oldDom = document.querySelector(`[ref="${hook}"]`);
|
|
312
|
-
|
|
313
|
-
} else {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
let targetElement = this.key ? document.querySelector(`[key="${this.key}"]`) : null;
|
|
317
|
-
|
|
318
|
-
let newDom = new DOMParser().parseFromString(this.render(), 'text/html').body
|
|
319
|
-
newDom = document.createElement('div').appendChild(newDom)
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
!targetElement ? targetElement = document.querySelector(`[key="${newDom.attributes?.key?.value || null}"]`) : null
|
|
323
|
-
if(!targetElement){
|
|
324
|
-
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')
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
newDom.querySelectorAll('*').forEach((el) => {
|
|
329
|
-
|
|
330
|
-
if(el.hasAttribute('key') && el.innerHTML !== document.querySelector(`[key="${el.attributes.key.value}"]`).innerHTML){
|
|
331
|
-
|
|
332
|
-
document.querySelector(`[key="${el.attributes.key.value}"]`).replaceWith(el)
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
patch(oldElements, newElements) {
|
|
345
|
-
const diff = this.domDifference(oldElements, newElements);
|
|
346
|
-
|
|
347
|
-
this.updateChangedElements(diff);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Handles an object by parsing it as JSON and evaluating it.
|
|
356
|
-
* @param {string} obj - The object to handle.
|
|
357
|
-
* @returns {*} - The evaluated object.
|
|
358
|
-
* @prvate
|
|
359
|
-
*/
|
|
360
|
-
handleObject(obj) {
|
|
361
|
-
try {
|
|
362
|
-
obj = JSON.parse(obj);
|
|
363
|
-
} catch (error) {
|
|
364
|
-
// Handle JSON parsing error if needed
|
|
365
|
-
}
|
|
366
|
-
return eval(obj);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Binds a function to the component.
|
|
373
|
-
* @param {string} funcData - The function data.
|
|
374
|
-
* @param {string} p - The parameter.
|
|
375
|
-
* @param {string} ref - The reference.
|
|
376
|
-
* @returns {string} - A valid inline JS function call.
|
|
377
|
-
*/
|
|
378
|
-
bind(funcTion, jsx,ref, paramNames, ...params) {
|
|
379
|
-
ref = ref + this.key || 2022
|
|
380
|
-
|
|
381
|
-
let paramObject = {};
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
paramNames = paramNames.replace(/,,/g, ',');
|
|
385
|
-
let newparamnames = paramNames.replaceAll(',,', ',')
|
|
386
|
-
|
|
387
|
-
for(var i in params){
|
|
388
|
-
let param = params[i]
|
|
389
|
-
let paramname = newparamnames.split(',')[i]
|
|
390
|
-
paramObject[paramname] = param
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
paramNames = paramNames.replace(',,', ',');
|
|
396
|
-
let func = null
|
|
397
|
-
// at the end of spaces or new lines add a ;
|
|
398
|
-
funcTion = funcTion.split('\n').join(';')
|
|
399
|
-
try{
|
|
400
|
-
func = new Function(`event, ${paramNames}`, `
|
|
401
|
-
return (async (event, ${paramNames}) => {
|
|
402
|
-
${funcTion.toString()}
|
|
403
|
-
})(event, ${Object.keys(paramObject).join(',')})
|
|
404
|
-
`);
|
|
405
|
-
}catch(e){
|
|
406
|
-
let { message } = e;
|
|
407
|
-
console.error(`Error in function ${ref} ${message}`)
|
|
408
|
-
}
|
|
409
|
-
func = func.bind(this)
|
|
410
|
-
|
|
411
|
-
if(!this.functions.find((f) => f.ref === ref)){
|
|
412
|
-
document.addEventListener(`$dispatch_#id=${ref}`, (e) => {
|
|
413
|
-
let { name, event } = e.detail;
|
|
414
|
-
if (name === ref) {
|
|
415
|
-
let params = this.functions.find((f) => f.ref === ref).params
|
|
416
|
-
Object.keys(params).forEach((key) => {
|
|
417
|
-
if(params[key] instanceof CustomEvent){
|
|
418
|
-
delete params[key]
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
params[key] === undefined ? delete params[key] : params[key]
|
|
422
|
-
})
|
|
423
|
-
func(event, ...Object.values(params))
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
window.callFunction = (name, event) => {
|
|
430
|
-
document.dispatchEvent(new CustomEvent(`$dispatch_#id=${name}`, { detail: { name: name, params: null, event: event } }));
|
|
431
|
-
}
|
|
432
|
-
!this.functions.find((f) => f.ref === ref) ? this.functions.push({ref: ref, params: paramObject}) : null
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return jsx ? funcTion : `((event)=>{event.target.ev = event; callFunction('${ref}', event.target.ev)})(event)`;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* useState hook.
|
|
442
|
-
*
|
|
443
|
-
* @template T
|
|
444
|
-
* @param {string} key - The key for the state property.
|
|
445
|
-
* @param {T} initialState - The initial state value.
|
|
446
|
-
* @returns {[() => T, (newValue: T, hook: Function) => void]} - A tuple with getter and setter functions.
|
|
447
|
-
*/
|
|
448
|
-
useState(key, initialState) {
|
|
449
|
-
if (!this.state[key]) {
|
|
450
|
-
this.state[key] = initialState;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Get the current state value.
|
|
455
|
-
*
|
|
456
|
-
* @returns {T} The current state value.
|
|
457
|
-
*/
|
|
458
|
-
let updatedValue = () => this.state[key];
|
|
459
|
-
|
|
460
|
-
let getValue = updatedValue();
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Set a new value for the state.
|
|
464
|
-
*
|
|
465
|
-
* @param {T} newValue - The new value to set.
|
|
466
|
-
* @param {Function} hook - The hook to hydrate after setting the value.
|
|
467
|
-
*/
|
|
468
|
-
const set = (newValue, hook) => {
|
|
469
|
-
this.state[key] = newValue;
|
|
470
|
-
|
|
471
|
-
this.hydrate(hook);
|
|
472
|
-
getValue = updatedValue();
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
return [getValue, set];
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
useRef(key = null, initialState) {
|
|
483
|
-
if (!this.state[key]) {
|
|
484
|
-
this.state[key] = initialState;
|
|
485
|
-
}
|
|
486
|
-
const getValue = () => document.querySelector(`[ref="${key + this.key}"]`) || initialState;
|
|
487
|
-
const set = (newValue) => {
|
|
488
|
-
this.state[key] = newValue;
|
|
489
|
-
|
|
490
|
-
this.hydrate();
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
return {
|
|
495
|
-
bind: key + this.key,
|
|
496
|
-
current: getValue(),
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
useReducer(key = null, initialState, func = null) {
|
|
501
|
-
|
|
502
|
-
if (!this.state[key]) {
|
|
503
|
-
this.state[key] = initialState;
|
|
504
|
-
}
|
|
505
|
-
const getValue = () => this.state[key];
|
|
506
|
-
let value = getValue();
|
|
507
|
-
const set = (newValue, hook) => {
|
|
508
|
-
const nextState = func(value, newValue) ?? newValue;
|
|
509
|
-
this.state[key] = nextState;
|
|
510
|
-
this.hydrate(hook);
|
|
511
|
-
value = getValue();
|
|
512
|
-
};
|
|
513
|
-
return [getValue(), set];
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Placeholder for content to be rendered.
|
|
519
|
-
* @method render
|
|
520
|
-
*/
|
|
521
|
-
render() {}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Checks if the component is mounted and triggers the onMount method.
|
|
525
|
-
* @private
|
|
526
|
-
*/
|
|
527
|
-
checkIFMounted() {
|
|
528
|
-
let observer = new MutationObserver((mutations) => {
|
|
529
|
-
mutations.forEach((mutation) => {
|
|
530
|
-
|
|
531
|
-
if (mutation.target.querySelector(`[key="${this.key}"]`) && !this.mounted) {
|
|
532
|
-
this.onMount();
|
|
533
|
-
this.mounted = true;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
if(Array.from(mutation.removedNodes).find((node) => node.attributes && node.attributes.key && node.attributes.key.value === this.key)){
|
|
537
|
-
this.onUnmount();
|
|
538
|
-
this.reset();
|
|
539
|
-
}
|
|
540
|
-
})
|
|
541
|
-
})
|
|
542
|
-
observer.observe(document.body, {
|
|
543
|
-
childList: true,
|
|
544
|
-
subtree: true,
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* Method that is called when the component is mounted.
|
|
550
|
-
* @method onMount
|
|
551
|
-
*/
|
|
552
|
-
onMount() {}
|
|
553
|
-
/**
|
|
554
|
-
* Method that is called when the component is unmounted.
|
|
555
|
-
* @method onUnmount
|
|
556
|
-
*/
|
|
557
|
-
onUnmount() {}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* useState hook.
|
|
564
|
-
*
|
|
565
|
-
* @param {string} key - The key for the state property.
|
|
566
|
-
* @param {*} initialState - The initial state value.
|
|
567
|
-
* @returns {[*]} - A tuple with the current state value and a setter function.
|
|
568
|
-
*/
|
|
569
|
-
export const useState = (key, initialState) => {
|
|
570
|
-
if (!states[key]) {
|
|
571
|
-
states[key] = initialState;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Get the current state value.
|
|
576
|
-
*
|
|
577
|
-
* @returns {*} The current state value.
|
|
578
|
-
*/
|
|
579
|
-
let updatedValue = () => states[key];
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* Set a new value for the state.
|
|
583
|
-
*
|
|
584
|
-
* @param {*} newValue - The new value to set.
|
|
585
|
-
* @param {Function} hook - The hook to hydrate after setting the value.
|
|
586
|
-
*/
|
|
587
|
-
const set = (newValue, hook) => {
|
|
588
|
-
states[key] = newValue;
|
|
589
|
-
this.hydrate(hook);
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
return [states[key], set];
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* @method useReducer
|
|
599
|
-
* @param {*} initialState
|
|
600
|
-
* @param {*} reducer
|
|
601
|
-
* @returns {Array} [value, set]
|
|
602
|
-
*/
|
|
603
|
-
export const useReducer = (/**@type {*}**/initialState, /**@type {function}**/reducer) => {
|
|
604
|
-
return [initialState, (newValue) => {}];
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* useRef hook.
|
|
610
|
-
*
|
|
611
|
-
* @param {*} initialState - The initial state value for the ref.
|
|
612
|
-
* @returns {{ current: *, bind: string }} - An object containing the current value and a bind string.
|
|
613
|
-
*/
|
|
614
|
-
export const useRef = (initialState) => {
|
|
615
|
-
return {
|
|
616
|
-
/**
|
|
617
|
-
* @description The current value of the ref.
|
|
618
|
-
@type {*}
|
|
619
|
-
*/
|
|
620
|
-
current: initialState,
|
|
621
|
-
/**
|
|
622
|
-
* @description A unique string that can be used to bind the ref to an element.
|
|
623
|
-
* @type {HTMLElement|string}
|
|
624
|
-
*/
|
|
625
|
-
bind: '',
|
|
626
|
-
};
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
export class Link extends Component{
|
|
630
|
-
constructor(props){
|
|
631
|
-
super(props)
|
|
632
|
-
this.props = props
|
|
633
|
-
this.link = document.createElement('a')
|
|
634
|
-
}
|
|
635
|
-
render(){
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
this.link.innerHTML = this.props.children
|
|
639
|
-
this.link.setAttribute('id', this.props?.href)
|
|
640
|
-
this.link.style = this.props?.style
|
|
641
|
-
|
|
642
|
-
this.link.setAttribute('class', this.props?.class)
|
|
643
|
-
this.link.setAttribute('onclick', `window.history.pushState({}, '', '${this.props?.href}'); window.dispatchEvent(new Event('popstate'));`)
|
|
644
|
-
return this.link.outerHTML
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
export default {
|
|
650
|
-
Component,
|
|
651
|
-
useRef,
|
|
652
|
-
useReducer,
|
|
653
|
-
useState,
|
|
654
|
-
strictMount,
|
|
655
|
-
Link
|
|
656
|
-
}
|
|
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 n=setInterval((()=>{document.querySelector(`[key="${e}"]`)&&!hasRan.includes(e)&&(clearInterval(n),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,n){function s(e){return"function"==typeof e&&/^class\s/.test(Function.prototype.toString.call(e))}let r=s(e)?new e(t):null;if(!e)throw new Error("Component must be defined");let o=new Component(t);if(s(e))r.props=t,r.children=n,r.props.children=n.join(""),r.parentNode=this,r.request=this.request,r.response=this.response,r.key=t.key||null,o=r;else{e.toString();o.key=e.toString().split('key="')[1]?e.toString().split('key="')[1].split('"')[0]:null;let n={key: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),parentNoe:this};o.render=n.render,o=n}return this.components[t.key]||(this.components[t.key]=o),!this.children.includes(o)&&this.children.push(o),this.components[t.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 n=t.render();return n&&n.split(">,").length>1&&(n=n.replaceAll(">,",">")),`<span key="${e.key}" >${n}</span>`}parseStyle(e){let t="";return Object.keys(e).forEach((n=>{let s=e[n];n=n.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,"$1-$2").toLowerCase(),t+=`${n}:${s};`})),t}bindMount(){mounts.push(this)}domDifference(e,t){let n=[];for(let s=0;s<e.length;s++){let r=e[s],o=t[s];r&&o&&!r.isEqualNode(o)&&n.push({type:"replace",old:r,new:o.cloneNode(!0)})}return n}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 n=this.domDifference(e,t);this.updateChangedElements(n)}handleObject(obj){try{obj=JSON.parse(obj)}catch(e){}return eval(obj)}bind(e,t,n,s,...r){n=n+this.key||2022;let o={},i=(s=s.replace(/,,/g,",")).replaceAll(",,",",");for(var u in r){let e=r[u];o[i.split(",")[u]]=e}s=s.replace(",,",",");let a=null;e=e.split("\n").join(";");try{a=new Function(`event, ${s}`,` \n return (async (event, ${s}) => { \n ${e.toString()}\n })(event, ${Object.keys(o).join(",")}) \n `)}catch(e){let{message:t}=e;console.error(`Error in function ${n} ${t}`)}return a=a.bind(this),this.functions.find((e=>e.ref===n))||document.addEventListener(`$dispatch_#id=${n}`,(e=>{let{name:t,event:s}=e.detail;if(t===n){let e=this.functions.find((e=>e.ref===n)).params;Object.keys(e).forEach((t=>{e[t]instanceof CustomEvent&&delete e[t],void 0===e[t]?delete e[t]:e[t]})),a(s,...Object.values(e))}})),window.callFunction=(e,t)=>{document.dispatchEvent(new CustomEvent(`$dispatch_#id=${e}`,{detail:{name:e,params:null,event:t}}))},!this.functions.find((e=>e.ref===n))&&this.functions.push({ref:n,params:o}),t?e:`((event)=>{event.target.ev = event; callFunction('${n}', event.target.ev)})(event)`}useState(e,t){this.state[e]||(this.state[e]=t);let n=()=>this.state[e],s=n();return[s,(t,r)=>{this.state[e]=t,this.hydrate(r),s=n()}]}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,n=null){this.state[e]||(this.state[e]=t);const s=()=>this.state[e];let r=s();return[s(),(t,o)=>{const i=n(r,t)??t;this.state[e]=i,this.hydrate(o),r=s()}]}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,n)=>{states[e]=t,this.hydrate(n)}]};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")}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 default{Component:Component,useRef:useRef,useReducer:useReducer,useState:useState,strictMount:strictMount,Link:Link};
|
package/vader.js
CHANGED
|
@@ -823,7 +823,8 @@ async function Build() {
|
|
|
823
823
|
</html>
|
|
824
824
|
`;
|
|
825
825
|
|
|
826
|
-
|
|
826
|
+
// generate random but common ports
|
|
827
|
+
let port = Math.floor(Math.random() * (65535 - 49152 + 1) + 49152)
|
|
827
828
|
|
|
828
829
|
const server = http.createServer((req, res) => {
|
|
829
830
|
if (req.url === '/') {
|
|
@@ -1181,11 +1182,9 @@ Vader.js is a reactive framework for building interactive applications for the w
|
|
|
1181
1182
|
Usage: vader <command>
|
|
1182
1183
|
|
|
1183
1184
|
Commands:
|
|
1184
|
-
--watch Watch the pages folder for changes
|
|
1185
|
+
--watch Watch the pages folder for changes and recompile
|
|
1185
1186
|
|
|
1186
1187
|
--build Build the project
|
|
1187
|
-
|
|
1188
|
-
--serve Serve the project on a given port
|
|
1189
1188
|
Learn more about vader: https://vader-js.pages.dev/
|
|
1190
1189
|
|
|
1191
1190
|
`)
|