vaderjs 1.3.3-alpha-7 → 1.3.3-alpha-9

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
@@ -2,7 +2,7 @@
2
2
  <a href="https://vader-js.pages.dev">
3
3
  <picture>
4
4
  <source media="(prefers-color-scheme: dark)" srcset="/icon.jpeg">
5
- <img src="logo.png" height="128">
5
+ <img src="./logo.png" height="128">
6
6
  </picture>
7
7
  <h1 align="center">Vader.js</h1>
8
8
  </a>
@@ -71,7 +71,7 @@ For pages that have [params] you can derive it using this.request
71
71
 
72
72
  ```jsx
73
73
  // pages/home.jsx
74
- let {Component, useState} = await import('./vader.js') // always use ./vader.js as files reference vaders main file
74
+ let {Component, useState} = await import('vaderjs/client') // this will be automatically handled by vader in compile time
75
75
  let Mycomponent = await require('./pages/mycomponent')
76
76
  class Home extends Vader {
77
77
  constructor() {
@@ -96,7 +96,7 @@ return {default:Home}
96
96
  ### State Management
97
97
 
98
98
  ```jsx
99
- let {Component, useState} = await import('./vader.js')
99
+ let {Component, useState} = await import('vaderjs/client')
100
100
 
101
101
  class MyApp extends Component{
102
102
  contructor(){
@@ -133,7 +133,7 @@ function click(event, otherparams){
133
133
  }
134
134
 
135
135
  const hello = function(event, otherparams){
136
-
136
+
137
137
  }
138
138
 
139
139
  return <>
@@ -0,0 +1,552 @@
1
+
2
+ window.params = {};
3
+ window.Vader = {
4
+ version: "1.3.2",
5
+ };
6
+
7
+ let errors = {
8
+ "SyntaxError: Unexpected token '<'": "You forgot to enclose tags in a fragment <></>",
9
+ }
10
+
11
+ const path = {
12
+ basename: (path) => {
13
+ return path.split("/").pop();
14
+ },
15
+
16
+ }
17
+
18
+ window.queryRef = (ref) => {
19
+ return document.querySelector(`[data-ref="${ref}"]`)
20
+ }
21
+ window.reinvoke = (eventtype, element) => {
22
+ const eventListener = (e) => {
23
+ return e
24
+ };
25
+
26
+ // Check if the event listener has not been added before adding it
27
+ if (!element._eventListenerAdded) {
28
+ element.addEventListener(eventtype, eventListener);
29
+
30
+ // Set the flag to indicate that the event listener has been added
31
+ element._eventListenerAdded = true;
32
+
33
+ // Trigger the event without overwriting existing data or listeners
34
+ element.dispatchEvent(new Event(eventtype));
35
+ }
36
+ };
37
+
38
+
39
+
40
+
41
+ let invokes = []
42
+ let hasran = [];
43
+ let states = {};
44
+ let mounts = [];
45
+ export const strictMount = (key, callback) => {
46
+ let interval = setInterval(() => {
47
+ if(mounts.find(mount => mount.key === key)
48
+ && !hasran.includes(callback.toString())
49
+ ){
50
+ callback();
51
+ clearInterval(interval)
52
+
53
+ hasran.push(callback.toString());
54
+ }
55
+ },0);
56
+ };
57
+
58
+ window.delegate = (event) => {
59
+ return event.detail.target
60
+ }
61
+
62
+ let components = {};
63
+
64
+ let style = document.createElement("style");
65
+ document.head.appendChild(style);
66
+
67
+ const parseStyles = async (styles, className = '') => {
68
+ let css = await fetch(styles).then((res) => res.text());
69
+ let classes = css.split("}");
70
+ let parsedClasses = {};
71
+ classes.forEach((cls) => {
72
+
73
+ let name = cls.split(".")[1];
74
+ let value = cls.split("{")[1]
75
+ let keys = value.split(";");
76
+ let newKeys = [];
77
+ keys.forEach((key) => {
78
+ if (key.includes(":")) {
79
+ let newKey = key.split(":")[0].trim();
80
+ let newValue = key.split(":")[1].trim();
81
+ newKeys.push(`${newKey}: "${newValue}"`);
82
+ }
83
+ });
84
+ value = `{${newKeys.join(",")}}`;
85
+
86
+
87
+ parsedClasses[name] = JSON.stringify(value);
88
+ });
89
+ return parsedClasses;
90
+ };
91
+
92
+
93
+ export const stylis = {
94
+ /**
95
+ * @method create
96
+ * @param {*} styles
97
+ * @returns {Object} classes
98
+ * @description This method allows you to create css classes from an object
99
+ */
100
+ create: async (/**@type {string} */ styles) => {
101
+
102
+ return await parseStyles(styles);
103
+ },
104
+ };
105
+
106
+ /**
107
+ * @method mem
108
+ * @param {Component} component
109
+ * @returns {Component} Stateless Component
110
+ * @description This method allows you to memoize a component - this means it will be intialized only once and can be reused multiple times baased on a static key
111
+ */
112
+ export const mem = (/**@type {Component}**/ component) => {
113
+ // ensure component is instance of Component
114
+ switch (true) {
115
+ case !(component instanceof Component):
116
+ throw new Error("component must be an instance of Component");
117
+ case !component.key:
118
+ throw new Error("component must have a static key");
119
+ // check if key was randomly generated
120
+ }
121
+ let key = component.key;
122
+ if (!components[key]) {
123
+ components[key] = component;
124
+ }
125
+
126
+ return components[key];
127
+ };
128
+
129
+ /**
130
+ * @method invoke
131
+ * @description This method allows you to invoke a function from its id
132
+ * @param {*} name
133
+ * @param {*} params
134
+ * @returns
135
+ * @example
136
+ * invoke(this.functions['test'], 'hello') // this will invoke the function test with the params hello
137
+ */
138
+
139
+ let functions = {};
140
+
141
+ export const invoke = (func, params) => {
142
+ let name = func.name;
143
+
144
+ window[name] = function (params) {
145
+ return func(params);
146
+ }
147
+ window[name] = window[name].bind(this);
148
+
149
+
150
+ return `${name}(${params})`;
151
+
152
+ };
153
+
154
+ /**
155
+ * Represents a component in the Vader framework.
156
+ */
157
+ export class Component {
158
+ /**
159
+ * Creates an instance of Component.
160
+ */
161
+ constructor() {
162
+ this.state = {};
163
+ this.key = null;
164
+ this.components = {};
165
+ this.mounted = false;
166
+ this.checkIFMounted();
167
+ this.currenthtml = null;
168
+ window.listeners = [];
169
+ this.functionMap = new Map();
170
+ this.freeMemoryFromFunctions();
171
+ this.memoizes = []
172
+ this.children = []
173
+ }
174
+
175
+ createComponent(/**@type {Component}**/component, props, children) {
176
+
177
+ if (!component) {
178
+ throw new Error("Component must be defined");
179
+ }
180
+ if(!props.key){
181
+ throw new Error('new components must have a key')
182
+ }
183
+ let comp = new component();
184
+
185
+ comp['props'] = props;
186
+ comp.children = children;
187
+ comp.props.children = children.join('')
188
+ comp.parentNode = this;
189
+ comp.key = props.key || null;
190
+ this.components[props.key] = comp
191
+ this.children.push(comp)
192
+ return this.components[props.key]
193
+ }
194
+ memoize(/**@type {Component}**/component){
195
+ if(!component.key){
196
+ throw new Error('Component must have a static key')
197
+ }
198
+ switch(true){
199
+ case !this.memoizes.includes(component.key):
200
+ this.memoizes.push(component.key)
201
+ this.components[component.key] = component;
202
+ break;
203
+ }
204
+
205
+ let comp = this.components[component.key];
206
+ let h = comp.render()
207
+
208
+ if(h && h.split('>,').length > 1){
209
+ h = h.replaceAll('>,', '>')
210
+ }
211
+
212
+ return `<div key="${component.key}">${h}</div>`
213
+ }
214
+ parseStyle(styles){
215
+ let css = ''
216
+ Object.keys(styles).forEach((key) => {
217
+ let value = styles[key]
218
+ key = key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase()
219
+ css += `${key}:${value};`
220
+ })
221
+ return css
222
+ }
223
+ bindMount(){
224
+ mounts.push(this)
225
+ }
226
+
227
+ /**
228
+ * Hydrates the component by updating the HTML content if it has changed.
229
+ * @private
230
+ */
231
+ hydrate() {
232
+ if (this.key) {
233
+
234
+ const el = document.querySelector(`[key="${this.key}"]`);
235
+
236
+
237
+ if (el) {
238
+
239
+
240
+ // Render the new HTML content
241
+ const newHtml = this.render();
242
+ // Compare the new HTML with the cached content
243
+ if (newHtml !== this.currentHtml) {
244
+ // Update the HTML only if it has changed
245
+ el.innerHTML = newHtml;
246
+
247
+ // Update the cached HTML content
248
+ this.currentHtml = newHtml;
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Handles an object by parsing it as JSON and evaluating it.
256
+ * @param {string} obj - The object to handle.
257
+ * @returns {*} - The evaluated object.
258
+ * @prvate
259
+ */
260
+ handleObject(obj) {
261
+ try {
262
+ obj = JSON.parse(obj);
263
+ } catch (error) {
264
+ // Handle JSON parsing error if needed
265
+ }
266
+ return eval(obj);
267
+ }
268
+
269
+ /**
270
+ * Frees memory from functions that have not been used for a certain period of time.
271
+ * @private
272
+ */
273
+ freeMemoryFromFunctions() {
274
+ setInterval(() => {
275
+ for (var [key, value] in this.functionMap) {
276
+ if (Date.now() - value.lastUsed > 1000) {
277
+ this.functionMap.delete(key);
278
+ }
279
+ }
280
+ }, 1000);
281
+ }
282
+
283
+ /**
284
+ * Binds a function to the component.
285
+ * @param {string} funcData - The function data.
286
+ * @param {string} p - The parameter.
287
+ * @param {string} ref - The reference.
288
+ * @returns {string} - A valid inline JS function call.
289
+ */
290
+ bind(funcData, d) {
291
+
292
+ const name = `func_${crypto ? crypto.getRandomValues(new Uint32Array(1))[0] : Math.random()}`;
293
+
294
+ var dynamicFunction = (params) => {
295
+ let func = new Function(`return (async (${params}) => {
296
+ console.log('called')
297
+ ${funcData}
298
+ })()`);
299
+ func = func.bind(this);
300
+ func(params);
301
+ };
302
+
303
+ dynamicFunction = dynamicFunction.bind(this);
304
+ if (!this.functionMap.has(name)) {
305
+ document.addEventListener(`call_${name}`, (e) => {
306
+
307
+ dynamicFunction();
308
+ this.functionMap.set(e.detail.name, {
309
+ lastUsed: Date.now(),
310
+ });
311
+ });
312
+ }
313
+
314
+ this.functionMap.set(name, {
315
+ lastUsed: Date.now(),
316
+ });
317
+
318
+ window.call = (name, eventdata, params) => {
319
+ document.dispatchEvent(
320
+ new CustomEvent(`call_${name}`, {
321
+ detail: { name: `call_${name}`, target: eventdata },
322
+ })
323
+ );
324
+ };
325
+
326
+ // Return a valid inline js function call
327
+ return d.jsx ? dynamicFunction : `
328
+ ((event) => {
329
+ event.target.setAttribute('data-ref', '${d.ref}');
330
+ let reference = event.target.getAttribute('data-ref');
331
+ event.target.eventData = event;
332
+ let domquery = queryRef(reference);
333
+ domquery.eventData = event;
334
+ domquery.eventData.detail.target = domquery;
335
+ call('${name}', {event:domquery.eventData}, '${d.params}')
336
+ })(event)
337
+ `;
338
+ }
339
+
340
+ /**
341
+ * Calls a function with the specified parameters. and dispatches an event.
342
+ * @param {string} func - The function name.
343
+ * @param {...*} params - The function parameters.
344
+ */
345
+ callFunction(func, isInlineJsx, ...params) {
346
+ if(!isInlineJsx && params[0] && params[0].detail){
347
+ let el = params[0].detail.target.event.target
348
+ params[0].data = el.value;
349
+ params[0] = params[0].detail.target.event
350
+ }
351
+ func = func.replace(/'/g, '');
352
+ document.dispatchEvent(new CustomEvent(func, { detail: { name: func, params: params } }));
353
+ }
354
+
355
+ /**
356
+ * Uses a function with the specified parameters.
357
+ * @param {Function} func - The function to use.
358
+ * @param {string} params - The function parameters.
359
+ * @param {boolean} [isInlineJsx=false] - Indicates if the function is an inline JSX.
360
+ * @returns {string} - The function call.
361
+ */
362
+ useFunction(func, params, isInlineJsx = false) {
363
+ const sanitizedFuncName = func.name.trim().replace(/\s+/g, '_');
364
+
365
+ if (!invokes.includes(`'${sanitizedFuncName}'${this.key}`)) {
366
+ invokes.push(`'${sanitizedFuncName}'${this.key}`);
367
+ document.addEventListener(`call_${sanitizedFuncName}_${this.key}`, (e) => {
368
+ let { name, params } = e.detail;
369
+ if (name === `call_${sanitizedFuncName}_${this.key}`) {
370
+ let isarray = Array.isArray(params);
371
+
372
+ func(...(isarray ? params : [params]));
373
+ }
374
+ });
375
+
376
+ func = func.bind(this);
377
+ }
378
+
379
+ try {
380
+ params = JSON.parse(params);
381
+ } catch (error) {
382
+ // Handle JSON parsing error if needed
383
+ }
384
+
385
+ const returnString = isInlineJsx
386
+ ? `'call_${sanitizedFuncName}_${this.key}'`
387
+ : `document.dispatchEvent(new CustomEvent('call_${sanitizedFuncName}_${this.key}', { detail: { name: 'call_${sanitizedFuncName}_${this.key}', params: ${JSON.stringify(params)} } }))`;
388
+
389
+ return returnString;
390
+ }
391
+
392
+ /**
393
+ * Uses state to dynamically update the component.
394
+ * @method useState
395
+ * @param {string} [key=null] - The auto-generated key.
396
+ * @param {*} initialState - The initial state.
397
+ * @param {Component.render} [func=null] - The render function.
398
+ * @returns {Array} - An array containing the state value and the setter function.
399
+ */
400
+ useState(key = null, initialState) {
401
+ if (!this.state[key]) {
402
+ this.state[key] = initialState;
403
+ }
404
+ const getValue = () => this.state[key];
405
+ const set = (newValue) => {
406
+ this.state[key] = newValue;
407
+ this.hydrate();
408
+ };
409
+ return [getValue, set];
410
+ }
411
+
412
+ useRef(key = null, initialState) {
413
+ if (!this.state[key]) {
414
+ this.state[key] = initialState;
415
+ }
416
+ const getValue = () => this.state[key];
417
+ const set = (newValue) => {
418
+ this.state[key] = newValue;
419
+ this.hydrate();
420
+ };
421
+ return {
422
+ bind: key,
423
+ current: () => document.querySelector(`[ref="${key}"]`) || this.state[key],
424
+ }
425
+ }
426
+
427
+ useReducer(key = null, initialState, func = null) {
428
+ const getValue = () => this.state[key];
429
+ const set = (newValue) => {
430
+
431
+ this.hydrate();
432
+ };
433
+ return [getValue, set];
434
+ }
435
+
436
+ /**
437
+ * Placeholder for content to be rendered.
438
+ * @method render
439
+ */
440
+ render() {}
441
+
442
+ /**
443
+ * Checks if the component is mounted and triggers the onMount method.
444
+ * @private
445
+ */
446
+ checkIFMounted() {
447
+ if (this.mounted) return;
448
+ let timer = setInterval(() => {
449
+ if (document.querySelector('[key="' + this.key + '"]')) {
450
+ clearInterval(timer);
451
+ this.mounted = true;
452
+ this.onMount();
453
+ }
454
+ }, 120);
455
+ }
456
+
457
+ /**
458
+ * Method that is called when the component is mounted.
459
+ * @method onMount
460
+ */
461
+ onMount() {}
462
+ }
463
+
464
+
465
+
466
+
467
+
468
+ let cache = {};
469
+ /**
470
+ * @method require
471
+ * @description Import CommonJS modules like Node.js for the browser
472
+ * @param {string} path
473
+ * @param {Boolean} noresolve - used to tell if the path should be automatically handled or manually handled - this is false by default
474
+ * @returns
475
+ */
476
+ export const require = async (path, noresolve = false) => {
477
+
478
+ if (cache[path]) {
479
+ return cache[path];
480
+ }
481
+ let file = ''
482
+ try {
483
+ file = await fetch(path).then((res) => res.text());
484
+ } catch (error) {
485
+ console.error(error)
486
+ }
487
+
488
+ file = file + `\n//# sourceURL=${path}\n`;
489
+
490
+ let filetype = path.split(".").pop();
491
+ switch (true) {
492
+ case filetype === "js":
493
+ let exports = file.match(/module.exports\s*=\s*{.*}/gs) || file.match(/exports\s*=\s*{.*}/gs);
494
+ exports = exports ? exports[0] : null;
495
+
496
+ if (exports) {
497
+ let keys = exports.split("{")[1].split("}")[0].split(",");
498
+ let returnstring = "";
499
+ keys.forEach((key) => {
500
+ key = key.trim();
501
+ returnstring += `${key},`;
502
+ });
503
+ returnstring = `return {${returnstring}}`;
504
+ file = file += returnstring;
505
+ file = file.replaceAll(exports, "");
506
+ }
507
+
508
+ return new Function(`return (async () => { ${file} })()`)();
509
+ case filetype === "jsx":
510
+ return new Function(`return (async () => { ${file} })()`)()
511
+
512
+ }
513
+ };
514
+
515
+
516
+ window.require = require;
517
+
518
+ /**
519
+ * @method useState - type
520
+ * @param {*} initialState
521
+ * @returns {Array} [value, set]
522
+ * @description Allows you to use state to dynamically update your component
523
+ */
524
+ export const useState = (initialState) => {
525
+ let value = initialState;
526
+ if (key && !states[key]) {
527
+ this.states[key] = initialState;
528
+ }
529
+ return [value, (newValue) => {}];
530
+ };
531
+
532
+ const constants = {};
533
+ let constantCounter = 0;
534
+
535
+ export const constant = (value) => {
536
+ const key = `constant_${constantCounter++}`;
537
+ if (!constants[key]) {
538
+ constants[key] = value;
539
+ }
540
+ return constants[key];
541
+ };
542
+
543
+ export default {
544
+ Component,
545
+ require,
546
+ invoke,
547
+ mem,
548
+ constant,
549
+ useState,
550
+ strictMount,
551
+ stylis,
552
+ }
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-7",
5
+ "version": "1.3.3-alpha-9",
6
6
  "bin": {
7
7
  "vader": "./vader.js"
8
8
  },
@@ -10,8 +10,7 @@
10
10
  <body>
11
11
  <div id="root"></div>
12
12
  <script type="module">
13
- import VaderRouter from './router.js'
14
-
13
+ import VaderRouter from './router.js'
15
14
  const router = new VaderRouter('/', 3000)
16
15
  window.router = router
17
16
  await import('./app.js')