vaderjs 1.3.1 → 1.3.3-2124566be812

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/vader.js CHANGED
@@ -1,1272 +1,1408 @@
1
- let dom = []
2
- let states = {};
3
- let worker = new Worker(new URL('./worker.js', import.meta.url));
4
- /**
5
- * @function markdown
6
- * @param {String} content
7
- * @description Allows you to convert markdown to html
8
- */
9
- function markdown(content) {
10
-
11
- let headers = content.match(/(#+)(.*)/g);
12
- if (headers) {
13
- headers.forEach((header) => {
14
- if(header.includes('/') || header.includes('<') || header.includes('>')){
15
- return
16
-
17
- }
18
- let level = header.split('#').length;
19
- content = content.replace(header, `<h${level} class="markdown_heading">${header.replace(/#/g, '')}</h${level}>`);
20
- });
1
+ #!/usr/bin/env node
2
+ import fs from "fs";
3
+ import { glob, globSync, globStream, globStreamSync, Glob, } from 'glob'
4
+ import puppeteer from 'puppeteer';
5
+ import http from 'http'
6
+ import { WebSocketServer } from 'ws'
7
+ import { watch } from "fs";
8
+ import path from 'path'
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;
21
16
  }
17
+ if (globalThis.isWriting !== file) {
18
+ return
19
+ }
20
+ await fs.writeFileSync(file, data);
22
21
 
23
- content = content.replace(/\*\*(.*?)\*\*/g, (match, text) => {
24
- return `<b class="markdown_bold">${text}</b>`;
25
- });
26
- content = content.replace(/\*(.*?)\*/g, (match, text) => {
27
- return `<i class="markdown_italic">${text}</i>`;
28
- })
29
- content = content.replace(/`(.*?)`/g, (match, text) => {
30
- return `<code>${text}</code>`;
31
- });
32
- content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
33
- return `<a class="markdown_link" href="${url}">${text}</a>`;
34
- });
35
- content = content.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, (match, alt, src) => {
36
- return `<img class="markdown_image" src="${src}" alt="${alt}" />`;
37
- });
38
- content = content.split('\n').map((line, index, arr) => {
39
- if (line.match(/^\s*-\s+(.*?)$/gm)) {
40
- if (index === 0 || !arr[index - 1].match(/^\s*-\s+(.*?)$/gm)) {
41
- return `<ul class="markdown_unordered" style="list-style-type:disc;list-style:inside"><li>${line.replace(/^\s*-\s+(.*?)$/gm, '$1')}</li>`;
42
- } else if (index === arr.length - 1 || !arr[index + 1].match(/^\s*-\s+(.*?)$/gm)) {
43
- return `<li>${line.replace(/^\s*-\s+(.*?)$/gm, '$1')}</li></ul>`;
44
- } else {
45
- return `<li>${line.replace(/^\s*-\s+(.*?)$/gm, '$1')}</li>`;
46
- }
47
- } else {
48
- return line;
49
- }
50
- }).join('\n');
51
-
52
- content = content.split('\n').map((line, index, arr) => {
53
- if (line.match(/^\s*\d+\.\s+(.*?)$/gm)) {
54
- if (index === 0 || !arr[index - 1].match(/^\s*\d+\.\s+(.*?)$/gm)) {
55
- return `<ol class="markdown_ordered" style="list-style-type:decimal;"><li>${line.replace(/^\s*\d+\.\s+(.*?)$/gm, '$1')}</li>`;
56
- } else if (index === arr.length - 1 || !arr[index + 1].match(/^\s*\d+\.\s+(.*?)$/gm)) {
57
- return `<li>${line.replace(/^\s*\d+\.\s+(.*?)$/gm, '$1')}</li></ol>`;
58
- } else {
59
- return `<li>${line.replace(/^\s*\d+\.\s+(.*?)$/gm, '$1')}</li>`;
60
- }
61
- } else {
62
- return line;
63
- }
64
- }).join('\n');
22
+ globalThis.isWriting = null
23
+ return { _written: true };
24
+ };
25
+
26
+ let bundleSize = 0;
27
+
28
+ if (!fs.existsSync(process.cwd() + '/dist')) {
29
+ fs.mkdirSync(process.cwd() + '/dist')
30
+ }
65
31
 
66
32
 
67
- return content
68
-
33
+
34
+
35
+ if (typeof process.env.isCloudflare !== "undefined" || !fs.existsSync(process.cwd() + '/dist/index.html')) {
36
+ let htmlFile = fs.readFileSync(process.cwd() + "/node_modules/vaderjs/runtime/index.html", 'utf8')
37
+ fs.writeFileSync(process.cwd() + "/dist/index.html", htmlFile)
69
38
  }
70
39
 
71
-
72
40
 
