juxscript 1.1.118 → 1.1.120
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/machinery/compiler3.js +113 -282
- package/package.json +1 -1
package/dom-structure-map.json
CHANGED
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'; // ✅ Configurable public path
|
|
17
17
|
this.defaults = config.defaults || {};
|
|
18
18
|
this.paths = config.paths || {};
|
|
19
19
|
this._juxscriptExports = null;
|
|
@@ -53,56 +53,24 @@ export class JuxCompiler {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
scanFiles(dir = this.srcDir, baseDir = this.srcDir) {
|
|
60
|
-
const views = [];
|
|
61
|
-
const dataModules = [];
|
|
62
|
-
const sharedModules = [];
|
|
56
|
+
scanFiles() {
|
|
57
|
+
const files = fs.readdirSync(this.srcDir)
|
|
58
|
+
.filter(f => (f.endsWith('.jux') || f.endsWith('.js')) && !this.isAssetFile(f));
|
|
63
59
|
|
|
64
|
-
const
|
|
60
|
+
const views = [], dataModules = [], sharedModules = [];
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
files.forEach(file => {
|
|
63
|
+
const content = fs.readFileSync(path.join(this.srcDir, file), 'utf8');
|
|
64
|
+
const name = file.replace(/\.[^/.]+$/, '');
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
}
|
|
66
|
+
if (file.includes('data')) {
|
|
67
|
+
dataModules.push({ name, file, content });
|
|
68
|
+
} else if (/export\s+(function|const|let|var|class)\s+/.test(content)) {
|
|
69
|
+
sharedModules.push({ name, file, content });
|
|
70
|
+
} else {
|
|
71
|
+
views.push({ name, file, content });
|
|
104
72
|
}
|
|
105
|
-
}
|
|
73
|
+
});
|
|
106
74
|
|
|
107
75
|
return { views, dataModules, sharedModules };
|
|
108
76
|
}
|
|
@@ -203,220 +171,7 @@ export class JuxCompiler {
|
|
|
203
171
|
}
|
|
204
172
|
|
|
205
173
|
/**
|
|
206
|
-
*
|
|
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
|
|
174
|
+
* Generate entry point without layout/theme logic
|
|
420
175
|
*/
|
|
421
176
|
generateEntryPoint(views, dataModules, sharedModules) {
|
|
422
177
|
let entry = `// Auto-generated JUX entry point\n\n`;
|
|
@@ -454,7 +209,7 @@ navigate(location.pathname);
|
|
|
454
209
|
entry += `\n// --- VIEW FUNCTIONS ---\n`;
|
|
455
210
|
|
|
456
211
|
views.forEach(v => {
|
|
457
|
-
const
|
|
212
|
+
const capitalized = v.name.charAt(0).toUpperCase() + v.name.slice(1);
|
|
458
213
|
allIssues.push(...this.validateViewCode(v.name, v.content));
|
|
459
214
|
|
|
460
215
|
sourceSnapshot[v.file] = {
|
|
@@ -467,7 +222,7 @@ navigate(location.pathname);
|
|
|
467
222
|
let viewCode = this.removeImports(v.content).replace(/^\s*export\s+default\s+.*$/gm, '');
|
|
468
223
|
const asyncPrefix = viewCode.includes('await ') ? 'async ' : '';
|
|
469
224
|
|
|
470
|
-
entry += `\n${asyncPrefix}function render${
|
|
225
|
+
entry += `\n${asyncPrefix}function render${capitalized}() {\n${viewCode}\n}\n`;
|
|
471
226
|
});
|
|
472
227
|
|
|
473
228
|
dataModules.forEach(m => {
|
|
@@ -502,17 +257,10 @@ navigate(location.pathname);
|
|
|
502
257
|
|
|
503
258
|
_generateRouter(views) {
|
|
504
259
|
let routeMap = '';
|
|
505
|
-
|
|
506
260
|
views.forEach(v => {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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`;
|
|
261
|
+
const cap = v.name.charAt(0).toUpperCase() + v.name.slice(1);
|
|
262
|
+
if (v.name.toLowerCase() === 'index') routeMap += ` '/': render${cap},\n`;
|
|
263
|
+
routeMap += ` '/${v.name.toLowerCase()}': render${cap},\n`;
|
|
516
264
|
});
|
|
517
265
|
|
|
518
266
|
return `
|
|
@@ -530,17 +278,12 @@ async function __juxLoadSources() {
|
|
|
530
278
|
}
|
|
531
279
|
|
|
532
280
|
function __juxFindSource(stack) {
|
|
533
|
-
var match = stack.match(/render(
|
|
281
|
+
var match = stack.match(/render(\\w+)/);
|
|
534
282
|
if (match) {
|
|
535
|
-
var
|
|
283
|
+
var viewName = match[1].toLowerCase();
|
|
536
284
|
for (var file in __juxSources || {}) {
|
|
537
|
-
|
|
538
|
-
|
|
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 };
|
|
285
|
+
if (__juxSources[file].name.toLowerCase() === viewName) {
|
|
286
|
+
return { file: file, source: __juxSources[file], viewName: match[1] };
|
|
544
287
|
}
|
|
545
288
|
}
|
|
546
289
|
}
|
|
@@ -671,6 +414,94 @@ navigate(location.pathname);
|
|
|
671
414
|
`;
|
|
672
415
|
}
|
|
673
416
|
|
|
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
|
+
|
|
674
505
|
/**
|
|
675
506
|
* Copy public folder contents to dist
|
|
676
507
|
*/
|