juxscript 1.1.91 → 1.1.92
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/dom-structure-map.json +1 -1
- package/lib/components/stack/BaseStack.ts +16 -16
- package/machinery/compiler3.js +282 -113
- package/package.json +1 -1
package/dom-structure-map.json
CHANGED
|
@@ -36,8 +36,8 @@ export abstract class BaseStack extends BaseComponent<StackState> {
|
|
|
36
36
|
private _inlineStyles: Map<string, string> = new Map();
|
|
37
37
|
|
|
38
38
|
constructor(id: string, children: Record<string, any> | any[], options: StackOptions = {}) {
|
|
39
|
-
const childArray = Array.isArray(children)
|
|
40
|
-
? children
|
|
39
|
+
const childArray = Array.isArray(children)
|
|
40
|
+
? children
|
|
41
41
|
: Object.values(children);
|
|
42
42
|
|
|
43
43
|
super(id, {
|
|
@@ -108,7 +108,7 @@ export abstract class BaseStack extends BaseComponent<StackState> {
|
|
|
108
108
|
const styleString = Array.from(this._inlineStyles.entries())
|
|
109
109
|
.map(([prop, val]) => `${prop}: ${val}`)
|
|
110
110
|
.join('; ');
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
this.state.style = styleString;
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -211,33 +211,33 @@ export abstract class BaseStack extends BaseComponent<StackState> {
|
|
|
211
211
|
|
|
212
212
|
protected buildClasses(): string {
|
|
213
213
|
const { spacing, align, justify, divider, responsive } = this.state;
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
const classes = [this.baseClassName];
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
if (spacing !== 'default') {
|
|
218
218
|
classes.push(`${this.baseClassName}-${spacing}`);
|
|
219
219
|
}
|
|
220
|
-
|
|
220
|
+
|
|
221
221
|
if (align) {
|
|
222
222
|
classes.push(`jux-stack-${align}`);
|
|
223
223
|
}
|
|
224
|
-
|
|
224
|
+
|
|
225
225
|
if (justify) {
|
|
226
226
|
classes.push(`jux-stack-justify-${justify}`);
|
|
227
227
|
}
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
if (divider) {
|
|
230
230
|
classes.push(`${this.baseClassName}-divider`);
|
|
231
231
|
}
|
|
232
|
-
|
|
232
|
+
|
|
233
233
|
if (responsive) {
|
|
234
234
|
classes.push(`${this.baseClassName}-responsive`);
|
|
235
235
|
}
|
|
236
|
-
|
|
236
|
+
|
|
237
237
|
if (this.state.class) {
|
|
238
238
|
classes.push(this.state.class);
|
|
239
239
|
}
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
return classes.join(' ');
|
|
242
242
|
}
|
|
243
243
|
|
|
@@ -277,17 +277,17 @@ export abstract class BaseStack extends BaseComponent<StackState> {
|
|
|
277
277
|
|
|
278
278
|
private getChildStyle(index: number): string {
|
|
279
279
|
const { childStyles } = this.state;
|
|
280
|
-
|
|
280
|
+
|
|
281
281
|
if (!childStyles) return '';
|
|
282
|
-
|
|
282
|
+
|
|
283
283
|
if (typeof childStyles === 'function') {
|
|
284
284
|
return childStyles(index);
|
|
285
285
|
}
|
|
286
|
-
|
|
286
|
+
|
|
287
287
|
if (Array.isArray(childStyles)) {
|
|
288
288
|
return childStyles[index] || '';
|
|
289
289
|
}
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
return '';
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -301,7 +301,7 @@ export abstract class BaseStack extends BaseComponent<StackState> {
|
|
|
301
301
|
const wrapper = document.createElement('div');
|
|
302
302
|
wrapper.className = this.buildClasses();
|
|
303
303
|
wrapper.id = this._id;
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
if (this.state.style) {
|
|
306
306
|
wrapper.setAttribute('style', this.state.style);
|
|
307
307
|
}
|
package/machinery/compiler3.js
CHANGED
|
@@ -13,7 +13,7 @@ export class JuxCompiler {
|
|
|
13
13
|
this.config = config;
|
|
14
14
|
this.srcDir = config.srcDir || './jux';
|
|
15
15
|
this.distDir = config.distDir || './.jux-dist';
|
|
16
|
-
this.publicDir = config.publicDir || './public';
|
|
16
|
+
this.publicDir = config.publicDir || './public';
|
|
17
17
|
this.defaults = config.defaults || {};
|
|
18
18
|
this.paths = config.paths || {};
|
|
19
19
|
this._juxscriptExports = null;
|
|
@@ -53,24 +53,56 @@ export class JuxCompiler {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
/**
|
|
57
|
+
* ✅ Recursively scan for .jux and .js files
|
|
58
|
+
*/
|
|
59
|
+
scanFiles(dir = this.srcDir, baseDir = this.srcDir) {
|
|
60
|
+
const views = [];
|
|
61
|
+
const dataModules = [];
|
|
62
|
+
const sharedModules = [];
|
|
59
63
|
|
|
60
|
-
const
|
|
64
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
// Skip hidden files/folders
|
|
68
|
+
if (entry.name.startsWith('.')) continue;
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const fullPath = path.join(dir, entry.name);
|
|
71
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
72
|
+
|
|
73
|
+
if (entry.isDirectory()) {
|
|
74
|
+
// ✅ Recurse into subdirectories
|
|
75
|
+
const nested = this.scanFiles(fullPath, baseDir);
|
|
76
|
+
views.push(...nested.views);
|
|
77
|
+
dataModules.push(...nested.dataModules);
|
|
78
|
+
sharedModules.push(...nested.sharedModules);
|
|
79
|
+
} else if ((entry.name.endsWith('.jux') || entry.name.endsWith('.js')) && !this.isAssetFile(entry.name)) {
|
|
80
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
81
|
+
|
|
82
|
+
// ✅ Generate name from folder structure
|
|
83
|
+
// Example: abc/juxabc.jux -> abc_juxabc
|
|
84
|
+
const nameFromPath = relativePath
|
|
85
|
+
.replace(/\.[^/.]+$/, '') // Remove extension
|
|
86
|
+
.replace(/\\/g, '/') // Normalize Windows paths
|
|
87
|
+
.replace(/\//g, '_') // Folder separator -> underscore
|
|
88
|
+
.replace(/[^a-zA-Z0-9_]/g, '_'); // Sanitize
|
|
89
|
+
|
|
90
|
+
const module = {
|
|
91
|
+
name: nameFromPath,
|
|
92
|
+
file: relativePath,
|
|
93
|
+
fullPath: fullPath,
|
|
94
|
+
content: content
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (entry.name.includes('data')) {
|
|
98
|
+
dataModules.push(module);
|
|
99
|
+
} else if (/export\s+(function|const|let|var|class)\s+/.test(content)) {
|
|
100
|
+
sharedModules.push(module);
|
|
101
|
+
} else {
|
|
102
|
+
views.push(module);
|
|
103
|
+
}
|
|
72
104
|
}
|
|
73
|
-
}
|
|
105
|
+
}
|
|
74
106
|
|
|
75
107
|
return { views, dataModules, sharedModules };
|
|
76
108
|
}
|
|
@@ -171,7 +203,220 @@ export class JuxCompiler {
|
|
|
171
203
|
}
|
|
172
204
|
|
|
173
205
|
/**
|
|
174
|
-
* Generate
|
|
206
|
+
* ✅ Generate routes based on folder structure
|
|
207
|
+
*/
|
|
208
|
+
_generateRouter(views) {
|
|
209
|
+
let routeMap = '';
|
|
210
|
+
|
|
211
|
+
views.forEach(v => {
|
|
212
|
+
// ✅ Generate route from folder structure
|
|
213
|
+
// abc/juxabc.jux -> /abc/juxabc
|
|
214
|
+
// index.jux -> /
|
|
215
|
+
// abc/index.jux -> /abc
|
|
216
|
+
|
|
217
|
+
const routePath = this._generateRoutePath(v.file);
|
|
218
|
+
const functionName = this._generateFunctionName(v.name);
|
|
219
|
+
|
|
220
|
+
routeMap += ` '${routePath}': render${functionName},\n`;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return `
|
|
224
|
+
// --- JUX SOURCE LOADER ---
|
|
225
|
+
var __juxSources = null;
|
|
226
|
+
async function __juxLoadSources() {
|
|
227
|
+
if (__juxSources) return __juxSources;
|
|
228
|
+
try {
|
|
229
|
+
var res = await fetch('/__jux_sources.json');
|
|
230
|
+
__juxSources = await res.json();
|
|
231
|
+
} catch (e) {
|
|
232
|
+
__juxSources = {};
|
|
233
|
+
}
|
|
234
|
+
return __juxSources;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function __juxFindSource(stack) {
|
|
238
|
+
var match = stack.match(/render([A-Z][a-zA-Z0-9_]*)/);
|
|
239
|
+
if (match) {
|
|
240
|
+
var funcName = match[1];
|
|
241
|
+
for (var file in __juxSources || {}) {
|
|
242
|
+
var normalized = __juxSources[file].name
|
|
243
|
+
.split('_')
|
|
244
|
+
.map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
|
|
245
|
+
.join('');
|
|
246
|
+
|
|
247
|
+
if (normalized === funcName) {
|
|
248
|
+
return { file: file, source: __juxSources[file], viewName: funcName };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// --- JUX RUNTIME ERROR OVERLAY ---
|
|
256
|
+
var __juxErrorOverlay = {
|
|
257
|
+
styles: \`
|
|
258
|
+
#__jux-error-overlay {
|
|
259
|
+
position: fixed; inset: 0; z-index: 99999;
|
|
260
|
+
background: rgba(0, 0, 0, 0.4);
|
|
261
|
+
display: flex; align-items: center; justify-content: center;
|
|
262
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
|
|
263
|
+
opacity: 0; transition: opacity 0.2s ease-out; backdrop-filter: blur(2px);
|
|
264
|
+
}
|
|
265
|
+
#__jux-error-overlay.visible { opacity: 1; }
|
|
266
|
+
#__jux-error-overlay * { box-sizing: border-box; }
|
|
267
|
+
.__jux-modal {
|
|
268
|
+
background: #f8f9fa; border-radius: 4px;
|
|
269
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
|
|
270
|
+
max-width: 80vw; width: 90%; max-height: 90vh;
|
|
271
|
+
overflow: hidden; display: flex; flex-direction: column;
|
|
272
|
+
transform: translateY(10px); transition: transform 0.2s ease-out;
|
|
273
|
+
}
|
|
274
|
+
#__jux-error-overlay.visible .__jux-modal { transform: translateY(0); }
|
|
275
|
+
.__jux-header { background: #fff; padding: 20px 24px; border-bottom: 1px solid #e5e7eb; }
|
|
276
|
+
.__jux-header h3 { margin: 0 0 6px; font-weight: 600; font-size: 11px; color: #9ca3af; text-transform: uppercase; }
|
|
277
|
+
.__jux-header h1 { margin: 0 0 8px; font-size: 18px; font-weight: 600; color: #dc2626; line-height: 1.3; }
|
|
278
|
+
.__jux-header .file-info { color: #6b7280; font-size: 13px; }
|
|
279
|
+
.__jux-header .file-info strong { color: #dc2626; font-weight: 600; }
|
|
280
|
+
.__jux-source { padding: 16px 24px; overflow: auto; flex: 1; background: #f8f9fa; }
|
|
281
|
+
.__jux-code { background: #fff; border-radius: 8px; overflow: hidden; border: 1px solid #e5e7eb; }
|
|
282
|
+
.__jux-code-line { display: flex; font-size: 13px; line-height: 1.7; }
|
|
283
|
+
.__jux-code-line.error { background: #fef2f2; }
|
|
284
|
+
.__jux-code-line.error .__jux-line-code { color: #dc2626; font-weight: 500; }
|
|
285
|
+
.__jux-code-line.context { background: #fefce8; }
|
|
286
|
+
.__jux-line-num { min-width: 44px; padding: 4px 12px; text-align: right; color: #9ca3af; background: #f9fafb; border-right: 1px solid #e5e7eb; font-size: 12px; }
|
|
287
|
+
.__jux-code-line.error .__jux-line-num { background: #fef2f2; color: #dc2626; }
|
|
288
|
+
.__jux-line-code { flex: 1; padding: 4px 16px; color: #374151; white-space: pre; overflow-x: auto; }
|
|
289
|
+
.__jux-footer { padding: 16px 24px; background: #fff; border-top: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center; }
|
|
290
|
+
.__jux-tip { color: #6b7280; font-size: 12px; }
|
|
291
|
+
.__jux-dismiss { background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; }
|
|
292
|
+
.__jux-dismiss:hover { background: #e5e7eb; }
|
|
293
|
+
.__jux-no-source { color: #6b7280; padding: 24px; text-align: center; }
|
|
294
|
+
.__jux-no-source pre { background: #fff; padding: 16px; border-radius: 8px; margin-top: 16px; font-size: 11px; color: #6b7280; overflow-x: auto; text-align: left; border: 1px solid #e5e7eb; }
|
|
295
|
+
\`,
|
|
296
|
+
|
|
297
|
+
show: async function(error, title) {
|
|
298
|
+
title = title || 'Runtime Error';
|
|
299
|
+
var existing = document.getElementById('__jux-error-overlay');
|
|
300
|
+
if (existing) existing.remove();
|
|
301
|
+
await __juxLoadSources();
|
|
302
|
+
|
|
303
|
+
var overlay = document.createElement('div');
|
|
304
|
+
overlay.id = '__jux-error-overlay';
|
|
305
|
+
var stack = error && error.stack ? error.stack : '';
|
|
306
|
+
var msg = error && error.message ? error.message : String(error);
|
|
307
|
+
var found = __juxFindSource(stack);
|
|
308
|
+
var sourceHtml = '', fileInfo = '';
|
|
309
|
+
|
|
310
|
+
if (found && found.source && found.source.lines) {
|
|
311
|
+
var lines = found.source.lines;
|
|
312
|
+
fileInfo = '<span class="file-info">in <strong>' + found.file + '</strong></span>';
|
|
313
|
+
var errorLineIndex = -1;
|
|
314
|
+
var errorMethod = msg.match(/\\.([a-zA-Z]+)\\s*is not a function/);
|
|
315
|
+
if (errorMethod) {
|
|
316
|
+
for (var i = 0; i < lines.length; i++) {
|
|
317
|
+
if (lines[i].indexOf('.' + errorMethod[1]) > -1) { errorLineIndex = i; break; }
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (errorLineIndex === -1) {
|
|
321
|
+
for (var i = 0; i < lines.length; i++) {
|
|
322
|
+
if (lines[i].indexOf('throw ') > -1) { errorLineIndex = i; break; }
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
var contextStart = Math.max(0, errorLineIndex - 3);
|
|
326
|
+
var contextEnd = Math.min(lines.length - 1, errorLineIndex + 5);
|
|
327
|
+
if (errorLineIndex === -1) { contextStart = 0; contextEnd = Math.min(14, lines.length - 1); }
|
|
328
|
+
|
|
329
|
+
for (var i = contextStart; i <= contextEnd; i++) {
|
|
330
|
+
var isError = (i === errorLineIndex);
|
|
331
|
+
var isContext = !isError && errorLineIndex > -1 && Math.abs(i - errorLineIndex) <= 2;
|
|
332
|
+
var lineClass = isError ? ' error' : (isContext ? ' context' : '');
|
|
333
|
+
var lineCode = lines[i].replace(/</g, '<').replace(/>/g, '>') || ' ';
|
|
334
|
+
sourceHtml += '<div class="__jux-code-line' + lineClass + '"><span class="__jux-line-num">' + (i + 1) + '</span><span class="__jux-line-code">' + lineCode + '</span></div>';
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
sourceHtml = '<div class="__jux-no-source"><p>Could not locate source file.</p><pre>' + stack + '</pre></div>';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
overlay.innerHTML = '<style>' + this.styles + '</style><div class="__jux-modal"><div class="__jux-header"><h3>' + title + '</h3><h1>' + msg + '</h1>' + fileInfo + '</div><div class="__jux-source"><div class="__jux-code">' + sourceHtml + '</div></div><div class="__jux-footer"><span class="__jux-tip">💡 Fix the error and save to reload</span><button class="__jux-dismiss" onclick="document.getElementById(\\'__jux-error-overlay\\').remove()">Dismiss</button></div></div>';
|
|
341
|
+
document.body.appendChild(overlay);
|
|
342
|
+
requestAnimationFrame(function() { overlay.classList.add('visible'); });
|
|
343
|
+
console.error(title + ':', error);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
window.addEventListener('error', function(e) { __juxErrorOverlay.show(e.error || new Error(e.message), 'Uncaught Error'); }, true);
|
|
348
|
+
window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection'); }, true);
|
|
349
|
+
|
|
350
|
+
// --- JUX ROUTER ---
|
|
351
|
+
const routes = {\n${routeMap}};
|
|
352
|
+
|
|
353
|
+
async function navigate(path) {
|
|
354
|
+
const view = routes[path];
|
|
355
|
+
if (!view) {
|
|
356
|
+
document.getElementById('app').innerHTML = '<h1 style="padding:40px;">404 - Not Found</h1>';
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
document.getElementById('app').innerHTML = '';
|
|
360
|
+
var overlay = document.getElementById('__jux-error-overlay');
|
|
361
|
+
if (overlay) overlay.remove();
|
|
362
|
+
try { await view(); } catch (err) { __juxErrorOverlay.show(err, 'Jux Render Error'); }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
document.addEventListener('click', e => {
|
|
366
|
+
const a = e.target.closest('a');
|
|
367
|
+
if (!a || a.dataset.router === 'false') return;
|
|
368
|
+
try { if (new URL(a.href, location.origin).origin !== location.origin) return; } catch { return; }
|
|
369
|
+
e.preventDefault();
|
|
370
|
+
history.pushState({}, '', a.href);
|
|
371
|
+
navigate(new URL(a.href, location.origin).pathname);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
window.addEventListener('popstate', () => navigate(location.pathname));
|
|
375
|
+
navigate(location.pathname);
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* ✅ Generate route path from file path
|
|
381
|
+
* Examples:
|
|
382
|
+
* index.jux -> /
|
|
383
|
+
* abc/juxabc.jux -> /abc/juxabc
|
|
384
|
+
* abc/index.jux -> /abc
|
|
385
|
+
*/
|
|
386
|
+
_generateRoutePath(filePath) {
|
|
387
|
+
// Normalize separators
|
|
388
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
389
|
+
|
|
390
|
+
// Remove extension
|
|
391
|
+
const withoutExt = normalized.replace(/\.[^/.]+$/, '');
|
|
392
|
+
|
|
393
|
+
// Handle index files
|
|
394
|
+
if (withoutExt === 'index') {
|
|
395
|
+
return '/';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Remove trailing /index
|
|
399
|
+
const cleaned = withoutExt.replace(/\/index$/, '');
|
|
400
|
+
|
|
401
|
+
// Ensure leading slash
|
|
402
|
+
return '/' + cleaned.toLowerCase();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* ✅ Generate PascalCase function name from sanitized name
|
|
407
|
+
* Examples:
|
|
408
|
+
* abc_juxabc -> AbcJuxabc
|
|
409
|
+
* index -> Index
|
|
410
|
+
*/
|
|
411
|
+
_generateFunctionName(name) {
|
|
412
|
+
return name
|
|
413
|
+
.split('_')
|
|
414
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
415
|
+
.join('');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* ✅ Generate entry point with nested folder support
|
|
175
420
|
*/
|
|
176
421
|
generateEntryPoint(views, dataModules, sharedModules) {
|
|
177
422
|
let entry = `// Auto-generated JUX entry point\n\n`;
|
|
@@ -209,7 +454,7 @@ export class JuxCompiler {
|
|
|
209
454
|
entry += `\n// --- VIEW FUNCTIONS ---\n`;
|
|
210
455
|
|
|
211
456
|
views.forEach(v => {
|
|
212
|
-
const
|
|
457
|
+
const functionName = this._generateFunctionName(v.name);
|
|
213
458
|
allIssues.push(...this.validateViewCode(v.name, v.content));
|
|
214
459
|
|
|
215
460
|
sourceSnapshot[v.file] = {
|
|
@@ -222,7 +467,7 @@ export class JuxCompiler {
|
|
|
222
467
|
let viewCode = this.removeImports(v.content).replace(/^\s*export\s+default\s+.*$/gm, '');
|
|
223
468
|
const asyncPrefix = viewCode.includes('await ') ? 'async ' : '';
|
|
224
469
|
|
|
225
|
-
entry += `\n${asyncPrefix}function render${
|
|
470
|
+
entry += `\n${asyncPrefix}function render${functionName}() {\n${viewCode}\n}\n`;
|
|
226
471
|
});
|
|
227
472
|
|
|
228
473
|
dataModules.forEach(m => {
|
|
@@ -257,10 +502,17 @@ export class JuxCompiler {
|
|
|
257
502
|
|
|
258
503
|
_generateRouter(views) {
|
|
259
504
|
let routeMap = '';
|
|
505
|
+
|
|
260
506
|
views.forEach(v => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
507
|
+
// ✅ Generate route from folder structure
|
|
508
|
+
// abc/juxabc.jux -> /abc/juxabc
|
|
509
|
+
// index.jux -> /
|
|
510
|
+
// abc/index.jux -> /abc
|
|
511
|
+
|
|
512
|
+
const routePath = this._generateRoutePath(v.file);
|
|
513
|
+
const functionName = this._generateFunctionName(v.name);
|
|
514
|
+
|
|
515
|
+
routeMap += ` '${routePath}': render${functionName},\n`;
|
|
264
516
|
});
|
|
265
517
|
|
|
266
518
|
return `
|
|
@@ -278,12 +530,17 @@ async function __juxLoadSources() {
|
|
|
278
530
|
}
|
|
279
531
|
|
|
280
532
|
function __juxFindSource(stack) {
|
|
281
|
-
var match = stack.match(/render(
|
|
533
|
+
var match = stack.match(/render([A-Z][a-zA-Z0-9_]*)/);
|
|
282
534
|
if (match) {
|
|
283
|
-
var
|
|
535
|
+
var funcName = match[1];
|
|
284
536
|
for (var file in __juxSources || {}) {
|
|
285
|
-
|
|
286
|
-
|
|
537
|
+
var normalized = __juxSources[file].name
|
|
538
|
+
.split('_')
|
|
539
|
+
.map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
|
|
540
|
+
.join('');
|
|
541
|
+
|
|
542
|
+
if (normalized === funcName) {
|
|
543
|
+
return { file: file, source: __juxSources[file], viewName: funcName };
|
|
287
544
|
}
|
|
288
545
|
}
|
|
289
546
|
}
|
|
@@ -414,94 +671,6 @@ navigate(location.pathname);
|
|
|
414
671
|
`;
|
|
415
672
|
}
|
|
416
673
|
|
|
417
|
-
async build() {
|
|
418
|
-
console.log('🚀 JUX Build\n');
|
|
419
|
-
|
|
420
|
-
const juxscriptPath = this.findJuxscriptPath();
|
|
421
|
-
if (!juxscriptPath) {
|
|
422
|
-
console.error('❌ Could not locate juxscript package');
|
|
423
|
-
return { success: false, errors: [{ message: 'juxscript not found' }], warnings: [] };
|
|
424
|
-
}
|
|
425
|
-
console.log(`📦 Using: ${juxscriptPath}`);
|
|
426
|
-
|
|
427
|
-
await this.loadJuxscriptExports();
|
|
428
|
-
|
|
429
|
-
if (fs.existsSync(this.distDir)) {
|
|
430
|
-
fs.rmSync(this.distDir, { recursive: true, force: true });
|
|
431
|
-
}
|
|
432
|
-
fs.mkdirSync(this.distDir, { recursive: true });
|
|
433
|
-
|
|
434
|
-
// ✅ Copy public folder if exists
|
|
435
|
-
this.copyPublicFolder();
|
|
436
|
-
|
|
437
|
-
const { views, dataModules, sharedModules } = this.scanFiles();
|
|
438
|
-
console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
|
|
439
|
-
|
|
440
|
-
// Copy data/shared modules to dist
|
|
441
|
-
const juxDistDir = path.join(this.distDir, 'jux');
|
|
442
|
-
fs.mkdirSync(juxDistDir, { recursive: true });
|
|
443
|
-
[...dataModules, ...sharedModules].forEach(m => {
|
|
444
|
-
fs.writeFileSync(path.join(juxDistDir, m.file), m.content);
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
const entryContent = this.generateEntryPoint(views, dataModules, sharedModules);
|
|
448
|
-
const entryPath = path.join(this.distDir, 'entry.js');
|
|
449
|
-
fs.writeFileSync(entryPath, entryContent);
|
|
450
|
-
|
|
451
|
-
const snapshotPath = path.join(this.distDir, '__jux_sources.json');
|
|
452
|
-
fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
453
|
-
console.log(`📸 Source snapshot written`);
|
|
454
|
-
|
|
455
|
-
const validation = this.reportValidationIssues();
|
|
456
|
-
if (!validation.isValid) {
|
|
457
|
-
console.log('🛑 BUILD FAILED\n');
|
|
458
|
-
return { success: false, errors: validation.errors, warnings: validation.warnings };
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
try {
|
|
462
|
-
await esbuild.build({
|
|
463
|
-
entryPoints: [entryPath],
|
|
464
|
-
bundle: true,
|
|
465
|
-
outfile: path.join(this.distDir, 'bundle.js'),
|
|
466
|
-
format: 'esm',
|
|
467
|
-
platform: 'browser',
|
|
468
|
-
target: 'esnext',
|
|
469
|
-
sourcemap: true,
|
|
470
|
-
loader: { '.jux': 'js', '.css': 'empty' },
|
|
471
|
-
plugins: [{
|
|
472
|
-
name: 'juxscript-resolver',
|
|
473
|
-
setup: (build) => {
|
|
474
|
-
build.onResolve({ filter: /^juxscript$/ }, () => ({ path: juxscriptPath }));
|
|
475
|
-
}
|
|
476
|
-
}],
|
|
477
|
-
});
|
|
478
|
-
console.log('✅ esbuild complete');
|
|
479
|
-
} catch (err) {
|
|
480
|
-
console.error('❌ esbuild failed:', err);
|
|
481
|
-
return { success: false, errors: [{ message: err.message }], warnings: [] };
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const html = `<!DOCTYPE html>
|
|
485
|
-
<html lang="en">
|
|
486
|
-
<head>
|
|
487
|
-
<meta charset="UTF-8">
|
|
488
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
489
|
-
<title>JUX App</title>
|
|
490
|
-
<script type="module" src="./bundle.js"></script>
|
|
491
|
-
</head>
|
|
492
|
-
<body>
|
|
493
|
-
<div id="app"></div>
|
|
494
|
-
</body>
|
|
495
|
-
</html>`;
|
|
496
|
-
fs.writeFileSync(path.join(this.distDir, 'index.html'), html);
|
|
497
|
-
|
|
498
|
-
fs.unlinkSync(entryPath);
|
|
499
|
-
fs.rmSync(juxDistDir, { recursive: true, force: true });
|
|
500
|
-
|
|
501
|
-
console.log(`\n✅ Build Complete!\n`);
|
|
502
|
-
return { success: true, errors: [], warnings: validation.warnings };
|
|
503
|
-
}
|
|
504
|
-
|
|
505
674
|
/**
|
|
506
675
|
* Copy public folder contents to dist
|
|
507
676
|
*/
|