73
- /**
74
- * @function useRef
75
- * @description Allows you to get reference to DOM element
76
- * @param {String} ref
77
- * @returns {void | Object} {current, update}
78
- */
41
+
42
+ function Compiler(func, file) {
43
+ let string = func;
44
+ let returns = []
45
+ let comments = string.match(/\{\s*\/\*.*\*\/\s*}/gs)?.map((comment) => comment.trim());
46
+
79
47
 
80
- export const useRef = (ref) => {
81
- const element = document.querySelector(`[ref="${ref}"]`);
82
- const getElement = () => element;
83
-
84
- const update = (data) => {
85
- const newDom = new DOMParser().parseFromString(data, "text/html");
86
- const newElement = newDom.body.firstChild;
87
-
88
- if (element) {
89
- // @ts-ignore
90
- const isDifferent = !newElement.isEqualNode(element);
91
- if (isDifferent) {
92
- // @ts-ignore
93
- element.parentNode.replaceChild(newElement, element);
94
- }
48
+
49
+
50
+
51
+
52
+ let childs = [];
53
+
54
+ const spreadAttributeRegex = /\s*([a-zA-Z0-9_-]+)(\s*\$\s*=\s*{{((?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*)}})/gs;
55
+ let spreadMatch;
56
+ while ((spreadMatch = spreadAttributeRegex.exec(string)) !== null) {
57
+ let [, element, spread] = spreadMatch;
58
+ let isJSXComponent = element.match(/[A-Z]/) ? true : false;
59
+ if (isJSXComponent) {
60
+ continue
61
+
95
62
  }
96
- };
63
+ let old = spread;
97
64
 
98
- return {
99
- current: getElement(),
100
- update,
101
- };
102
- };
65
+ // turn spread into attributes
66
+ spread = spread.replace(/\s*$\s*=\s*/, "");
67
+ spread = spread.replace(/{{/, "");
68
+ spread = spread.replace(/}}/, "");
69
+ spread = spread.replace(/\$\s*=\s*/, "");
70
+
71
+ // turn : into =
72
+
73
+ // do not split inner Objects ex: {color: 'red', background: {color: 'blue'}} -> {color: 'red', background: {color: 'blue'}}
74
+ let splitByCommas = spread.split(/,(?![^{]*})/g);
75
+ splitByCommas = splitByCommas.map((e) => e.trim())
76
+ splitByCommas = splitByCommas.map((e) => {
77
+ switch (true) {
78
+ case e.includes('function') || e.includes('=>'):
79
+ e = e.replace(/:(.*)/gs, '={$1}')
80
+ break;
81
+ case e.includes('style'):
82
+ e = e.replace(/:(.*)/gs, '="${this.parseStyle($1)}"')
83
+ break;
84
+ case e.includes('[') && e.includes(']'):
85
+ e = e.replace(/:(.*)/gs, '={$1.join(" ")}')
86
+ break;
87
+ default:
88
+ e = e.replace(/:(.*)/gs, '=$1')
89
+ break;
90
+ }
91
+
92
+ return e.trim()
93
+ })
94
+
95
+ let newSpread = `\t` + splitByCommas.join(' ') + `\t`
96
+
97
+ string = string.replace(old, newSpread);
98
+
99
+ }
100
+
101
+
102
+
103
+ function extractAttributes(code) {
104
+ // Match elements with opening tags
105
+ const elementRegex = /<([a-zA-Z0-9_-]+)([^>]*)>/gs;
106
+
107
+ // Match attributes in an opening tag, including those with ={}
108
+ // Match attributes in an opening tag, including those with ={...}
109
+ const attributeRegex =
110
+ /\s*([a-zA-Z0-9_-]+)(\s*=\s*("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|\{(?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*\}|(?:\([^)]*\)|\{[^}]*\}|()=>\s*(?:\{[^}]*\})?)|\[[^\]]*\]))?/gs;
103
111
 
104
- let components = [];
105
- /**
106
- * @class Component
107
- * @description Allows you to create a component
108
- * @returns {void}
109
- * @example
110
- * import { Vader } from "../../dist/vader/index.js";
111
- * export class Home extends Vader.Component {
112
- * constructor() {
113
- * super();
114
- * }
115
- * async render() {
116
- * return this.html(`
117
- * <div className="hero p-5">
118
- * <h1>Home</h1>
119
- * </div>
120
- * `);
121
- * }
122
- * }
123
- */
124
- export class Component {
125
- constructor() {
126
- this.states = {};
127
- //@ts-ignore
128
- this.name = this.constructor.name;
129
- this.executedEffects = {};
130
- this.storedProps = {};
131
- this.componentMounted = false;
132
- this.hasMounted = false;
133
- this.$_signal_subscribers = [];
134
- /**
135
- * @property {Array} $_signal_subscribers_ran
136
- * @description Allows you to keep track of signal subscribers
137
- * @private
138
- */
139
- this.$_signal_subscribers_ran = [];
140
- this.effects = {};
141
- this.$_useStore_subscribers = [];
142
- this.init();
143
- this.Componentcontent = null;
144
- this.$_signal_dispatch_event = new CustomEvent("signalDispatch", {
145
- detail: {
146
- hasUpdated: false,
147
- state: null,
148
- },
149
- });
150
- this.snapshots = [];
151
- /**
152
- * @property {Object} dom
153
- * @description Allows you to get reference to DOM element
154
- * @returns {void | HTMLElement}
155
- *
156
- */
157
- this.dom = []
158
112
 
113
+
114
+
115
+ const functionAttributeRegex = /\s*([a-zA-Z0-9_-]+)(\s*=\s*{((?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\})*)*\})*)*)})/gs;
116
+
117
+ let attributesList = [];
118
+
119
+ let spreadAttributes = [];
120
+ let spreadMatch;
159
121
  /**
160
- * @property {Boolean} cfr
161
- * @description Allows you to compile html code on the fly - client fly rendering
162
- *
122
+ * @search - handle spread for html elements
123
+ * @keywords - spread, spread attributes, spread props, spread html attributes
163
124
  */
164
- this.cfr = false
125
+
126
+
165
127
  /**
166
- * @property {Boolean} worker
167
- * @description Allows you to use a web worker to compile html code on the fly - client fly rendering
168
-
128
+ * @search - handle function parsing for html elements
129
+ * @keywords - function, function attributes, function props, function html attributes
130
+ *
169
131
  */
170
-
171
- }
132
+ let functionAttributes = [];
133
+ let functionMatch;
134
+ while ((functionMatch = functionAttributeRegex.exec(code)) !== null) {
172
135
 
173
- /**
174
- * @method adapter
175
- * @description Allows you to create an adapter - this is used to create custom logic
176
- *
177
- *
178
- */
179
- adapter() {
180
- return
181
- }
182
- init() {
183
- this.registerComponent();
184
-
185
- }
136
+ let [, attributeName, attributeValue] = functionMatch;
137
+ let attribute = {};
186
138
 
187
- registerComponent() {
188
- components.push(this);
189
- }
139
+ if (attributeValue && attributeValue.includes("=>") || attributeValue && attributeValue.includes("function")
140
+ && !spreadFunctions.includes(attributeValue)
141
+ ) {
142
+
143
+ let ref = Math.random().toString(36).substring(2).split('').filter((e) => !Number(e)).join('')
144
+ let old = `${attributeName}${attributeValue}`
145
+
146
+ let elementMatch = string.match(/<([a-zA-Z0-9_-]+)([^>]*)>/gs);
147
+ let isJSXComponent = false;
148
+ elementMatch.forEach((element) => {
149
+ element = element.trim().replace(/\s+/g, " ");
150
+ if (element.includes(attributeName)) {
151
+ let elementTag = element
152
+ .split("<")[1]
153
+ .split(">")[0]
154
+ .split(" ")[0];
155
+ isJSXComponent = elementTag.match(/^[A-Z]/) ? true : false;
156
+ }
157
+ });
158
+ // add ; after newlines
190
159
 
191
- /**
192
- * @method setState
193
- * @description Allows you to set state
194
- * @param {String} key
195
- * @param {*} value
196
- * @returns {void}
197
- * @example
198
- * this.setState('count', 1)
199
- * */
200
- setState(key, value) {
201
- this.states[key] = value;
202
- this.updateComponent();
203
- }
204
- /**
205
- * @method componentUnmount
206
- * @description Allows you to run code after component has unmounted
207
- * @type {VoidFunction}
208
- * @returns {void}
209
- */
210
- unmount() {
211
- this.componentMounted = false;
212
- this.componentWillUnmount();
213
- // @ts-ignore
214
- document.querySelector(`[data-component="${this.name}"]`).remove();
215
- if (!document.querySelector(`[data-component="${this.name}"]`)) {
216
- components = components.filter(
217
- (component) => component.name !== this.name
218
- );
219
- }
220
- }
221
160
 
222
- /**
223
- * @method componentUpdate
224
- * @description Allows you to run code after component has updated
225
- * @param {Object} prev_state
226
- * @param {Object} prev_props
227
- * @param {Object} snapshot
228
- * @returns {void}
229
- * @example
230
- * componentUpdate(prev_state, prev_props, snapshot) {
231
- * console.log(prev_state, prev_props, snapshot)
232
- * }
233
- * */
234
- componentUpdate(prev_state, prev_props, snapshot) {}
235
- /**
236
- * @method componentDidMount
237
- * @description Allows you to run code after component has mounted
238
- */
239
- componentDidMount() {}
161
+ let newvalue = attributeValue.includes('=>') ? attributeValue.split("=>").slice(1).join("=>").trim() : attributeValue.split("function").slice(1).join("function").trim()
240
162
 
241
- /**
242
- * @method componentWillUnmount
243
- * @description Allows you to run code before component unmounts
244
- * @type {VoidFunction}
245
- * @returns {void}
246
- */
247
- componentWillUnmount() {}
248
163
 
249
- /**
250
- * @method signal
251
- * @description Allows you to create a signal
252
- * @param {String} key
253
- * @param {any} initialState
254
- * @returns {Object} {subscribe, cleanup, dispatch, call, set, get}
255
- * @example
256
- * let signal = this.signal('count', 0);
257
- * signal.subscribe((value) => {
258
- * console.log(value)
259
- * }, false) // false means it will run every time
260
- * signal.subscribe((value) => {
261
- * console.log(value)
262
- * }, true) // true means it will run once
263
- * signal.call() // this will call all subscribers
264
- * signal.set(1) // this will set the value of the signal
265
- * signal.get() // this will get the value of the signal
266
- * signal.cleanup() // this will remove all subscribers
267
- */
268
- signal = (key, initialState) => {
269
- let hasCaller = false;
270
- let [state, setState] = this.useState(key, initialState, () => {
271
- if (this.$_signal_subscribers.length > 0) {
272
- for (var i = 0; i < this.$_signal_subscribers.length; i++) {
273
- if (!hasCaller) {
274
- if (
275
- this.$_signal_subscribers[i].runonce &&
276
- // @ts-ignore
277
- this.$_signal_subscribers_ran.includes(
278
- this.$_signal_subscribers[i]
279
- )
280
- ) {
281
- break;
282
- } else {
283
- this.$_signal_subscribers[i].function(state);
284
- this.$_signal_subscribers_ran.push(this.$_signal_subscribers[i]);
285
- return;
286
- }
164
+
165
+ newvalue = newvalue.trim();
166
+
167
+ //remove starting {
168
+ newvalue = newvalue.replace("{", "")
169
+
170
+
171
+
172
+ let params = attributeValue
173
+ .split("=>")[0]
174
+ .split("(")[1]
175
+ .split(")")[0]
176
+ .trim();
177
+
178
+ // remove comments
179
+ params = params.match(/\/\*.*\*\//gs)
180
+ ? params.replace(params.match(/\/\*.*\*\//gs)[0], "")
181
+ : params;
182
+ // split first {}
183
+ newvalue = newvalue.trim();
184
+
185
+
186
+
187
+ newvalue = newvalue.replace(/}\s*$/, '');
188
+
189
+
190
+
191
+ newvalue = newvalue.trim();
192
+
193
+ // remmove trailing }
194
+
195
+ newvalue = newvalue.trim();
196
+ newvalue = newvalue.replace(/}\s*$/, '');
197
+
198
+
199
+
200
+
201
+ newvalue = newvalue.replaceAll(',,', ',')
202
+ let paramnames = params ? params.split(',').map((e) => e.trim()) : null
203
+ paramnames = paramnames ? paramnames.filter((e) => e.length > 0) : null
204
+ // remove comments
205
+ paramnames = paramnames ? paramnames.map((e) => e.match(/\/\*.*\*\//gs) ? e.replace(e.match(/\/\*.*\*\//gs)[0], "") : e) : null
206
+
207
+ // add ; after newlines
208
+ newvalue = newvalue.replaceAll(/\n/g, ";\n")
209
+
210
+ let bind = isJSXComponent ? `${attributeName}='function(${params}){${newvalue}}'` : `${attributeName}="\$\{this.bind(function(){${newvalue}}.bind(this), ${isJSXComponent}, "${ref}", "${paramnames ? paramnames.map((e, index) => {
211
+ if (e.length < 1) return ''
212
+ if (e.length > 0) {
213
+ index == 0 ? e : ',' + e
287
214
  }
288
- }
289
- } else {
290
- this.$_signal_dispatch_event.detail.hasUpdated = true;
291
- this.$_signal_dispatch_event.detail.state = state;
292
- window.dispatchEvent(this.$_signal_dispatch_event);
215
+ return e
216
+ }) : ''}" ${params ? params.split(',').map((e) => e.trim()).filter(Boolean).map((e) => `,${e}`).join('') : ''})}"`
217
+
218
+ string = string.replace(old, bind);
293
219
  }
294
- });
220
+ }
221
+
295
222
  /**
296
- * @function $_signal_subscribe
297
- * @description Allows you to subscribe to a signal
298
- * @param {*} fn
299
- * @param {*} runonce
300
- * @returns {void}
301
- *
223
+ * @search - handle attributes for html elements
224
+ * @keywords - attributes, props, html attributes
302
225
  */
303
- this.$_signal_subscribe = (fn, runonce) => {
304
- this.$_signal_subscribers.push({
305
- function: fn,
306
- runonce: runonce,
307
- });
308
- };
309
- this.$_signal_cleanup = (fn) => {
310
- this.$_signal_subscribers = this.$_signal_subscribers.filter(
311
- (subscriber) => subscriber.function !== fn
312
- );
313
- };
314
- this.$_signal_dispatch = () => {
315
- for (var i = 0; i < this.$_signal_subscribers.length; i++) {
316
- if (
317
- this.$_signal_subscribers[i].runonce &&
318
- // @ts-ignore
319
- this.$_signal_subscribers_ran.includes(this.$_signal_subscribers[i])
320
- ) {
321
- break;
322
- } else {
323
- this.$_signal_subscribers[i].function(state);
324
- this.$_signal_subscribers_ran.push(this.$_signal_subscribers[i]);
325
- }
226
+ let match;
227
+ while ((match = elementRegex.exec(code)) !== null) {
228
+ let [, element, attributes] = match;
229
+
230
+ let attributesMatch;
231
+ let elementAttributes = {};
232
+
233
+ while ((attributesMatch = attributeRegex.exec(attributes)) !== null) {
234
+ let [, attributeName, attributeValue] = attributesMatch;
235
+
236
+ elementAttributes[attributeName] = attributeValue || null;
326
237
  }
327
- };
328
- this.$_signal_get = () => {
329
- return state;
330
- };
331
- this.$_signal_call = () => {
332
- hasCaller = true;
333
- // @ts-ignore
334
- this.$_signal_dispatch();
335
- };
336
- /**
337
- * @function $_signal_set
338
- * @description Allows you to set the value of a signal
339
- * @param {*} detail
340
- */
341
- this.$_signal_set = (detail) => {
342
- setState(detail);
343
- };
344
238
 
345
- return {
346
- /**
347
- * @function subscribe
348
- * @description Allows you to subscribe to a signal
349
- * @param {*} fn
350
- * @param {*} runonce
351
- */
352
- subscribe: this.$_signal_subscribe,
353
- /**
354
- * @function cleanup
355
- * @description Allows you to cleanup a signal
356
- * @param {*} fn
357
- * @returns {null}
358
- */
359
- cleanup: this.$_signal_cleanup,
360
- /**
361
- * @function dispatch
362
- * @description Allows you to dispatch a signal
363
- * @returns {null}
364
- */
365
- dispatch: this.$_signal_dispatch,
366
- /**
367
- * @function call
368
- * @description Allows you to call a signal
369
- * @returns {null}
370
- */
371
- call: this.$_signal_call,
372
- /**
373
- * @function set
374
- * @description Allows you to set the value of a signal
375
- * @param {*} detail
376
- * @returns {null}
377
- */
378
- set: this.$_signal_set,
379
- /**
380
- * @function get
381
- * @readonly
382
- * @description Allows you to get the value of a signal
383
- * @returns {any}
384
- */
385
- get: this.$_signal_get,
386
- };
387
- };
388
- /**
389
- * @method useAuth
390
- * @description Allows you to create an auth object
391
- * @param {Object} options
392
- * @param {Array} options.rulesets
393
- * @param {Object} options.user
394
- * @returns {Object} {can, hasRole, canWithRole, assignRule, revokeRule, canAnyOf, canAllOf, canGroup}
395
- * @example
396
- * let auth = this.useAuth({
397
- * rulesets: [
398
- * {
399
- * action: 'create',
400
- * condition: (user) => {
401
- * return user.role === 'admin'
402
- * }
403
- * }
404
- * ],
405
- * user: {
406
- * role: 'admin'
407
- * }
408
- * })
409
- * auth.can('create') // true
410
- */
411
- useAuth(options) {
412
- /**@type {Array}**/
413
- let rules = options.rulesets;
414
- if (!rules) {
415
- throw new Error("No rulesets provided");
239
+ attributesList.push({ element, attributes: elementAttributes });
416
240
  }
417
- /**@type {Object}**/
418
- let user = options.user;
419
- let auth = {
420
- can: (action) => {
421
- let can = false;
422
- rules.forEach((rule) => {
423
- if (rule.action === action) {
424
- if (rule.condition(user)) {
425
- can = true;
426
- }
427
- }
428
- });
429
- return can;
430
- },
431
- /**
432
- * @function hasRole
433
- * @description Allows you to check if user has a role
434
- * @param {String} role
435
- * @returns {Boolean}
436
- */
437
- hasRole: (role) => {
438
- return user.role && user.role.includes(role);
439
- },
440
- /**
441
- * @function canWithRole
442
- * @param {String} action
443
- * @param {String} role
444
- * @returns
445
- */
446
- canWithRole: (action, role) => {
447
- return auth.can(action) && auth.hasRole(role);
448
- },
449
- assignRule: (rule) => {
450
- if (
451
- !rules.some((existingRule) => existingRule.action === rule.action)
452
- ) {
453
- rules.push(rule);
454
- }
455
- },
456
- revokeRule: (action) => {
457
- rules = rules.filter((rule) => rule.action !== action);
458
- },
459
- canAnyOf: (actions) => {
460
- return actions.some((action) => auth.can(action));
461
- },
462
- canAllOf: (actions) => {
463
- return actions.every((action) => auth.can(action));
464
- },
465
- canGroup: (actions, logicalOperator = "any") => {
466
- return logicalOperator === "any"
467
- ? auth.canAnyOf(actions)
468
- : auth.canAllOf(actions);
469
- },
470
- };
471
- return auth;
241
+
242
+ return attributesList;
472
243
  }
473
- /**
474
- * @method useReducer
475
- * @description Allows you to create a reducer
476
- * @param {*} key
477
- * @param {*} reducer
478
- * @param {*} initialState
479
- * @url - useReducer works similarly to - https://react.dev/reference/react/useReducer
480
- * @returns {Array} [state, dispatch]
481
- * @example
482
- * let [count, setCount] = this.useReducer('count', (state, action) => {
483
- * switch (action.type) {
484
- * case 'increment':
485
- * return state + 1
486
- * case 'decrement':
487
- * return state - 1
488
- * default:
489
- * throw new Error()
490
- * }
491
- * }, 0)
492
- * setCount({type: 'increment'})
493
- */
494
- useReducer(key, reducer, initialState) {
495
- if (!this.states[key]) {
496
- this.states[key] = initialState;
244
+
245
+ function extractOuterReturn(code) {
246
+ // match return [...]
247
+ let returns = code.match(/return\s*\<>.*\<\/>|return\s*\(.*\)/gs);
248
+
249
+ return returns || [];
250
+ }
251
+ if (string.match(/return\s*\<>|return\s*\(.*\)/gs) && !string.match(/return\s*\<>.*\<\/>|return\s*\(.*\)/gs)
252
+ || string.match(/return\s*\<[a-zA-Z0-9_-]+.*>/gs)
253
+ ) {
254
+
255
+ try {
256
+ 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)
257
+ } catch (error) {
258
+ console.error(error)
259
+ process.exit(1)
497
260
  }
498
- return [
499
- this.states[key],
500
- /**
501
- * @function dispatch
502
- * @description Allows you to dispatch a reducer
503
- * @param {*} action
504
- * @returns {void}
505
- */
506
- (action) => {
507
- this.states[key] = reducer(this.states[key], action);
508
- this.updateComponent();
509
- },
510
- ];
511
261
  }
512
262
 
513
-
514
- runEffects() {
515
- Object.keys(this.effects).forEach((component) => {
516
- this.effects[component].forEach((effect) => {
517
- if (!this.executedEffects[effect]) {
518
- effect();
519
- this.executedEffects[effect] = true;
520
- }
521
- });
522
- });
523
- }
524
-
263
+ let outerReturn = extractOuterReturn(string);
264
+ let contents = "";
265
+ let updatedContents = "";
525
266
  /**
526
- * @method useSyncStore
527
- * @description Allows you to create a store
528
- * @param {String} storeName
529
- * @param {*} initialState
530
- * @returns {Object} {getField, setField, subscribe, clear}
531
- * @example
532
- * let store = this.useSyncStore('store', {count: 0})
533
- * store.setField('count', 1)
534
- * store.getField('count') // 1
535
- *
267
+ * @search - handle return [...]
268
+ * @keywords - return, return jsx, return html, return [...]
536
269
  */
537
- useSyncStore(storeName, initialState) {
538
- let [storedState, setStoredState] = this.useState(
539
- storeName,
540
- initialState ||
541
- // @ts-ignore
542
- localStorage.getItem(`$_vader_${storeName}`, (s) => {
543
- localStorage.setItem(`$_vader_${storeName}`, JSON.stringify(s));
544
- this.$_useStore_subscribers.forEach((subscriber) => {
545
- subscriber(s);
546
- });
547
- }) ||
548
- {},
270
+ outerReturn.forEach((returnStatement) => {
549
271
 
550
- );
272
+ let lines = returnStatement.split("\n");
551
273
 
552
- const getField = (fieldName) => {
553
- return storedState[fieldName];
554
- };
555
- const setField = (fieldName, value) => {
556
- const newState = { ...storedState, [fieldName]: value };
557
- setStoredState(newState);
558
- };
559
- const subscribe = (subscriber) => {
560
- return this.$_useStore_subscribers.push(subscriber);
561
- };
274
+ for (let i = 0; i < lines.length; i++) {
275
+ let line = lines[i];
276
+ if (line.match(/return\s*\<>|return\s*\(/gs)) {
277
+ continue;
278
+ }
279
+ contents += line + "\n";
280
+ }
281
+ let usesBraces = returnStatement.match(/return\s*\(/gs) ? true : false;
562
282
 
563
- const clear = (fieldName) => {
564
- let newState = storedState;
565
- delete newState[fieldName];
566
- setStoredState(newState);
567
- };
568
- return {
569
- getField,
570
- setField,
571
- subscribe,
572
- clear,
573
- };
574
- }
575
- /**
576
- * @method useState
577
- * @description Allows you to create a state
578
- * @param {String} key
579
- * @param {*} initialValue
580
- * @param {*} callback
581
- * @url - useState works similarly to - https://react.dev/reference/react/useState
582
- * @returns {Array} [state, setState]
583
- * @example
584
- * let [count, setCount] = this.useState('count', 0, () => {
585
- * console.log('count has been updated')
586
- * })
587
- *
588
- * setCount(count + 1)
589
- */
590
- useState(key, initialValue, callback = null) {
591
-
592
- if(!this.states[key]){
593
- this.states[key] = initialValue;
594
- }
595
-
596
-
597
- return [
598
- this.states[key],
599
- /**
600
- * @function setState
601
- * @description Allows you to set state
602
- * @param {*} value
603
- * @returns {void}
604
- */
605
- (value) => {
606
- this.states[key] = value;
607
- this.updateComponent();
608
- // @ts-ignore
609
- typeof callback === "function" ? callback() : null;
610
- },
611
- ];
612
- }
613
- /**
614
- * @method useRef
615
- * @memberof Component
616
- * @param {string} ref
617
- * @description Allows you to get reference to DOM elements from the dom array
618
- * @returns {Object} {current, update}
619
- * @example
620
- * let ref = this.useRef('ref')
621
- * ref.current // returns the DOM element
622
283
 
623
- */
284
+ contents = contents.trim().replace(/\]$/, "")
285
+ contents = contents.replace(/\)$/, "");
286
+ usesBraces ? !contents.includes('<>') ? contents = `<>${contents}</>` : null : null
287
+ updatedContents = contents;
288
+ let attributes = extractAttributes(contents);
624
289
 
625
- useRef(ref) {
626
- // get ref from array
627
- console.log(this.dom)
628
- const element = this.dom[ref]
629
-
630
- const getElement = () => element;
631
-
632
- const update = (data) => {
633
- const newDom = new DOMParser().parseFromString(data, "text/html");
634
- const newElement = newDom.body.firstChild;
635
-
636
- if (element) {
637
- // @ts-ignore
638
- const isDifferent = !newElement.isEqualNode(element);
639
- if (isDifferent) {
640
- // @ts-ignore
641
- element.parentNode.replaceChild(newElement, element);
642
- }
643
- }
644
- };
290
+ let newAttributes = [];
291
+ let oldAttributes = [];
292
+ attributes.forEach((attribute) => {
293
+ const { element, attributes } = attribute;
294
+ if (Object.keys(attributes).length === 0) return;
645
295
 
646
- return {
647
- /**@type {HTMLElement} */
648
- // @ts-ignore
649
- current: getElement,
650
- /**@type {Function} */
651
- update,
652
- };
653
- }
654
296
 
655
- /**
656
- *
657
- * @function useEffect
658
- * @param {*} effectFn
659
- * @param {*} dependencies
660
- * @description Allows you to run side effects
661
- * @deprecated - this is no longer suggested please use vader signals instead
662
- * @returns {Object} {cleanup}
663
- */
664
- useEffect(effectFn, dependencies) {
665
- if (!this.effects[this.name]) {
666
- this.effects[this.name] = [];
667
- }
668
- this.effects[this.name].push(effectFn);
297
+ newAttributes.push(attribute);
298
+ for (let key in attributes) {
669
299
 
670
- if (dependencies.length > 0) {
671
- dependencies.forEach((d) => {
672
- if (d.set) {
673
- throw new Error(
674
- "signal found, do not use effect and signals at the same time - signals are more efficient"
675
- );
676
- }
677
- });
678
- } else if (!this.hasMounted) {
679
- effectFn();
680
- this.hasMounted = true;
681
- }
300
+ let value = attributes[key];
301
+ let oldvalue = value;
302
+ if (value && !value.new) {
303
+ if (value && value.includes("={")) {
304
+ value = value.replace("=", "");
305
+ value == "undefined" ? (value = '"') : (value = value);
682
306
 
683
- return {
684
- cleanup: () => {
685
- this.effects[this.name] = this.effects[this.name].filter(
686
- (effect) => effect !== effectFn
687
- );
688
- },
689
- };
690
- }
691
- /**
692
- * @method $Function
693
- * @description Allows you to create a function in global scope
694
- * @returns {Function}
695
- * @example
696
- * let func = this.$Function(function add(e, a){
697
- * return e + a
698
- * })
699
- * @param {*} fn
700
- */
701
- $Function(fn) {
702
- // @ts-ignore
703
- if (!typeof fn === "function") {
704
- throw new Error("fn must be a function");
705
- }
706
- let name = fn.name;
707
- if (!name) {
708
- name = "anonymous" + Math.floor(Math.random() * 100000000000000000);
709
- }
710
- window[name] = fn;
711
- // @ts-ignore
712
- return `window.${name}()`;
713
- }
307
+ key == 'style'
308
+ && value.includes("{{")
309
+ ? value = `{this.parseStyle({${value.split('{{')[1].split('}}')[0]}})}` : null
714
310
 
715
- // Add other methods like render, useEffect, useReducer, useAuth, etc.
716
311
 
717
- updateComponent() {
718
- const fragment = document.createDocumentFragment();
719
- Object.keys(components).forEach(async (component) => {
720
- const { name } = components[component];
721
-
722
-
723
- let componentContainer = document.querySelector(
724
- `[data-component="${name}"]`
725
- );
726
- let time = new Date().getTime();
727
- /**
728
- * @property {Object} snapshot
729
- * @description Allows you to keep track of component snapshots
730
- * @private
731
- * @returns {Object} {name, time, prev_state, prev_props, content}
732
- */
733
- let snapshot = {
734
- name: name,
735
- time: time,
736
- prev_state: this.states,
737
- prev_props: this.storedProps,
738
- // @ts-ignore
739
- content: componentContainer.innerHTML,
740
- };
741
-
742
- if (!componentContainer) return;
743
- const newHtml = await new Function(
744
- "useState",
745
- "useEffect",
746
- "useAuth",
747
- "useReducer",
748
- "useSyncStore",
749
- "signal",
750
- "rf",
751
- "props",
752
- "render",
753
- "return `" + (await this.render()) + "`;"
754
- )(
755
- this.useState,
756
- this.useEffect,
757
- this.useAuth,
758
- this.useReducer,
759
- this.useSyncStore,
760
- this.signal,
761
- this.render
762
- );
763
-
764
- if (newHtml !== componentContainer.innerHTML) {
765
- if (this.snapshots.length > 0) {
766
- let lastSnapshot = this.snapshots[this.snapshots.length - 1];
767
- if (lastSnapshot !== snapshot) {
768
- this.snapshots.push(snapshot);
312
+
313
+ value = `="\$${value}",`;
314
+ string = string.replace(oldvalue, value);
315
+
316
+ } else if (value && value.includes("={`")) {
317
+ value = value.replace("=", "");
318
+
319
+ value = `"\$${value}",`;
320
+ string = string.replace(oldvalue, value);
321
+
769
322
  }
770
- } else {
771
- this.snapshots.push(snapshot);
323
+ } else if (value && value.new) {
324
+ string = string.replace(oldvalue, value.new);
772
325
  }
773
- this.componentUpdate(
774
- snapshot.prev_state,
775
- snapshot.prev_props,
776
- snapshot.content
777
- );
778
- // batch updates
779
- fragment.appendChild(
780
- document.createRange().createContextualFragment(newHtml)
781
- );
782
- componentContainer.innerHTML = "";
783
- componentContainer.appendChild(fragment);
784
- this.runEffects();
785
326
  }
786
327
  });
328
+ });
329
+
330
+
331
+
332
+ if (comments) {
333
+ comments.forEach((comment) => {
334
+ let before = comment.trim();
335
+
336
+ comment = comment.replaceAll(/\s+/g, " ");
337
+ comment = comment.trim();
338
+ string = string.replace(before, comment);
339
+ let to_remove = comment.split("{")[1].split("}")[0].trim();
340
+ let beforeComment = comment;
341
+ comment = comment.replaceAll(`{ ${to_remove} }`, "");
342
+
343
+ string = string.replace(beforeComment, comment);
344
+ });
787
345
  }
788
- /**
789
- * @method validateClassName
790
- * @param {String} className
791
- * @private
792
- * @returns {Boolean}
793
- */
794
- validateClassName(className) {
795
- // validate classNames ensure they are camelCase but also allow for - and _
796
- return /^[a-zA-Z0-9-_]+$/.test(className);
797
- }
346
+ let lines = string.split("\n");
347
+ lines.forEach((line) => {
798
348
 
349
+ if (
350
+ line.includes("let") ||
351
+ line.includes("const") ||
352
+ line.includes("var")
353
+ ) {
354
+ switch (true) {
355
+ case line.includes("useState") && !line.includes("import"):
356
+ let varType = line.split("[")[0]
357
+ let key = line.split("=")[0].split(",")[0].trim().split('[')[1];
799
358
 
800
- parseHTML(result) {
801
-
802
- const dom = new DOMParser().parseFromString(result, "text/html");
803
- console.log(dom)
804
- const elements = dom.documentElement.querySelectorAll("*");
805
-
806
- elements.forEach((element) => {
807
- switch (element.nodeName) {
808
- case "IMG":
809
- if (
810
- !element.hasAttribute("alt") &&
811
- !document.documentElement.outerHTML
812
- .trim()
813
- .includes("<!-- #vader-disable_accessibility -->")
814
- ) {
815
- throw new SyntaxError(
816
- `Image: ${element.outerHTML} missing alt attribute`
817
- );
818
- } else if (
819
- element.hasAttribute("alt") &&
820
- // @ts-ignore
821
- element.getAttribute("alt").length < 1 &&
822
- !document.documentElement.outerHTML
823
- .trim()
824
- .includes("<!-- #vader-disable_accessibility -->")
825
- ) {
826
- throw new SyntaxError(
827
- `Image: ${element.outerHTML} alt attribute cannot be empty`
828
- );
829
-
830
- } else if (
831
- element.hasAttribute("src") &&
832
- !element.getAttribute("src")?.includes("http") || !element.getAttribute("src")?.includes("https") &&
833
- !document.documentElement.outerHTML
834
- .trim()
835
- .includes("<!-- #vader-disable_accessibility -->")
836
- ) {
837
- let prevurl = element.getAttribute("src");
838
- element.setAttribute("aria-hidden", "true");
839
- element.setAttribute("hidden", "true");
840
- // if window.lcoation.pathname includes a html file remove it and only use the path
841
- let url = window.location.origin + window.location.pathname.replace(/\/[^\/]*$/, '') + '/public/' + element.getAttribute("src");
842
- let image = new Image();
843
- image.src = url;
844
- image.onerror = () => {
845
- // @ts-ignore
846
- element.setAttribute("src", prevurl);
847
- throw new Error(`Image: ${element.outerHTML} not found`);
848
- };
849
- element.setAttribute("src", url);
850
-
851
- image.onload = () => {
852
- document.querySelectorAll(`img[src="${url}"]`).forEach((img) => {
853
- img.setAttribute("src", url);
854
- img.removeAttribute("aria-hidden");
855
- img.removeAttribute("hidden");
856
- });
857
- };
858
- }
359
+ let setKey = line.split("=")[0].trim().split(",")[1].trim().replace("]", "");
360
+ key = key.replace("[", "").replace(",", "");
361
+ let valuestate = line.split("=")[1].split("useState(")[1];
362
+
363
+ let regex = /useState\((.*)\)/gs;
364
+ valuestate = valuestate.match(regex) ? valuestate.match(regex)[0].split("useState(")[1].split(")")[0].trim() : valuestate
365
+
366
+
367
+ let newState = `${varType} [${key}, ${setKey}] = this.useState('${key}', ${valuestate}`;
368
+ string = string.replace(line, newState);
859
369
  break;
370
+ case line.includes("useRef") && !line.includes("import"):
371
+ line = line.trim();
372
+ let typeref = line.split(" ")[0]
860
373
 
861
- default:
862
- if (element.hasAttribute("ref")) {
863
- // @ts-ignore
864
- dom[element.getAttribute("ref")] = element;
865
- }
866
- if(element.nodeName === "MARKDOWN"){
867
- element.innerHTML = markdown(element.innerHTML.replace(/\\n/g, '\n').trim())
868
- }
374
+ let keyref = line.split(typeref)[1].split("=")[0].trim().replace("[", "").replace(",", "");
869
375
 
870
- if (element.hasAttribute("class")) {
871
- const allowClassComments =
872
- document.documentElement.outerHTML.includes(
873
- "<!-- #vader-allow_class -->"
874
- );
875
- if (!allowClassComments) {
876
- console.warn(
877
- "you can disable class errors using, <!-- #vader-allow_class -->"
878
- );
879
- throw new Error(
880
- "class attribute is not allowed, please use className instead"
881
- );
882
- }
883
- } else if (element.hasAttribute("className")) {
884
- // @ts-ignore
885
- element.setAttribute("class", element.getAttribute("className"));
886
- element.removeAttribute("className");
887
- }
888
376
 
889
- if (
890
- element.hasAttribute("href") &&
891
- // @ts-ignore
892
- element.getAttribute("href").startsWith("/") &&
893
- !document.documentElement.outerHTML
894
- .trim()
895
- .includes("<!-- #vader-disable_relative-paths -->")
896
- ) {
897
- element.setAttribute(
898
- "href",
899
- // @ts-ignore
900
- `#/${element.getAttribute("href").replace("/", "")}`
901
- );
902
- }
377
+ let valueref = line.split("=")[1].split("useRef(")[1];
903
378
 
904
- if (
905
- element.hasAttribute("src") &&
906
- // @ts-ignore
907
- !element.getAttribute("src").includes("http") &&
908
- // @ts-ignore
909
- !element.getAttribute("src").includes("https") &&
910
- !document.documentElement.outerHTML.includes(`<!-- #vader-disable_relative-paths -->`)
911
- ) {
912
- element.setAttribute(
913
- "src",
914
- // @ts-ignore
915
- `./public/${element.getAttribute("src")}`
916
- );
917
- }
379
+ let newStateref = `${typeref} ${keyref} = this.useRef('${keyref}', ${valueref}`;
380
+ string = string.replace(line, newStateref);
918
381
  break;
919
- }
920
-
921
- });
382
+ case line.includes("useReducer") && !line.includes("import"):
383
+ line = line.trim();
384
+ line = line.replaceAll(/\s+/g, " ");
385
+
386
+ let varTypereducer = line.split(" ")[0];
387
+ let keyreducer = line
388
+ .split("=")[0]
389
+ .split(" ")[1]
390
+ .trim()
391
+ .replace("[", "")
392
+ .replace(",", "");
393
+ let setKeyreducer = line.split("=")[0].trim().split(",")[1].trim().replace("]", "");
922
394
 
923
- result = dom.body.innerHTML;
395
+ let reducer = line.split("=")[1].split("useReducer(")[1];
924
396
 
925
- this.Componentcontent = result;
397
+ let newStatereducer = `${varTypereducer} [${keyreducer}, ${setKeyreducer}] = this.useReducer('${keyreducer}', ${line.includes('=>') ? reducer + '=>{' : reducer}`;
398
+
399
+ string = string.replace(line, newStatereducer);
400
+ break;
401
+ }
926
402
 
927
- if (!result.includes("<div data-component")) {
928
- result = `<div data-component="${this.name}">${result}</div>`;
929
403
  }
930
- return markdown(result.replace(/\\n/g, '\n').trim())
404
+ });
405
+
406
+
407
+ string = string.replaceAll(/\$\{\/\*.*\*\/\}/gs, "");
408
+
409
+ string = string.replaceAll('../src', './src')
410
+
411
+ function parseComponents(body, isChild) {
412
+ let componentRegex = /<([A-Z][A-Za-z0-9_-]+)\s*([^>]*)>\s*([\s\S]*?)\s*<\/\1>|<([A-Z][A-Za-z0-9_-]+)([^]*?)\/>/gs;
413
+
414
+ let componentMatch = body.match(componentRegex);
415
+ let topComponent = "";
416
+ componentMatch?.forEach(async (component) => {
417
+
418
+ let [, element, attributes] = component;
419
+ let before = component;
420
+ component = component.trim().replace(/\s+/g, " ");
421
+
422
+ !isChild ? (topComponent = component) : null;
423
+
424
+
425
+ let myChildrens = [];
426
+
427
+ let name = component.split("<")[1].split(">")[0].split(" ")[0].replace("/", "");
428
+ let componentAttributes = component.split("<")[1].split(">")[0].split(" ").join(" ").replace(name, "").trim();
429
+ const dynamicAttributesRegex = /(\w+)(?:="([^"]*?)"|='([^']*?)'|(?:=\{([^}]*?)\})?|(?:=\{(.*?)*\})?|(?:={([^}]*?)})?|(?:{([^}]*?)})?|(?:}))?|\$=\s*\{\s*\{\s*(.*?)\s*\}\s*\}/gs;
430
+
431
+
432
+
433
+
434
+ let props = component.match(dynamicAttributesRegex)
435
+
436
+ let filteredProps = [];
437
+ let isWithinComponent = false;
438
+ let componentName = name
439
+ let currentProps = []
440
+
441
+ let $_ternaryprops = []
442
+
443
+ for (let prop of props) {
444
+
445
+ if (prop === componentName) {
446
+
447
+ isWithinComponent = true;
448
+ filteredProps.push(prop);
449
+ } else if (isWithinComponent && prop.includes('=')) {
450
+
451
+ if (prop.startsWith('$=')) {
452
+ let old = prop
453
+ prop = prop.replace('$=', '$_ternary=')
454
+
455
+ // remove trailing }
456
+ prop = prop.replace(/}\s*$/, '')
457
+ component = component.replace(old, prop)
458
+ componentAttributes = componentAttributes.replace(old, prop)
459
+
460
+ $_ternaryprops.push(prop)
931
461
 
932
- }
933
-
934
- /**
935
- * The `html` method generates and processes HTML content for a component, performing various validations and tasks.
936
- *
937
- * @param {String} strings - The HTML content to be processed.
938
- * @param {...any} args - Dynamic values to be inserted into the template.
939
- * @returns {string} - The processed HTML content as a string.
940
- *
941
- * @throws {SyntaxError} - Throws a `SyntaxError` if image-related attributes are missing or invalid.
942
- * @throws {Error} - Throws an `Error` if there are issues with class names or relative paths.
943
- *
944
- * @example
945
- * // Example usage within a component:
946
- * const myComponent = new Component();
947
- * const htmlContent = myComponent.html`
948
- * <div>
949
- * <img src="/images/example.jpg" alt="Example Image" />
950
- * </div>
951
- * `;
952
- * document.body.innerHTML = htmlContent;
953
- *
954
- * @remarks
955
- * The `html` method is a core function used in component rendering. It allows you to define and generate HTML content within your component while enforcing best practices and accessibility standards. The method performs several essential tasks:
956
- *
957
- * 1. **Image Validation**: It checks images for the presence of 'alt' attributes and their validity.
958
- * - Throws a `SyntaxError` if an image is missing the 'alt' attribute.
959
- * - Throws a `SyntaxError` if the 'alt' attribute is empty.
960
- * - Checks for an 'aria-hidden' attribute for image elements.
961
- *
962
- * 2. **Class Attribute Handling**: It enforces class attribute usage and allows optional configuration via comments.
963
- * - Throws an `Error` if 'class' attributes are used without permission.
964
- * - Supports 'className' attributes for class definitions.
965
- * - Allows or disallows class-related comments based on your configuration.
966
- *
967
- * 3. **Relative Path Handling**: It processes relative paths in 'href' and 'src' attributes, ensuring proper routing.
968
- * - Converts relative 'href' attributes to anchor links with appropriate routing.
969
- * - Converts relative 'src' attributes to absolute paths with 'public' directories.
970
- *
971
- * 4. **Custom Component Attributes**: It supports adding a 'data-component' attribute to the root element.
972
- * - Ensures that the 'data-component' attribute is present for component identification.
973
- *
974
- * 5. **Lifecycle Method Invocation**: It invokes the `componentDidMount` method if called from a 'render' context.
975
- * - Executes `componentDidMount` to handle component initialization once the DOM is ready.
976
- *
977
- * @see {@link Component}
978
- * @see {@link Component#componentDidMount}
979
- */
980
-
981
-
982
-
983
- html(strings, ...args) {
984
- // @ts-ignore
985
- let tiemr = setInterval(()=>{
986
- if(document.querySelector(`[data-component="${this.name}"]`)){
987
- clearInterval(tiemr)
988
- this.componentMounted = true;
989
-
990
- document.querySelector(`[data-component="${this.name}"]`)?.querySelectorAll("*").forEach((element)=>{
991
- if(element.hasAttribute("ref")){
992
- // @ts-ignore
993
- this.dom[element.getAttribute("ref")] = element
994
462
  }
995
- })
996
- this.componentDidMount();
463
+ else if (prop.includes('${')) {
464
+
465
+ prop = prop.replace('="', ':').replace('}"', '}')
466
+ if (prop.includes('${')) {
467
+ prop = prop.replace('="', ':')
468
+ prop = prop.replace('${', '')
469
+ prop = prop.replace('}', '')
470
+
471
+ }
472
+ if (prop.includes('="${{')) {
473
+ prop = prop.replace('${{', '{')
474
+ prop = prop.replace('}}', '}')
475
+ prop = prop.replace('="', ':')
476
+ prop = prop.replace('}"', '}')
477
+ }
478
+
479
+ }
480
+ else if (prop.startsWith('={')) {
481
+ prop = prop.replace('={', ':`${')
482
+ prop.replace('} ', '}`')
483
+ }
484
+
485
+ if (prop.includes('function')) {
486
+ // parse 'function' to function
487
+ prop = prop.replace("'", '')
488
+
489
+ if (prop.endsWith("}'")) {
490
+ prop = prop.replace("}'", '}')
491
+
492
+ }
493
+
494
+ prop = prop.replace('=function', ':function')
495
+ }
496
+
497
+ filteredProps.push(prop);
498
+
499
+
500
+
501
+ }
502
+
503
+
504
+ else {
505
+ isWithinComponent = false;
506
+ }
997
507
  }
998
- }, 100)
999
- let script = document.createElement("script");
1000
- script.setAttribute("type", "text/javascript");
1001
- script.setAttribute(`data-component-script`, this.name);
1002
-
508
+ component = component.replaceAll(/\s+/g, " ");
1003
509
 
1004
-
1005
- let dom = this.dom
1006
-
1007
- if(this.cfr){
1008
-
1009
- worker.postMessage({strings, args, location: window.location.origin + window.location.pathname.replace(/\/[^\/]*$/, '') + '/public/', name: this.name})
1010
- let promise = new Promise((resolve, reject)=>{
1011
- worker.onmessage = (e)=>{
1012
- if(e.data.error){
1013
- throw new Error(e.data.error)
510
+ component = component.replace(componentAttributes, '')
511
+ $_ternaryprops.forEach((prop) => {
512
+ component = component.replace(prop, '')
513
+ })
514
+
515
+ let children = component.split(`<${name}`)[1].split(`</${name}>`)[0].trim().replace(/\s+/g, " ").trim().replace(/,$/, '').replace('>', '').replace(/\/$/, '').trim()
516
+
517
+
518
+ props = filteredProps.join(',').replace(/\s+/g, " ").trim().replace(/,$/, '')
519
+
520
+ let savedname = name;
521
+
522
+
523
+
524
+ name = name + Math.random().toString(36).substring(2);
525
+ if (children && children.match(componentRegex)) {
526
+ children = parseComponents(children, true);
527
+ childs.push({ parent: name, children: children });
528
+ } else {
529
+
530
+ children ? childs.push({ parent: name, children: children }) : null;
531
+ }
532
+
533
+ childs.forEach((child) => {
534
+ if (child.parent == name) {
535
+ let html = child.children.match(
536
+ /<([A-Z][A-Za-z0-9_-]+)([^>]*)>(.*?)<\/\1>|<([A-Z][A-Za-z0-9_-]+)([^]*?)\/>/gs
537
+ );
538
+ if (html) {
539
+ html = html.map((h) => h.trim().replace(/\s+/g, " ")).join(" ");
540
+ child.children = child.children.replaceAll(html, `${html}`);
541
+ // remove duplicate quotes
1014
542
  }
1015
- const dom = this.dom; // Assuming this.dom is an object
1016
- console.log(this.dom)
1017
- let js = e.data.js
1018
- let template = e.data.template
1019
- // Bind the component's context
1020
-
1021
- const useState = this.useState.bind(this); // Bind the component's context
1022
- const useEffect = this.useEffect.bind(this); // Bind the component's context
1023
- const useReducer = this.useReducer.bind(this); // Bind the component's context
1024
- const useAuth = this.useAuth.bind(this); // Bind the component's context
1025
- const useSyncStore = this.useSyncStore.bind(this); // Bind the component's context
1026
- const signal = this.signal.bind(this); // Bind the component's context
1027
- const rf = this.$Function.bind(this); // Bind the component's context
1028
- let states = this.states
1029
- const useRef = this.useRef.bind(this); // Bind the component's context
1030
- new Function("useState", "useEffect", "useAuth", "useReducer", "useSyncStore", "signal", "rf", "dom", "render", "states", "useRef", js)(
1031
- useState,
1032
- useEffect,
1033
- useAuth,
1034
- useReducer,
1035
- useSyncStore,
1036
- signal,
1037
- rf,
1038
- this.dom,
1039
- this.render,
1040
- this.states,
1041
- useRef
1042
- )
1043
-
1044
- resolve(new Function("useRef", "states", "return" + "`" + template + "`")(useRef, states))
1045
-
1046
-
1047
-
1048
-
543
+
544
+ myChildrens.push(child.children);
545
+ childs = childs.filter((e) => e.parent !== name);
546
+ }
547
+ });
548
+
549
+
550
+
551
+
552
+ props = props.replaceAll(`,${savedname}`, '').replaceAll(savedname, '')
553
+ if (props.startsWith(',')) {
554
+ props = props.replace(',', '')
555
+ }
556
+ props = props.replaceAll("='", ":'")
557
+ .replaceAll('=`', ':`')
558
+ .replaceAll('="', ':"')
559
+ .replaceAll('={', ':')
560
+
561
+
562
+ /**
563
+ * @memoize - memoize a component to be remembered on each render and replace the old jsx
564
+ */
565
+
566
+
567
+ let replace = "";
568
+ replace = `\${this.memoize(this.createComponent(${savedname}, {${props}}, [\`${myChildrens.join('')}\`]))}`;
569
+
570
+
571
+ body = body.replace(before, replace);
572
+ });
573
+
574
+ return body;
575
+ }
576
+
577
+ string = string.replaceAll('vaderjs/client', '/vader.js')
578
+
579
+ const importRegex = /import\s*([^\s,]+|\{[^}]+\})\s*from\s*(['"])(.*?)\2/g;
580
+ const imports = string.match(importRegex);
581
+ let replaceMents = [];
582
+
583
+
584
+ for (let match of imports) {
585
+ let path = match.split('from')[1].trim().replace(/'/g, '').replace(/"/g, '').trim()
586
+ switch (true) {
587
+ case path && !path.includes('./') && !path.includes('/vader.js') && !path.includes('/vaderjs/client') && !path.startsWith('src') && !path.startsWith('public') && !path.includes('http') && !path.includes('https'):
588
+ let componentFolder = fs.existsSync(process.cwd() + '/node_modules/' + path) ? process.cwd() + '/node_modules/' + path : process.cwd() + '/node_modules/' + path.split('/')[0]
589
+ componentFolder = componentFolder.split(process.cwd())[1]
590
+ if (!fs.existsSync(process.cwd() + componentFolder)) {
591
+ throw new Error('Could not find ' + path + ' at ' + match + ' in file ' + file)
1049
592
  }
1050
- worker.onerror = (e)=>{
1051
- reject(e)
593
+
594
+ if (!fs.existsSync(process.cwd() + '/dist/src/' + componentFolder.split('/').slice(2).join('/'))) {
595
+ fs.mkdirSync(process.cwd() + '/dist/src/' + componentFolder.split('/').slice(2).join('/'), { recursive: true })
1052
596
  }
1053
- })
1054
- // @ts-ignore
1055
- return promise;
1056
- }else{
1057
- let result = "";
1058
- for (let i = 0; i < strings.length; i++) {
1059
- result += strings[i];
1060
- if (i < args.length) {
1061
- result += args[i];
597
+
598
+ let baseFolder = componentFolder.split('node_modules')[1].split('/')[1]
599
+ let glp = globSync('**/**/**/**.{jsx,js}', {
600
+ cwd: process.cwd() + '/node_modules/' + baseFolder + '/',
601
+ absolute: true,
602
+ recursive: true
603
+ })
604
+ for (let file of glp) {
605
+ let text = fs.readFileSync(file, "utf8");
606
+ if (!file.endsWith('.js') && file.endsWith('.jsx')) {
607
+ text = Compiler(text, file);
608
+
609
+ }
610
+ let dest = file.split('node_modules')[1]
611
+ dest = dest.split(baseFolder)[1]
612
+ // write to dist
613
+ writer(process.cwd() + '/dist/src/' + baseFolder + dest, text)
614
+ let importname = match.split('import')[1].split('from')[0].trim()
615
+ let oldImportstring = match.split('from')[1].trim().replace(/'/g, '').replace(/"/g, '').trim()
616
+ let newImport = `/src/${baseFolder + dest}`
617
+ newImport = newImport.replaceAll('.jsx', '.js').replaceAll('\\', '/')
618
+ replaceMents.push({ match: oldImportstring, replace: newImport })
619
+ console.log(`📦 imported Node Package ${baseFolder} `)
1062
620
  }
621
+
622
+
623
+ break;
624
+ default:
625
+ break;
626
+ }
627
+ }
628
+
629
+ for (let replace of replaceMents) {
630
+ string = string.replaceAll(replace.match, replace.replace)
631
+ }
632
+
633
+ string = string.replaceAll(/\$\{[^{]*\.{3}/gs, (match) => {
634
+ if (match.includes('...')) {
635
+ // Your logic for replacement goes here
636
+ // For example, you can replace '...' with some other string
637
+ return match.replace('...', '');
638
+ }
639
+
640
+ return match;
641
+ });
642
+
643
+ string = string.replaceAll(/\$\{\/\*.*\*\/\}/gs, "");
644
+ string = string.replaceAll("<>", "`").replaceAll("</>", "`");
645
+ string = string.replaceAll(".jsx", ".js");
646
+ string = parseComponents(string);
647
+
648
+ string = string
649
+ .replaceAll("className", "class")
650
+ .replaceAll("classname", "class");
651
+
652
+ string = string.replaceAll('../src', './src')
653
+ string += `\n\n //wascompiled`;
654
+
655
+
656
+ string = string.replaceAll("undefined", "");
657
+ const parse = (css) => {
658
+ let styles = {};
659
+ let currentSelector = '';
660
+
661
+ css.split('\n').forEach(line => {
662
+ line = line.trim();
663
+
664
+ if (line.endsWith('{')) {
665
+ // Start of a block, extract the selector
666
+ currentSelector = line.slice(0, -1).trim();
667
+ styles[currentSelector] = {};
668
+ } else if (line.endsWith('}')) {
669
+ // End of a block
670
+ currentSelector = '';
671
+ } else if (line.includes(':') && currentSelector) {
672
+ // Inside a block and contains key-value pair
673
+ let [key, value] = line.split(':').map(part => part.trim());
674
+ styles[currentSelector][key] = value;
1063
675
  }
1064
- result = new Function("useRef", `return \`${result}\``)(useRef)
676
+ });
677
+
678
+ return styles;
679
+ };
680
+ string.split('\n').forEach(line => {
681
+ if (line.includes('import')) {
682
+ // Regular expression for matching import() statements
683
+ let asyncimportMatch = line.match(/import\s*\((.*)\)/gs);
684
+ // handle import { Component } from 'vaderjs/runtime/vader.js'
685
+ 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);
686
+
687
+ if (asyncimportMatch) {
688
+ asyncimportMatch.forEach(async (match) => {
689
+ let beforeimport = match
690
+ let path = match.split('(')[1].split(')')[0].trim()
691
+ let newImport = ''
692
+ let name = match.split('import')[1].split('from')[0].trim()
693
+ switch (true) {
694
+ case path && path.includes('json'):
695
+ path = path.replace(';', '')
696
+ newImport = `let ${name} = await fetch('${path}').then(res => res.json())`
697
+
698
+ break;
699
+ case path && path.includes('module.css'):
700
+ let css = await fs.readFileSync(process.cwd() + path, 'utf8')
701
+ css = css.replaceAll('.', '')
702
+
703
+ if (!name) {
704
+ throw new Error('Could not find name for css module ' + path + ' at' + beforeimport + ' file' + file)
705
+ }
706
+ newImport = `let ${name} = ${JSON.stringify(parse(css.replaceAll('.', '').replace(/\s+/g, " ")))}`
707
+
708
+ break;
709
+ default:
710
+ let deep = path.split('/').length - 1
711
+ for (let i = 0; i < deep; i++) {
712
+ path = path.split('../').join('')
713
+ path = path.split('./').join('')
714
+ }
715
+ path = path.replace(/'/g, '').trim().replace(/"/g, '').trim()
716
+ // remove double / from path
717
+ path = path.split('//').join('/')
718
+ if (!path.startsWith('./') && !path.includes('/vader.js') && !path.startsWith('src')
719
+ && !path.startsWith('/public')
720
+ ) {
721
+ path = '/src/' + path
722
+ }
723
+
724
+ path = path.replaceAll('.jsx', '.js');
725
+ newImport = `await import(${path})`
726
+ }
727
+ if (newImport) {
728
+ string = string.replace(beforeimport, newImport)
729
+ }
730
+ })
731
+ }
732
+
733
+ if (regularimportMatch) {
734
+ for (let match of regularimportMatch) {
735
+ let beforeimport = match
736
+ let path = match.split('from')[1] ? match.split('from')[1].trim() : match.split('import')[1].trim()
737
+
738
+ let newImport = ''
739
+ let name = match.split('import')[1].split('from')[0].trim()
740
+
741
+
742
+ switch (true) {
743
+ case path && path.includes('json'):
744
+ path = path.replace(';', '')
745
+ newImport = `let ${name} = await fetch('${path}').then(res => res.json())`
746
+
747
+ break;
748
+ case path && path.includes('module.css'):
749
+
750
+ path = path.replace(';', '')
751
+ path = path.replace(/'/g, '').trim().replace(/"/g, '').trim()
752
+ path = path.replaceAll('.jsx', '.js');
753
+ path = path.replaceAll('../', '');
754
+
755
+ let css = fs.readFileSync(process.cwd() + '/' + path, 'utf8')
756
+
757
+ css = css.replaceAll('.', '')
758
+ newImport = `let ${name} = ${JSON.stringify(parse(css))}`
759
+ string = string.replace(beforeimport, newImport)
760
+ break;
761
+ case path && path.includes('.css'):
762
+ string = string.replace(beforeimport, '')
763
+ newImport = ``
764
+ break;
765
+ case path && !path.startsWith('./') && !path.includes('/vader.js') && !path.startsWith('src') && !path.startsWith('public') &&
766
+ path.match(/^[A-Za-z0-9_-]+$/gs) && !path.includes('http') && !path.includes('https'):
767
+
768
+ break;
769
+ default:
770
+ let beforePath = path
771
+ let deep = path.split('/').length - 1
772
+ for (let i = 0; i < deep; i++) {
773
+ path = path.split('../').join('')
774
+ path = path.split('./').join('')
775
+ }
776
+ path = path.replace(/'/g, '').trim().replace(/"/g, '').trim()
777
+ // remove double / from path
778
+ path = path.split('//').join('/')
779
+ if (!path.startsWith('./') && !path.includes('/vader.js') && !path.startsWith('src') && !path.startsWith('public')) {
780
+ path.includes('src') ? path.split('src')[1] : null
781
+ path = '/src/' + path
782
+ } else if (path.startsWith('src') || path.startsWith('public')) {
783
+ path = '/' + path
784
+ }
785
+ path = path.replaceAll('.jsx', '.js');
786
+
787
+
788
+ string = string.replace(beforePath, "'" + path + "'")
789
+ break;
790
+
791
+ }
792
+
793
+
794
+ if (newImport) {
795
+ string = string.replace(beforeimport, newImport)
796
+ }
797
+
798
+ }
799
+
1065
800
 
1066
- if (!result.trim().startsWith("<body>")) {
1067
- console.warn(
1068
- "You should wrap your html in a body tag, vader may not grab all html!"
1069
- );
1070
801
  }
1071
-
1072
-
1073
-
1074
- return this.parseHTML(result);
1075
802
  }
1076
803
 
1077
-
1078
- }
1079
- // write types to ensure it returns a string
1080
- /**
1081
- * @method render
1082
- * @description Allows you to render html
1083
- * @returns {Promise <any>}
1084
- * @example
1085
- * async render() {
1086
- * return this.html(`
1087
- * <div className="hero p-5">
1088
- * <h1>Home</h1>
1089
- * </div>
1090
- * `);
1091
- */
1092
- async render(props) {}
804
+
805
+ })
806
+
807
+ return string
1093
808
  }
1094
809
 
1095
- /**
1096
- * @object Vader
1097
- * @property {class} Component
1098
- * @property {function} useRef
1099
- * @description Allows you to create a component
1100
- * @example
1101
- * import { Vader } from "../../dist/vader/vader.js";
1102
- * export class Home extends Vader.Component {
1103
- * constructor() {
1104
- * super('Home');
1105
- * }
1106
- * async render() {
1107
- * return this.html(`
1108
- * <div className="hero p-5">
1109
- * <h1>Home</h1>
1110
- * </div>
1111
- * `);
1112
- * }
1113
- */
1114
- const Vader = {
1115
- /**
1116
- * @class Component
1117
- * @description Allows you to create a component
1118
- * @returns {void}
1119
- * @memberof {Vader}
1120
- * @example
1121
- * import { Vader } from "../../dist/vader/index.js";
1122
- * export class Home extends Vader.Component {
1123
- * constructor() {
1124
- * super();
1125
- * }
1126
- * async render() {
1127
- * return this.html(`
1128
- * <div className="hero p-5">
1129
- * <h1>Home</h1>
1130
- * </div>
1131
- * `);
1132
- * }
1133
- * }
1134
- */
1135
- Component: Component,
1136
- useRef: useRef,
1137
- };
1138
- export const component = (name) => {
1139
- return new Component();
1140
- };
810
+ globalThis.isBuilding = false
811
+ globalThis.isWriting = null
812
+ const glb = await glob("**/**/**/**.{jsx,js}", {
813
+ ignore: ["node_modules/**/*", "dist/**/*"],
814
+ cwd: process.cwd() + '/pages/',
815
+ absolute: true,
816
+ recursive: true
817
+ });
818
+ async function Build() {
819
+ globalThis.isBuilding = true
820
+ console.log('Compiling......')
821
+ let reader = async (file) => {
822
+ let text = await fs.readFileSync(file, "utf8");
823
+ return text;
824
+ };
1141
825
 
1142
- /**
1143
- * @function rf
1144
- * @param {*} name
1145
- * @param {*} fn
1146
- * @returns {void}
1147
- * @deprecated - rf has been replaced with Vader.Component.$Function
1148
- * @description Allows you to register function in global scope
1149
- */
1150
- export const rf = (name, fn) => {
1151
- window[name] = fn;
1152
- };
1153
- let cache = {};
1154
- async function handletemplate(data){
1155
- let dom = new DOMParser().parseFromString(data, "text/html");
1156
- let elements = dom.documentElement.querySelectorAll("*");
1157
-
1158
- if (elements.length > 0) {
1159
- for (var i = 0; i < elements.length; i++) {
1160
-
1161
- if (elements[i].nodeName === "INCLUDE") {
1162
- if(!elements[i].getAttribute("src") || elements[i].getAttribute("src") === ""){
1163
- throw new Error("Include tag must have src attribute")
826
+
827
+ function ssg(routes = []) {
828
+ globalThis.isBuilding = true
829
+ console.log(`Generating html files for ${routes.length} routes`)
830
+ routes.forEach(async (route) => {
831
+ if (route.url.includes(':')) {
832
+ console.log('Route ' + route.url + ' is a dynamic route and will not be generated')
833
+ return
834
+ }
835
+ let equalparamroute = routes.map((e) => {
836
+ if (e.url.includes(':')) {
837
+ let url = e.url.split('/:')[0]
838
+ if (url && route.url === url) {
839
+ return e
840
+ } else {
841
+ return null
842
+
843
+ }
1164
844
  }
1165
-
1166
- let componentName = elements[i].getAttribute("src")?.split("/").pop()?.split(".")[0]
1167
- // @ts-ignore
1168
- let filedata = await include(elements[i].getAttribute("src"))
1169
- // replace ` with \`\` to allow for template literals
1170
- filedata = filedata.replace(/`/g, "\\`")
1171
- cache[elements[i].getAttribute("src")] = filedata
1172
- filedata = new Function(`return \`${filedata}\`;`)();
1173
- let newdom = new DOMParser().parseFromString(filedata, "text/html");
1174
-
1175
- newdom.querySelectorAll("include").forEach((el)=>{
1176
- el.remove()
1177
- })
1178
- // @ts-ignore
1179
-
1180
- let els = dom.querySelectorAll(componentName)
1181
-
1182
- els.forEach((el)=>{
1183
-
1184
- if(el.attributes.length > 0){
1185
- for(var i = 0; i < el.attributes.length; i++){
1186
- newdom.body.outerHTML = newdom.body.outerHTML.replace(`{{${el.attributes[i].name}}}`, el.attributes[i].value)
1187
- }
1188
-
1189
- }
1190
- if(el.children.length > 0 && newdom.body.querySelector('slot')){
1191
- for(var i = 0; i < el.children.length; i++){
1192
- let slots = newdom.body.querySelectorAll("slot")
1193
- slots.forEach((slot)=>{
1194
- let id = slot.getAttribute("id")
1195
- if(id === el.nodeName.toLowerCase()){
1196
- slot.outerHTML = `<div>${el.innerHTML}</div>`
1197
- }
1198
- })
1199
-
1200
-
1201
- }
1202
-
1203
- }
1204
-
1205
- dom.body.querySelectorAll('include').forEach((el)=>{
1206
- el.remove()
1207
- })
1208
- // replace ` with \`\` to allow for template literals
1209
- dom.body.outerHTML = dom.body.outerHTML.replace(/`/g, "\\`")
1210
- dom.body.outerHTML = dom.body.outerHTML.replace(el.outerHTML, new Function(`return \`${newdom.body.outerHTML}\`;`)())
845
+ return null
846
+ }).filter(Boolean)
847
+ let document = `
848
+ <!DOCTYPE html>
849
+ <html lang="en">
850
+ <head>
851
+ <script>
852
+ window.routes = JSON.parse('${JSON.stringify(routes)}')
853
+ </script>
854
+ <script id="isServer">
855
+ window.isServer = true
856
+ </script>
857
+ <meta charset="UTF-8">
858
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
859
+ <script type="module" id="meta">
860
+ window.history.pushState({}, '', '${route.url}')
861
+ window.module = await import('/${route.fileName.replace('.jsx', '.js')}')
862
+ let metadata = await module.$metadata
863
+ if(metadata && metadata.title){
864
+ document.head.innerHTML += '<title>' + metadata.title + '</title>'
865
+ }
866
+ if(metadata && metadata.description){
867
+ document.head.innerHTML += '<meta name="description" content="' + metadata.description + '">'
868
+ }
869
+ if(metadata && metadata.keywords){
870
+ document.head.innerHTML += '<meta name="keywords" content="' + metadata.keywords + '">'
871
+ }
872
+ if(metadata && metadata.author){
873
+ document.head.innerHTML += '<meta name="author" content="' + metadata.author + '">'
874
+ }
875
+ if(metadata && metadata.image){
876
+ let image = metadata.image.file
877
+ let type = metadata.image.type
878
+
879
+ document.head.innerHTML += '<meta property="og:image" content="' + image + '">'
880
+ document.head.innerHTML += '<meta property="og:image:type" content="' + type + '">'
881
+ }
882
+ if(metadata && metadata.url){
883
+ document.head.innerHTML += '<meta property="og:url" content="' + metadata.url + '">'
884
+ }
1211
885
 
1212
-
1213
- })
886
+ if(metadata && metadata.robot){
887
+ document.head.innerHTML += '<meta name="robots" content="' + metadata.robot + '">'
888
+ }
889
+ if(metadata && metadata.manifest){
890
+ document.head.innerHTML += '<link rel="manifest" href="' + metadata.manifest + '">'
891
+ }
892
+ if(metadata && metadata.tags){
893
+ metadata.tags.forEach(tag => {
894
+ document.head.innerHTML += tag
895
+ })
896
+ }
897
+
898
+ if(metadata && metadata.styles){
899
+ metadata.styles.forEach(style => {
900
+ style = style.replaceAll('./', '/')
901
+ style = style.replaceAll('../', '/')
902
+ style = style.replace("'", '')
903
+ document.head.innerHTML += '<link rel="stylesheet" href="' + style + '">'
904
+ })
905
+ }
906
+ if(metadata && metadata.icon){
907
+ document.head.innerHTML += '<link rel="icon" href="' + metadata.icon + '">'
908
+ }
909
+ </script>
910
+ <script type="module" id="router">
911
+ import VaderRouter from '/router.js'
912
+ const router = new VaderRouter('${route.url}', 3000)
913
+ router.get('${route.url}', async (req, res) => {
914
+ let module = await import('/${route.fileName.replace('.jsx', '.js')}')
915
+ if(Object.keys(module).includes('$prerender') && !module.$prerender){
916
+ document.head.setAttribute('prerender', 'false')
917
+ }
918
+ res.render(module, req, res, module.$metadata)
919
+ })
920
+ ${equalparamroute.length > 0 ? equalparamroute.map((e) => {
921
+
922
+
923
+
924
+ return `router.get('${e.url}', async (req, res) => {
925
+ let module = await import('/${e.fileName.replace('.jsx', '.js')}')
926
+ res.render(module, req, res, module.$metadata)
927
+ })\n`
928
+ }) : ''}
929
+ router.listen(3000)
930
+
931
+ </script>
932
+ </head>
933
+ <body>
934
+ <div id="root"></div>
935
+ </body>
1214
936
 
1215
-
1216
-
937
+
938
+ </html>
939
+ `;
940
+
941
+ // generate random but common ports
942
+ let port = Math.floor(Math.random() * (65535 - 49152 + 1) + 49152)
943
+
944
+ const server = http.createServer((req, res) => {
945
+
946
+ if (req.url === '/') {
947
+ res.writeHead(200, { 'Content-Type': 'text/html' });
948
+ res.end(document);
949
+ } else {
950
+ // Serve static files (adjust the file paths based on your project structure)
951
+ const filePath = process.cwd() + '/dist/' + req.url
1217
952
 
953
+ fs.readFile(filePath, (err, data) => {
954
+ if (err) {
955
+ res.writeHead(404, { 'Content-Type': filePath.includes('js') ? 'text/javascript' : 'text/html' });
956
+ res.end('File not found');
957
+ } else {
958
+ res.writeHead(200, { 'Content-Type': filePath.includes('js') ? 'text/javascript' : 'text/html' });
959
+ res.end(data);
960
+ }
961
+ });
962
+ }
963
+ });
964
+
965
+ server.listen(port)
966
+ server.on('error', (err) => {
967
+ if (err.code === 'EADDRINUSE') {
968
+ console.log(`Port ${port} is in use, trying another port...`);
969
+ setTimeout(() => {
970
+ server.close();
971
+ server.listen(++port);
972
+ }, 1000);
973
+ }
974
+ })
975
+
976
+ globalThis.listen = true;
977
+
978
+ const browser = await puppeteer.launch({
979
+ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'],
980
+ warning: false,
981
+ })
982
+
983
+ const browserPID = browser.process().pid
984
+ try {
985
+
986
+ route.url = route.url.replaceAll(/\/:[a-zA-Z0-9_-]+/gs, '')
987
+ let page = await browser.newPage();
988
+ await page.goto(`http://localhost:${port}/`, { waitUntil: 'networkidle2' });
989
+ await page.on('console', msg => console.log('PAGE LOG:', msg.text()));
990
+ await page.on('error', err => console.log('PAGE LOG:', err));
991
+ await page.on('pageerror', err => console.log('PAGE LOG:', err));
992
+ await page.on('requestfailed', err => console.log(err));
993
+ await page.evaluate(() => {
994
+ window.onerror = function (msg, url, lineNo, columnNo, error) {
995
+ console.log(msg, url, lineNo, columnNo, error)
996
+ }
997
+ })
998
+ await page.waitForSelector('#root', { timeout: 10000 })
999
+ await page.evaluate(() => {
1000
+ document.getElementById('meta').remove()
1001
+ document.querySelector('#isServer').innerHTML = 'window.isServer = false'
1002
+ if (document.head.getAttribute('prerender') === 'false') {
1003
+ document.querySelector('#root').innerHTML = ''
1004
+ console.log(`Disabled prerendering for ${window.location.pathname}`)
1005
+ }
1006
+ })
1007
+ const html = await page.content();
1008
+
1009
+ await page.close();
1010
+ await writer(process.cwd() + '/dist/' + (route.url === '/' ? 'index.html' : `${route.url}/` + 'index.html'), html)
1011
+ await browser.close();
1012
+ server.close()
1013
+
1014
+ } catch (error) {
1015
+ server.close()
1016
+ await browser.close();
1218
1017
  }
1018
+ finally {
1019
+ await browser.close();
1020
+ server.close()
1021
+ }
1022
+ try {
1023
+ process.kill(browserPID)
1024
+ } catch (error) {
1025
+ }
1026
+
1027
+
1028
+ })
1029
+
1030
+ let timeout = setTimeout(() => {
1031
+ globalThis.isBuilding = false
1032
+ clearTimeout(timeout)
1033
+ }, 1000)
1034
+ console.log(`Generated ${routes.length} html files for ${routes.length} routes`)
1035
+ }
1036
+
1037
+ globalThis.routes = []
1038
+
1039
+ for await (let file of glb) {
1040
+ // Normalize file paths
1041
+ let origin = file.replace(/\\/g, '/');
1042
+ let fileName = origin.split('/pages/')[1].split('.jsx')[0].replace('.jsx', '') + '.jsx';
1043
+ let isBasePath = fileName === 'index.jsx';
1044
+ let isParamRoute = fileName.includes('[') && fileName.includes(']') ? true : false
1045
+
1046
+ // Extract all dynamic parameters from the file path [param1]/[param2]/[param3
1047
+ let aburl = origin.split('/pages')[1].split('.jsx')[0].replace('.jsx', '').split('[').join(':').split(']').join('');
1048
+
1049
+ if (aburl.includes('...')) {
1050
+ // this is a catch all route
1051
+ // it should be /pages/[...]/index.jsx or /pages/[...].jsx
1052
+ aburl = aburl.split('...').join('*').split(':*').join('*')
1053
+ aburl = aburl.replaceAll('./index', '')
1054
+
1219
1055
  }
1056
+ // Create an object with URL and pathname properties
1057
+ let obj = {
1058
+ url: isBasePath ? '/' : aburl.replaceAll('/index', ''),
1059
+ pathname: `/pages/${origin.split('pages/')[1].split('.jsx')[0].replace('.jsx', '')}.jsx`,
1060
+ fullpath: origin,
1061
+ };
1062
+
1063
+
1064
+
1065
+ let data = await fs.readFileSync(origin, "utf8");
1066
+ console.log(`Compiling ${fileName}...`)
1067
+ data = Compiler(data, origin);
1068
+
1069
+
1070
+ await writer(process.cwd() + "/dist/" + fileName.replace('.jsx', '.js'), data).then(async () => {
1071
+
1072
+
1073
+
1074
+ await writer(process.cwd() + "/dist/" + fileName.replace('.jsx', '.js'), data)
1075
+
1076
+ })
1077
+
1078
+ // configure routing for each page
1079
+
1080
+ obj.compiledPath = process.cwd() + "/dist/pages/" + fileName.replace('.jsx', '.js')
1081
+ let providerRedirects = { cloudflare: '_redirects', vercel: 'vercel.json', netlify: '_redirects' }
1082
+ switch (true) {
1083
+ case config && config.host && !config.host['_redirect']:
1084
+ let host = config.host.provider
1085
+
1086
+ let provider = providerRedirects[host]
1087
+ if (provider) {
1088
+
1089
+ let redirectFile = null
1090
+ switch (true) {
1091
+ case provider === '_redirects':
1092
+ redirectFile = fs.existsSync(process.cwd() + '/dist/' + provider) ? fs.readFileSync(process.cwd() + '/dist/' + provider, 'utf8') : ''
1093
+ break;
1094
+ case provider === 'vercel.json':
1095
+ redirectFile = fs.existsSync(process.cwd() + '/' + provider) ? fs.readFileSync(process.cwd() + '/' + provider, 'utf8') : ''
1096
+ break;
1097
+ default:
1098
+ break;
1099
+ }
1100
+ let type = provider === '_redirects' ? 'text/plain' : 'application/json'
1101
+
1102
+ let root = obj.url.includes(':') ? obj.url.split('/:')[0] : obj.url
1103
+ switch (true) {
1104
+ case root === '/':
1105
+ break;
1106
+ case type === 'text/plain' && !redirectFile.includes(obj.url) && obj.url.includes(':'):
1107
+ let page = obj.pathname.split('/pages/')[1].replace('.jsx', '.js')
1108
+ redirectFile += `\n/${page} /${page} 200\n${obj.url} ${root} 200\n`
1109
+ !redirectFile.includes('/404') ? redirectFile += `\n/404 /404 404` : null
1110
+ fs.writeFileSync(process.cwd() + '/dist/' + provider, redirectFile)
1111
+ console.log(`Added ${obj.url} ${obj.url} 200 to ${provider}`)
1112
+ break;
1113
+ case type === 'application/json' && !redirectFile?.includes(`${obj.url}`):
1114
+ let json = redirectFile ? JSON.parse(redirectFile) : {}
1115
+ let isVercel = provider === 'vercel.json' ? true : false
1116
+ if (isVercel) {
1117
+ json['rewrites'] = json['rewrites'] || []
1118
+ json['rewrites'].push({ "source": obj.url, "destination": `${root}/index.html` })
1119
+ fs.writeFileSync(process.cwd() + '/' + provider, JSON.stringify(json, null, 2))
1120
+ console.log(`Added ${obj.url} ${root}/index.html to ${provider}`)
1121
+ }
1122
+
1123
+ }
1124
+ }
1125
+ break;
1126
+ case config && config.host && config.host['_redirect']:
1127
+ let file = config.host['_redirect']
1128
+ file = file.split('./').join('')
1129
+ let redirectFile = fs.existsSync(process.cwd() + '/' + file) ? fs.readFileSync(process.cwd() + '/' + file, 'utf8') : ''
1130
+ fs.writeFileSync(process.cwd() + '/dist/' + file, redirectFile)
1131
+ console.log(`Using ${file} for redirects`)
1132
+ default:
1133
+ break;
1134
+
1135
+ }
1136
+
1137
+
1138
+ globalThis.routes.push({ fileName: fileName, url: obj.url, html: '/' + (isBasePath ? 'index.html' : `${obj.url}/` + 'index.html') })
1139
+
1140
+
1220
1141
 
1221
-
1222
1142
  }
1223
-
1224
- // replace ` with \`\` to allow for template literals
1225
- dom.body.outerHTML = dom.body.outerHTML.replace(/`/g, "\\`")
1226
- data = new Function(`return \`${dom.body.outerHTML}\`;`)();
1227
-
1228
- return data;
1143
+
1144
+ ssg(globalThis.routes)
1145
+
1146
+
1147
+ const scannedSourceFiles = await glob("**/**.{jsx,js,json}", {
1148
+ ignore: ["node_modules/**/*", "dist/**/*"],
1149
+ cwd: process.cwd() + '/src/',
1150
+ absolute: true,
1151
+ });
1152
+ const scannedVaderFiles = await glob("**/**.{html,js,json}", {
1153
+ cwd: process.cwd() + '/node_modules/vaderjs/runtime',
1154
+ absolute: true,
1155
+ });
1156
+
1157
+ scannedVaderFiles.forEach(async (file) => {
1158
+ file = file.replace(/\\/g, '/');
1159
+
1160
+
1161
+ let name = file.split('/node_modules/vaderjs/runtime/')[1]
1162
+ if (file.includes('index.html') && fs.existsSync(process.cwd() + "/dist/" + name)) {
1163
+ return
1164
+ }
1165
+ let data = await reader(file)
1166
+ bundleSize += fs.statSync(file).size;
1167
+ await writer(process.cwd() + "/dist/" + name, data);
1168
+ })
1169
+ scannedSourceFiles.forEach(async (file) => {
1170
+ file = file.replace(/\\/g, '/');
1171
+ let name = file.split('/src/')[1]
1172
+ //parse jsx
1173
+
1174
+ let data = await reader(process.cwd() + "/src/" + name)
1175
+ if (name.includes('.jsx')) {
1176
+ data = Compiler(data, process.cwd() + "/src/" + name);
1177
+
1178
+ await writer(process.cwd() + "/dist/src/" + name.split('.jsx').join('.js'), data).then(async () => {
1179
+ await writer(process.cwd() + "/dist/src/" + name.replace('.jsx', '.js'), data)
1180
+
1181
+ })
1182
+ return
1183
+ }
1184
+ bundleSize += fs.statSync(process.cwd() + "/src/" + name).size;
1185
+ await writer(process.cwd() + "/dist/src/" + name, data);
1186
+ })
1187
+
1188
+ const scannedPublicFiles = await glob("**/**.{css,js,html,mjs,cjs}", {
1189
+ ignore: ["node_modules/**/*", "dist/**/*"],
1190
+ cwd: process.cwd() + '/public/',
1191
+ absolute: true,
1192
+ });
1193
+ scannedPublicFiles.forEach(async (file) => {
1194
+ file = file.replace(/\\/g, '/');
1195
+ file = file.split('/public/')[1]
1196
+ let data = await reader(process.cwd() + "/public/" + file)
1197
+ bundleSize += fs.statSync(process.cwd() + "/public/" + file).size;
1198
+ await writer(process.cwd() + "/dist/public/" + file, data);
1199
+ })
1200
+ const scannedFiles = await glob("**/**.{css,js,html}", {
1201
+ ignore: ["node_modules/**/*", "dist/**/*"],
1202
+ cwd: process.cwd() + "/runtime/",
1203
+ absolute: true,
1204
+ })
1205
+
1206
+
1207
+ if (!fs.existsSync(process.cwd() + "/dist/index.html")) {
1208
+
1209
+ scannedFiles.forEach(async (file) => {
1210
+ file = file.split(process.cwd() + '/runtime/')[1]
1211
+
1212
+ let objCase = {
1213
+ ...file == "app.js" ? { exit: true } : null,
1214
+ ...file.includes("index.html") && fs.existsSync(process.cwd() + "/dist/" + file) ? { exit: true } : null,
1215
+
1216
+ }
1217
+ if (objCase.exit) {
1218
+ console.log('exiting')
1219
+ return true
1220
+ }
1221
+ bundleSize += fs.statSync(process.cwd() + "/node_modules/vaderjs/runtime/" + file).size;
1222
+ let data = await reader(process.cwd() + "/node_modules/vaderjs/runtime/" + file)
1223
+ await writer(process.cwd() + "/dist/" + file, data);
1224
+ });
1225
+
1226
+ }
1227
+
1228
+ globalThis.isBuilding = false
1229
+ console.log(`📦 Build completed: Build Size -> ${Math.round(bundleSize / 1000)}kb`)
1230
+
1231
+ bundleSize = 0;
1232
+
1233
+ return true
1229
1234
  }
1230
- /**
1231
- * @function include
1232
- * @description Allows you to include html file
1233
- * @returns {Promise} - modified string with html content
1234
- * @param {string} path
1235
- */
1235
+ const s = () => {
1236
1236
 
1237
-
1238
-
1239
- export const include = async (path) => {
1240
-
1241
- if (
1242
- path.startsWith("/") &&
1243
- !path.includes("/src/") &&
1244
- !document.documentElement.outerHTML
1245
- .trim()
1246
- .includes("<!-- #vader-disable_relative-paths -->")
1247
- ) {
1248
- path = "/src/" + path;
1237
+ const server = http.createServer((req, res) => {
1238
+
1239
+ const validExtensions = ['.js', '.css', '.mjs', '.cjs', '.html', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp4', '.webm', '.ogg'];
1240
+
1241
+ if (!validExtensions.some(ext => req.url.endsWith(ext))) {
1242
+ req.url = req.url !== '/' ? req.url.split('/')[1] : req.url;
1243
+ req.url = path.join(process.cwd(), 'dist', req.url, 'index.html');
1244
+ } else {
1245
+ req.url = path.join(process.cwd(), 'dist', req.url);
1246
+ }
1247
+
1248
+ const filePath = req.url
1249
+
1250
+ fs.readFile(filePath, (err, data) => {
1251
+ if (err) {
1252
+ res.writeHead(404, { 'Content-Type': 'text/html' });
1253
+ res.end(fs.existsSync(process.cwd() + '/dist/404') ? fs.readFileSync(process.cwd() + '/dist/404/index.html') : '404');
1254
+ } else {
1255
+ const contentType = getContentType(filePath);
1256
+ switch (true) {
1257
+ case contentType === 'text/html' && globalThis.devMode:
1258
+ data = data.toString() + `<script type="module">
1259
+ let ws = new WebSocket('ws://localhost:${process.env.PORT || 3000}')
1260
+ ws.onmessage = (e) => {
1261
+ if(e.data === 'reload'){
1262
+ window.location.reload()
1263
+ }
1264
+ }
1265
+ </script>
1266
+ `
1267
+ }
1268
+ res.writeHead(200, { 'Content-Type': contentType });
1269
+ res.end(data);
1270
+ }
1271
+ });
1272
+ });
1273
+
1274
+
1275
+ const ws = new WebSocketServer({ server });
1276
+ ws.on('connection', (socket) => {
1277
+ console.log('WebSocket Hydration Client connected');
1278
+ socket.on('close', () => console.log('WebSocket Hydration Client disconnected'));
1279
+ });
1280
+
1281
+
1282
+ function getContentType(filePath) {
1283
+ let ext = ['.js', '.css', '.mjs', '.cjs', '.html', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.mp4', '.webm', '.ogg'].includes(path.extname(filePath)) ? path.extname(filePath) : '.html'
1284
+ switch (ext) {
1285
+ case '.js':
1286
+ return 'text/javascript';
1287
+ case '.css':
1288
+ return 'text/css';
1289
+ case '.mjs':
1290
+ return 'text/javascript';
1291
+ case '.cjs':
1292
+ return 'text/javascript';
1293
+ case '.html':
1294
+ return 'text/html';
1295
+ case '.json':
1296
+ return 'application/json';
1297
+ case '.png':
1298
+ return 'image/png';
1299
+ case '.jpg':
1300
+ return 'image/jpg';
1301
+ case '.jpeg':
1302
+ return 'image/jpeg';
1303
+ case '.gif':
1304
+ return 'image/gif';
1305
+ case '.svg':
1306
+ return 'image/svg+xml';
1307
+ case '.mp4':
1308
+ return 'video/mp4';
1309
+ case '.webm':
1310
+ return 'video/webm';
1311
+ case '.ogg':
1312
+ return 'video/ogg';
1313
+ default:
1314
+ return 'application/octet-stream';
1315
+ }
1249
1316
  }
1250
- if (cache[path]) {
1251
- return await handletemplate(new Function(`return \`${cache[path]}\`;`)())
1252
-
1253
- }else{
1254
- return fetch(`./${path}`)
1255
- .then((res) => {
1256
- if (res.status === 404) {
1257
- throw new Error(`No file found at ${path}`);
1317
+
1318
+ const PORT = process.env.PORT || 3000;
1319
+ server.listen(PORT, () => {
1320
+ console.log(`Server is running on port ${PORT}`);
1321
+ });
1322
+ let i =
1323
+ setInterval(() => {
1324
+ if (globalThis.isBuilding && globalThis.devMode) {
1325
+
1326
+ ws.clients.forEach((client) => {
1327
+ client.send('reload')
1328
+ })
1329
+ } else {
1330
+ clearInterval(i)
1258
1331
  }
1259
- return res.text();
1332
+ }, 120)
1333
+
1334
+ }
1335
+
1336
+
1337
+ switch (true) {
1338
+ case process.argv.includes('--watch') && !process.argv.includes('--build') && !process.argv.includes('--serve'):
1339
+
1340
+ globalThis.devMode = true
1341
+ console.log(`
1342
+ Vader.js v1.3.3
1343
+ - Watching for changes in ./pages
1344
+ - Watching for changes in ./src
1345
+ - Watching for changes in ./public
1346
+ `)
1347
+ !globalThis.isBuilding ? Build() : null
1348
+
1349
+
1350
+ Array.from(Array(3).keys()).forEach((i) => {
1351
+ let p = `${process.cwd()}${i == 0 ? '/pages/' : i == 1 ? '/src/' : '/public/'}`
1352
+ watch(p
1353
+ , { recursive: true }, (event, filename) => {
1354
+ if (event == 'change'
1355
+ && !globalThis.isBuilding
1356
+ ) {
1357
+
1358
+ Build()
1359
+ }
1360
+ }).on('error', (err) => console.log(err))
1260
1361
  })
1261
- .then(async (data) => {
1262
- cache[path] = data
1263
-
1264
- data = await handletemplate(new Function(`return \`${data}\`;`)())
1362
+ let p = process.argv[process.argv.indexOf('--watch') + 1] || process.env.PORT || 3000
1363
+
1364
+ process.env.PORT = p
1365
+ s()
1366
+
1367
+ globalThis.listen = true;
1368
+
1369
+ break;
1370
+ case process.argv.includes('--build') && !process.argv.includes('--watch') && !process.argv.includes('--serve'):
1371
+ globalThis.devMode = false
1372
+ console.log(`
1373
+ Vader.js v1.3.3
1374
+ Building to ./dist
1375
+ `)
1376
+ Build()
1377
+
1378
+ break;
1379
+ case process.argv.includes('--serve') && !process.argv.includes('--watch') && !process.argv.includes('--build'):
1380
+ let port = process.argv[process.argv.indexOf('--serve') + 1] || 3000
1381
+ process.env.PORT = port
1382
+ globalThis.devMode = false
1383
+ console.log(`
1384
+ Vader.js v1.3.3
1385
+ Serving ./dist on port ${port}
1386
+ url: http://localhost:${port}
1387
+ `)
1388
+ s()
1389
+ break;
1390
+ default:
1391
+ console.log(`
1392
+ Vader.js is a reactive framework for building interactive applications for the web built ontop of bun.js!
1265
1393
 
1266
- return data
1267
- });
1268
- }
1269
-
1270
- };
1394
+ Usage: vader <command>
1395
+
1396
+ Commands:
1397
+ --watch (port) Watch the pages folder for changes with hot reloading
1398
+
1399
+ --build Build the project to ./dist
1400
+
1401
+ --serve (400) Serve the project on a port (default 3000 or process.env.PORT)
1402
+
1403
+ Learn more about vader: https://vader-js.pages.dev/
1404
+
1405
+ `)
1406
+ break;
1271
1407
 
1272
- export default Vader;
1408
+ }