vaderjs 1.3.2 → 1.3.3-2124766be812

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