juxscript 1.1.91 → 1.1.93
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 +156 -251
- 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,46 @@ export class JuxCompiler {
|
|
|
171
203
|
}
|
|
172
204
|
|
|
173
205
|
/**
|
|
174
|
-
* Generate
|
|
206
|
+
* ✅ Generate route path from file path
|
|
207
|
+
* Examples:
|
|
208
|
+
* index.jux -> /
|
|
209
|
+
* abc/juxabc.jux -> /abc/juxabc
|
|
210
|
+
* abc/index.jux -> /abc
|
|
211
|
+
*/
|
|
212
|
+
_generateRoutePath(filePath) {
|
|
213
|
+
// Normalize separators
|
|
214
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
215
|
+
|
|
216
|
+
// Remove extension
|
|
217
|
+
const withoutExt = normalized.replace(/\.[^/.]+$/, '');
|
|
218
|
+
|
|
219
|
+
// Handle index files
|
|
220
|
+
if (withoutExt === 'index') {
|
|
221
|
+
return '/';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Remove trailing /index
|
|
225
|
+
const cleaned = withoutExt.replace(/\/index$/, '');
|
|
226
|
+
|
|
227
|
+
// Ensure leading slash
|
|
228
|
+
return '/' + cleaned.toLowerCase();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* ✅ Generate PascalCase function name from sanitized name
|
|
233
|
+
* Examples:
|
|
234
|
+
* abc_juxabc -> AbcJuxabc
|
|
235
|
+
* index -> Index
|
|
236
|
+
*/
|
|
237
|
+
_generateFunctionName(name) {
|
|
238
|
+
return name
|
|
239
|
+
.split('_')
|
|
240
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
241
|
+
.join('');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* ✅ Generate entry point with nested folder support
|
|
175
246
|
*/
|
|
176
247
|
generateEntryPoint(views, dataModules, sharedModules) {
|
|
177
248
|
let entry = `// Auto-generated JUX entry point\n\n`;
|
|
@@ -209,7 +280,7 @@ export class JuxCompiler {
|
|
|
209
280
|
entry += `\n// --- VIEW FUNCTIONS ---\n`;
|
|
210
281
|
|
|
211
282
|
views.forEach(v => {
|
|
212
|
-
const
|
|
283
|
+
const functionName = this._generateFunctionName(v.name);
|
|
213
284
|
allIssues.push(...this.validateViewCode(v.name, v.content));
|
|
214
285
|
|
|
215
286
|
sourceSnapshot[v.file] = {
|
|
@@ -222,7 +293,7 @@ export class JuxCompiler {
|
|
|
222
293
|
let viewCode = this.removeImports(v.content).replace(/^\s*export\s+default\s+.*$/gm, '');
|
|
223
294
|
const asyncPrefix = viewCode.includes('await ') ? 'async ' : '';
|
|
224
295
|
|
|
225
|
-
entry += `\n${asyncPrefix}function render${
|
|
296
|
+
entry += `\n${asyncPrefix}function render${functionName}() {\n${viewCode}\n}\n`;
|
|
226
297
|
});
|
|
227
298
|
|
|
228
299
|
dataModules.forEach(m => {
|
|
@@ -239,267 +310,101 @@ export class JuxCompiler {
|
|
|
239
310
|
}
|
|
240
311
|
|
|
241
312
|
reportValidationIssues() {
|
|
242
|
-
|
|
243
|
-
const errors = issues.filter(i => i.type === 'error');
|
|
244
|
-
const warnings = issues.filter(i => i.type === 'warning');
|
|
245
|
-
|
|
246
|
-
if (issues.length > 0) {
|
|
247
|
-
console.log('\n⚠️ Validation Issues:\n');
|
|
248
|
-
issues.forEach(issue => {
|
|
249
|
-
const icon = issue.type === 'error' ? '❌' : '⚠️';
|
|
250
|
-
console.log(`${icon} [${issue.view}:${issue.line}] ${issue.message}`);
|
|
251
|
-
});
|
|
252
|
-
console.log('');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return { isValid: errors.length === 0, errors, warnings };
|
|
256
|
-
}
|
|
313
|
+
if (!this._validationIssues || this._validationIssues.length === 0) return;
|
|
257
314
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (v.name.toLowerCase() === 'index') routeMap += ` '/': render${cap},\n`;
|
|
263
|
-
routeMap += ` '/${v.name.toLowerCase()}': render${cap},\n`;
|
|
315
|
+
console.log('\n⚠️ Validation Issues:\n');
|
|
316
|
+
this._validationIssues.forEach(issue => {
|
|
317
|
+
const icon = issue.type === 'error' ? '❌' : '⚠️';
|
|
318
|
+
console.log(` ${icon} ${issue.view}:${issue.line} - ${issue.message}`);
|
|
264
319
|
});
|
|
265
|
-
|
|
266
|
-
return `
|
|
267
|
-
// --- JUX SOURCE LOADER ---
|
|
268
|
-
var __juxSources = null;
|
|
269
|
-
async function __juxLoadSources() {
|
|
270
|
-
if (__juxSources) return __juxSources;
|
|
271
|
-
try {
|
|
272
|
-
var res = await fetch('/__jux_sources.json');
|
|
273
|
-
__juxSources = await res.json();
|
|
274
|
-
} catch (e) {
|
|
275
|
-
__juxSources = {};
|
|
276
|
-
}
|
|
277
|
-
return __juxSources;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function __juxFindSource(stack) {
|
|
281
|
-
var match = stack.match(/render(\\w+)/);
|
|
282
|
-
if (match) {
|
|
283
|
-
var viewName = match[1].toLowerCase();
|
|
284
|
-
for (var file in __juxSources || {}) {
|
|
285
|
-
if (__juxSources[file].name.toLowerCase() === viewName) {
|
|
286
|
-
return { file: file, source: __juxSources[file], viewName: match[1] };
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// --- JUX RUNTIME ERROR OVERLAY ---
|
|
294
|
-
var __juxErrorOverlay = {
|
|
295
|
-
styles: \`
|
|
296
|
-
#__jux-error-overlay {
|
|
297
|
-
position: fixed; inset: 0; z-index: 99999;
|
|
298
|
-
background: rgba(0, 0, 0, 0.4);
|
|
299
|
-
display: flex; align-items: center; justify-content: center;
|
|
300
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
|
|
301
|
-
opacity: 0; transition: opacity 0.2s ease-out; backdrop-filter: blur(2px);
|
|
302
|
-
}
|
|
303
|
-
#__jux-error-overlay.visible { opacity: 1; }
|
|
304
|
-
#__jux-error-overlay * { box-sizing: border-box; }
|
|
305
|
-
.__jux-modal {
|
|
306
|
-
background: #f8f9fa; border-radius: 4px;
|
|
307
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
|
|
308
|
-
max-width: 80vw; width: 90%; max-height: 90vh;
|
|
309
|
-
overflow: hidden; display: flex; flex-direction: column;
|
|
310
|
-
transform: translateY(10px); transition: transform 0.2s ease-out;
|
|
311
|
-
}
|
|
312
|
-
#__jux-error-overlay.visible .__jux-modal { transform: translateY(0); }
|
|
313
|
-
.__jux-header { background: #fff; padding: 20px 24px; border-bottom: 1px solid #e5e7eb; }
|
|
314
|
-
.__jux-header h3 { margin: 0 0 6px; font-weight: 600; font-size: 11px; color: #9ca3af; text-transform: uppercase; }
|
|
315
|
-
.__jux-header h1 { margin: 0 0 8px; font-size: 18px; font-weight: 600; color: #dc2626; line-height: 1.3; }
|
|
316
|
-
.__jux-header .file-info { color: #6b7280; font-size: 13px; }
|
|
317
|
-
.__jux-header .file-info strong { color: #dc2626; font-weight: 600; }
|
|
318
|
-
.__jux-source { padding: 16px 24px; overflow: auto; flex: 1; background: #f8f9fa; }
|
|
319
|
-
.__jux-code { background: #fff; border-radius: 8px; overflow: hidden; border: 1px solid #e5e7eb; }
|
|
320
|
-
.__jux-code-line { display: flex; font-size: 13px; line-height: 1.7; }
|
|
321
|
-
.__jux-code-line.error { background: #fef2f2; }
|
|
322
|
-
.__jux-code-line.error .__jux-line-code { color: #dc2626; font-weight: 500; }
|
|
323
|
-
.__jux-code-line.context { background: #fefce8; }
|
|
324
|
-
.__jux-line-num { min-width: 44px; padding: 4px 12px; text-align: right; color: #9ca3af; background: #f9fafb; border-right: 1px solid #e5e7eb; font-size: 12px; }
|
|
325
|
-
.__jux-code-line.error .__jux-line-num { background: #fef2f2; color: #dc2626; }
|
|
326
|
-
.__jux-line-code { flex: 1; padding: 4px 16px; color: #374151; white-space: pre; overflow-x: auto; }
|
|
327
|
-
.__jux-footer { padding: 16px 24px; background: #fff; border-top: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center; }
|
|
328
|
-
.__jux-tip { color: #6b7280; font-size: 12px; }
|
|
329
|
-
.__jux-dismiss { background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; }
|
|
330
|
-
.__jux-dismiss:hover { background: #e5e7eb; }
|
|
331
|
-
.__jux-no-source { color: #6b7280; padding: 24px; text-align: center; }
|
|
332
|
-
.__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; }
|
|
333
|
-
\`,
|
|
334
|
-
|
|
335
|
-
show: async function(error, title) {
|
|
336
|
-
title = title || 'Runtime Error';
|
|
337
|
-
var existing = document.getElementById('__jux-error-overlay');
|
|
338
|
-
if (existing) existing.remove();
|
|
339
|
-
await __juxLoadSources();
|
|
340
|
-
|
|
341
|
-
var overlay = document.createElement('div');
|
|
342
|
-
overlay.id = '__jux-error-overlay';
|
|
343
|
-
var stack = error && error.stack ? error.stack : '';
|
|
344
|
-
var msg = error && error.message ? error.message : String(error);
|
|
345
|
-
var found = __juxFindSource(stack);
|
|
346
|
-
var sourceHtml = '', fileInfo = '';
|
|
347
|
-
|
|
348
|
-
if (found && found.source && found.source.lines) {
|
|
349
|
-
var lines = found.source.lines;
|
|
350
|
-
fileInfo = '<span class="file-info">in <strong>' + found.file + '</strong></span>';
|
|
351
|
-
var errorLineIndex = -1;
|
|
352
|
-
var errorMethod = msg.match(/\\.([a-zA-Z]+)\\s*is not a function/);
|
|
353
|
-
if (errorMethod) {
|
|
354
|
-
for (var i = 0; i < lines.length; i++) {
|
|
355
|
-
if (lines[i].indexOf('.' + errorMethod[1]) > -1) { errorLineIndex = i; break; }
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
if (errorLineIndex === -1) {
|
|
359
|
-
for (var i = 0; i < lines.length; i++) {
|
|
360
|
-
if (lines[i].indexOf('throw ') > -1) { errorLineIndex = i; break; }
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
var contextStart = Math.max(0, errorLineIndex - 3);
|
|
364
|
-
var contextEnd = Math.min(lines.length - 1, errorLineIndex + 5);
|
|
365
|
-
if (errorLineIndex === -1) { contextStart = 0; contextEnd = Math.min(14, lines.length - 1); }
|
|
366
|
-
|
|
367
|
-
for (var i = contextStart; i <= contextEnd; i++) {
|
|
368
|
-
var isError = (i === errorLineIndex);
|
|
369
|
-
var isContext = !isError && errorLineIndex > -1 && Math.abs(i - errorLineIndex) <= 2;
|
|
370
|
-
var lineClass = isError ? ' error' : (isContext ? ' context' : '');
|
|
371
|
-
var lineCode = lines[i].replace(/</g, '<').replace(/>/g, '>') || ' ';
|
|
372
|
-
sourceHtml += '<div class="__jux-code-line' + lineClass + '"><span class="__jux-line-num">' + (i + 1) + '</span><span class="__jux-line-code">' + lineCode + '</span></div>';
|
|
373
|
-
}
|
|
374
|
-
} else {
|
|
375
|
-
sourceHtml = '<div class="__jux-no-source"><p>Could not locate source file.</p><pre>' + stack + '</pre></div>';
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
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>';
|
|
379
|
-
document.body.appendChild(overlay);
|
|
380
|
-
requestAnimationFrame(function() { overlay.classList.add('visible'); });
|
|
381
|
-
console.error(title + ':', error);
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
window.addEventListener('error', function(e) { __juxErrorOverlay.show(e.error || new Error(e.message), 'Uncaught Error'); }, true);
|
|
386
|
-
window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection'); }, true);
|
|
387
|
-
|
|
388
|
-
// --- JUX ROUTER ---
|
|
389
|
-
const routes = {\n${routeMap}};
|
|
390
|
-
|
|
391
|
-
async function navigate(path) {
|
|
392
|
-
const view = routes[path];
|
|
393
|
-
if (!view) {
|
|
394
|
-
document.getElementById('app').innerHTML = '<h1 style="padding:40px;">404 - Not Found</h1>';
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
document.getElementById('app').innerHTML = '';
|
|
398
|
-
var overlay = document.getElementById('__jux-error-overlay');
|
|
399
|
-
if (overlay) overlay.remove();
|
|
400
|
-
try { await view(); } catch (err) { __juxErrorOverlay.show(err, 'Jux Render Error'); }
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
document.addEventListener('click', e => {
|
|
404
|
-
const a = e.target.closest('a');
|
|
405
|
-
if (!a || a.dataset.router === 'false') return;
|
|
406
|
-
try { if (new URL(a.href, location.origin).origin !== location.origin) return; } catch { return; }
|
|
407
|
-
e.preventDefault();
|
|
408
|
-
history.pushState({}, '', a.href);
|
|
409
|
-
navigate(new URL(a.href, location.origin).pathname);
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
window.addEventListener('popstate', () => navigate(location.pathname));
|
|
413
|
-
navigate(location.pathname);
|
|
414
|
-
`;
|
|
320
|
+
console.log('');
|
|
415
321
|
}
|
|
416
322
|
|
|
323
|
+
/**
|
|
324
|
+
* ✅ Build method - INSIDE the class
|
|
325
|
+
*/
|
|
417
326
|
async build() {
|
|
418
|
-
console.log('
|
|
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 });
|
|
327
|
+
console.log('🔨 Building JUX application...\n');
|
|
433
328
|
|
|
434
|
-
|
|
435
|
-
|
|
329
|
+
try {
|
|
330
|
+
const startTime = Date.now();
|
|
436
331
|
|
|
437
|
-
|
|
438
|
-
|
|
332
|
+
// Scan files
|
|
333
|
+
const { views, dataModules, sharedModules } = this.scanFiles();
|
|
439
334
|
|
|
440
|
-
|
|
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
|
-
});
|
|
335
|
+
console.log(`📂 Found ${views.length} views, ${dataModules.length} data modules, ${sharedModules.length} shared modules\n`);
|
|
446
336
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
fs.writeFileSync(entryPath, entryContent);
|
|
337
|
+
// Load juxscript exports for validation
|
|
338
|
+
await this.loadJuxscriptExports();
|
|
450
339
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
340
|
+
// Ensure dist directory exists
|
|
341
|
+
if (!fs.existsSync(this.distDir)) {
|
|
342
|
+
fs.mkdirSync(this.distDir, { recursive: true });
|
|
343
|
+
}
|
|
454
344
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
345
|
+
// Generate entry point
|
|
346
|
+
const entryCode = this.generateEntryPoint(views, dataModules, sharedModules);
|
|
347
|
+
const entryPath = path.join(this.distDir, 'entry.js');
|
|
348
|
+
fs.writeFileSync(entryPath, entryCode);
|
|
460
349
|
|
|
461
|
-
|
|
350
|
+
// Bundle with esbuild
|
|
351
|
+
console.log('📦 Bundling with esbuild...');
|
|
462
352
|
await esbuild.build({
|
|
463
353
|
entryPoints: [entryPath],
|
|
464
354
|
bundle: true,
|
|
465
|
-
outfile: path.join(this.distDir, 'bundle.js'),
|
|
466
355
|
format: 'esm',
|
|
356
|
+
outfile: path.join(this.distDir, 'bundle.js'),
|
|
467
357
|
platform: 'browser',
|
|
468
|
-
target: '
|
|
358
|
+
target: 'es2020',
|
|
469
359
|
sourcemap: true,
|
|
470
|
-
|
|
471
|
-
plugins: [{
|
|
472
|
-
name: 'juxscript-resolver',
|
|
473
|
-
setup: (build) => {
|
|
474
|
-
build.onResolve({ filter: /^juxscript$/ }, () => ({ path: juxscriptPath }));
|
|
475
|
-
}
|
|
476
|
-
}],
|
|
360
|
+
external: []
|
|
477
361
|
});
|
|
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
362
|
|
|
484
|
-
|
|
363
|
+
// Generate index.html
|
|
364
|
+
console.log('📄 Generating index.html...');
|
|
365
|
+
const indexHtml = `<!DOCTYPE html>
|
|
485
366
|
<html lang="en">
|
|
486
367
|
<head>
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
368
|
+
<meta charset="UTF-8">
|
|
369
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
370
|
+
<title>JUX App</title>
|
|
371
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/juxscript@latest/lib/layouts/default.css">
|
|
491
372
|
</head>
|
|
492
373
|
<body>
|
|
493
|
-
|
|
374
|
+
<div id="app"></div>
|
|
375
|
+
<script type="module" src="/bundle.js"></script>
|
|
494
376
|
</body>
|
|
495
377
|
</html>`;
|
|
496
|
-
fs.writeFileSync(path.join(this.distDir, 'index.html'), html);
|
|
497
378
|
|
|
498
|
-
|
|
499
|
-
fs.rmSync(juxDistDir, { recursive: true, force: true });
|
|
379
|
+
fs.writeFileSync(path.join(this.distDir, 'index.html'), indexHtml);
|
|
500
380
|
|
|
501
|
-
|
|
502
|
-
|
|
381
|
+
// Copy public folder
|
|
382
|
+
this.copyPublicFolder();
|
|
383
|
+
|
|
384
|
+
// Write source snapshot for error overlay
|
|
385
|
+
const snapshotPath = path.join(this.distDir, '__jux_sources.json');
|
|
386
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
387
|
+
|
|
388
|
+
const endTime = Date.now();
|
|
389
|
+
console.log(`\n✅ Build complete in ${endTime - startTime}ms`);
|
|
390
|
+
console.log(` Output: ${this.distDir}`);
|
|
391
|
+
|
|
392
|
+
this.reportValidationIssues();
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
success: true,
|
|
396
|
+
errors: this._validationIssues || [],
|
|
397
|
+
distDir: this.distDir
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
} catch (err) {
|
|
401
|
+
console.error('❌ Build failed:', err.message);
|
|
402
|
+
console.error(err.stack);
|
|
403
|
+
return {
|
|
404
|
+
success: false,
|
|
405
|
+
errors: [err.message]
|
|
406
|
+
};
|
|
407
|
+
}
|
|
503
408
|
}
|
|
504
409
|
|
|
505
410
|
/**
|