vaderjs 1.3.1 → 1.3.3-5b5a772ebe58

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