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/README.md +243 -0
- package/client/index.js +705 -0
- package/package.json +26 -20
- package/runtime/index.html +20 -0
- package/runtime/router.js +1 -0
- package/runtime/vader.js +1 -0
- package/vader.js +1366 -1200
- package/.vscode/settings.json +0 -5
- package/.vscode/vaderjs.autosense.json +0 -5
- package/index.js +0 -12
- package/jsconfig.json +0 -17
- package/readme.md +0 -262
- package/tsconfig.json +0 -18
- package/vader-min.js +0 -1
- package/vaderRouter.js +0 -231
- package/worker-min.js +0 -1
- package/worker.js +0 -223
package/vader.js
CHANGED
|
@@ -1,1272 +1,1438 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
566
|
+
|
|
567
|
+
children ? childs.push({ parent: name, children: children }) : null;
|
|
46
568
|
}
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
671
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
1214
|
+
bundleSize += fs.statSync(process.cwd() + "/src/" + name).size;
|
|
1215
|
+
await writer(process.cwd() + "/dist/src/" + name, data);
|
|
1216
|
+
})
|
|
714
1217
|
|
|
715
|
-
|
|
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
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
let
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
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
|
-
|
|
928
|
-
|
|
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
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
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
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
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
|
-
.
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1438
|
+
}
|