juxscript 1.0.104 → 1.0.106
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/lib/componentsv2/grid/component.js +0 -4
- package/machinery/build3.js +141 -3
- package/machinery/compiler3.js +165 -155
- package/package.json +1 -1
|
@@ -19,10 +19,6 @@ export function Grid(id, options = {}) {
|
|
|
19
19
|
};
|
|
20
20
|
// @ts-ignore
|
|
21
21
|
engine.injectCSS = (id, css) => skin.injectCSS(id, css);
|
|
22
|
-
// --- Structural API ---
|
|
23
|
-
// --- Tools ---
|
|
24
|
-
// @ts-ignore
|
|
25
|
-
engine.gridder = (active) => { engine.toggleGridder(active); return engine; };
|
|
26
22
|
// --- Helpers ---
|
|
27
23
|
// @ts-ignore
|
|
28
24
|
engine.getCellId = (row, col) => `${id}-${row}-${col}`;
|
package/machinery/build3.js
CHANGED
|
@@ -1,12 +1,150 @@
|
|
|
1
1
|
import { JuxCompiler } from './compiler3.js';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
3
4
|
|
|
4
|
-
// Resolve paths relative to CWD (user's project)
|
|
5
5
|
const PROJECT_ROOT = process.cwd();
|
|
6
6
|
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════
|
|
8
|
+
// LOAD CONFIG
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════
|
|
10
|
+
const JUX_CONFIG_PATH = path.resolve(PROJECT_ROOT, 'juxconfig.js');
|
|
11
|
+
let rawConfig = {};
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
rawConfig = (await import(JUX_CONFIG_PATH)).config;
|
|
15
|
+
console.log(`⚙️ Loaded config: ${JUX_CONFIG_PATH}`);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.warn(`⚠️ No juxconfig.js found, using defaults`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════
|
|
21
|
+
// EXPLODE CONFIG INTO NAMED OBJECTS
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════
|
|
23
|
+
const directories = {
|
|
24
|
+
source: rawConfig.directories?.source || './jux',
|
|
25
|
+
distribution: rawConfig.directories?.distribution || './.jux-dist',
|
|
26
|
+
themes: rawConfig.directories?.themes || './themes',
|
|
27
|
+
layouts: rawConfig.directories?.layouts || './themes/layouts',
|
|
28
|
+
assets: rawConfig.directories?.assets || './themes/assets'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const defaults = {
|
|
32
|
+
httpPort: rawConfig.defaults?.httpPort || 3000,
|
|
33
|
+
wsPort: rawConfig.defaults?.wsPort || 3001,
|
|
34
|
+
autoRoute: rawConfig.defaults?.autoRoute ?? true,
|
|
35
|
+
layout: rawConfig.defaults?.layout || null,
|
|
36
|
+
theme: rawConfig.defaults?.theme || null
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Resolve absolute paths
|
|
40
|
+
const paths = {
|
|
41
|
+
source: path.resolve(PROJECT_ROOT, directories.source),
|
|
42
|
+
distribution: path.resolve(PROJECT_ROOT, directories.distribution),
|
|
43
|
+
themes: path.resolve(PROJECT_ROOT, directories.source, directories.themes),
|
|
44
|
+
layouts: path.resolve(PROJECT_ROOT, directories.source, directories.layouts),
|
|
45
|
+
assets: path.resolve(PROJECT_ROOT, directories.source, directories.assets)
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════
|
|
49
|
+
// VALIDATE DIRECTORIES
|
|
50
|
+
// ═══════════════════════════════════════════════════════════════
|
|
51
|
+
console.log(`\n📁 Directory Check:`);
|
|
52
|
+
|
|
53
|
+
const dirStatus = {};
|
|
54
|
+
for (const [name, dirPath] of Object.entries(paths)) {
|
|
55
|
+
if (name === 'distribution') continue;
|
|
56
|
+
|
|
57
|
+
const exists = fs.existsSync(dirPath);
|
|
58
|
+
dirStatus[name] = exists;
|
|
59
|
+
|
|
60
|
+
const icon = exists ? '✓' : '✗';
|
|
61
|
+
const suffix = exists ? '' : ' (will be skipped)';
|
|
62
|
+
console.log(` ${icon} ${name}: ${dirPath}${suffix}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ═══════════════════════════════════════════════════════════════
|
|
66
|
+
// RESOLVE DEFAULT LAYOUT & THEME
|
|
67
|
+
// ═══════════════════════════════════════════════════════════════
|
|
68
|
+
function resolveFile(filename, ...searchPaths) {
|
|
69
|
+
if (!filename) return null;
|
|
70
|
+
|
|
71
|
+
// First try the filename as-is
|
|
72
|
+
if (fs.existsSync(filename)) {
|
|
73
|
+
return path.resolve(filename);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Try each search path
|
|
77
|
+
for (const searchPath of searchPaths) {
|
|
78
|
+
if (!searchPath || !fs.existsSync(searchPath)) continue;
|
|
79
|
+
|
|
80
|
+
const fullPath = path.resolve(searchPath, filename);
|
|
81
|
+
if (fs.existsSync(fullPath)) {
|
|
82
|
+
return fullPath;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`\n🎨 Defaults Resolution:`);
|
|
90
|
+
|
|
91
|
+
// Resolve layout
|
|
92
|
+
const layoutPath = defaults.layout ? resolveFile(
|
|
93
|
+
defaults.layout,
|
|
94
|
+
paths.layouts,
|
|
95
|
+
paths.source,
|
|
96
|
+
PROJECT_ROOT
|
|
97
|
+
) : null;
|
|
98
|
+
|
|
99
|
+
if (defaults.layout) {
|
|
100
|
+
if (layoutPath) {
|
|
101
|
+
console.log(` ✓ layout: ${layoutPath}`);
|
|
102
|
+
} else {
|
|
103
|
+
console.log(` ✗ layout: "${defaults.layout}" not found, will be skipped`);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
console.log(` - layout: not configured`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Resolve theme
|
|
110
|
+
const themePath = defaults.theme ? resolveFile(
|
|
111
|
+
defaults.theme,
|
|
112
|
+
paths.themes,
|
|
113
|
+
paths.source,
|
|
114
|
+
PROJECT_ROOT
|
|
115
|
+
) : null;
|
|
116
|
+
|
|
117
|
+
if (defaults.theme) {
|
|
118
|
+
if (themePath) {
|
|
119
|
+
console.log(` ✓ theme: ${themePath}`);
|
|
120
|
+
} else {
|
|
121
|
+
console.log(` ✗ theme: "${defaults.theme}" not found, will be skipped`);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
console.log(` - theme: not configured`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════
|
|
128
|
+
// VALIDATE SOURCE DIRECTORY EXISTS
|
|
129
|
+
// ═══════════════════════════════════════════════════════════════
|
|
130
|
+
if (!dirStatus.source) {
|
|
131
|
+
console.error(`\n❌ Source directory not found: ${paths.source}`);
|
|
132
|
+
console.error(` Create the directory or update juxconfig.js`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ═══════════════════════════════════════════════════════════════
|
|
137
|
+
// RUN BUILD
|
|
138
|
+
// ═══════════════════════════════════════════════════════════════
|
|
139
|
+
console.log(`\n`);
|
|
140
|
+
|
|
7
141
|
const compiler = new JuxCompiler({
|
|
8
|
-
srcDir:
|
|
9
|
-
distDir:
|
|
142
|
+
srcDir: paths.source,
|
|
143
|
+
distDir: paths.distribution,
|
|
144
|
+
layoutPath,
|
|
145
|
+
themePath,
|
|
146
|
+
defaults,
|
|
147
|
+
paths
|
|
10
148
|
});
|
|
11
149
|
|
|
12
150
|
compiler.build()
|
package/machinery/compiler3.js
CHANGED
|
@@ -13,6 +13,10 @@ 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.layoutPath = config.layoutPath || null;
|
|
17
|
+
this.themePath = config.themePath || null;
|
|
18
|
+
this.defaults = config.defaults || {};
|
|
19
|
+
this.paths = config.paths || {};
|
|
16
20
|
this._juxscriptExports = null;
|
|
17
21
|
this._juxscriptPath = null;
|
|
18
22
|
}
|
|
@@ -61,7 +65,7 @@ export class JuxCompiler {
|
|
|
61
65
|
|
|
62
66
|
scanFiles() {
|
|
63
67
|
const files = fs.readdirSync(this.srcDir)
|
|
64
|
-
.filter(f => f.endsWith('.jux') || f.endsWith('.js'));
|
|
68
|
+
.filter(f => (f.endsWith('.jux') || f.endsWith('.js')) && !this.isAssetFile(f));
|
|
65
69
|
|
|
66
70
|
const views = [], dataModules = [], sharedModules = [];
|
|
67
71
|
|
|
@@ -81,6 +85,11 @@ export class JuxCompiler {
|
|
|
81
85
|
return { views, dataModules, sharedModules };
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
isAssetFile(filename) {
|
|
89
|
+
const assetExtensions = ['.css', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.woff', '.woff2', '.ttf', '.eot'];
|
|
90
|
+
return assetExtensions.some(ext => filename.endsWith(ext));
|
|
91
|
+
}
|
|
92
|
+
|
|
84
93
|
removeImports(code) {
|
|
85
94
|
return code
|
|
86
95
|
.replace(/^\s*import\s+.*?from\s+['"][^'"]+['"][\s;]*$/gm, '')
|
|
@@ -93,6 +102,56 @@ export class JuxCompiler {
|
|
|
93
102
|
return name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
94
103
|
}
|
|
95
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Copy all source assets (themes, layouts, assets folders) to dist
|
|
107
|
+
*/
|
|
108
|
+
copySourceAssets() {
|
|
109
|
+
const copied = { themes: 0, layouts: 0, assets: 0, other: 0 };
|
|
110
|
+
|
|
111
|
+
const copyDir = (srcDir, destDir, category) => {
|
|
112
|
+
if (!fs.existsSync(srcDir)) return 0;
|
|
113
|
+
|
|
114
|
+
let count = 0;
|
|
115
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
116
|
+
|
|
117
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
120
|
+
const destPath = path.join(destDir, entry.name);
|
|
121
|
+
|
|
122
|
+
if (entry.isDirectory()) {
|
|
123
|
+
count += copyDir(srcPath, destPath, category);
|
|
124
|
+
} else {
|
|
125
|
+
fs.copyFileSync(srcPath, destPath);
|
|
126
|
+
count++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return count;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Copy themes folder
|
|
133
|
+
if (this.paths.themes && fs.existsSync(this.paths.themes)) {
|
|
134
|
+
copied.themes = copyDir(this.paths.themes, path.join(this.distDir, 'themes'), 'themes');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Copy layouts folder (if separate from themes)
|
|
138
|
+
if (this.paths.layouts && fs.existsSync(this.paths.layouts) && this.paths.layouts !== this.paths.themes) {
|
|
139
|
+
copied.layouts = copyDir(this.paths.layouts, path.join(this.distDir, 'layouts'), 'layouts');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Copy assets folder
|
|
143
|
+
if (this.paths.assets && fs.existsSync(this.paths.assets)) {
|
|
144
|
+
copied.assets = copyDir(this.paths.assets, path.join(this.distDir, 'assets'), 'assets');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const total = copied.themes + copied.layouts + copied.assets;
|
|
148
|
+
if (total > 0) {
|
|
149
|
+
console.log(`📂 Copied source assets: ${copied.themes} themes, ${copied.layouts} layouts, ${copied.assets} assets`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return copied;
|
|
153
|
+
}
|
|
154
|
+
|
|
96
155
|
/**
|
|
97
156
|
* Collect and bundle all component CSS into a single file
|
|
98
157
|
*/
|
|
@@ -114,15 +173,9 @@ export class JuxCompiler {
|
|
|
114
173
|
} else if (entry.name === 'structure.css') {
|
|
115
174
|
const componentName = relativePath || 'base';
|
|
116
175
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
117
|
-
|
|
118
|
-
// Add comment header for each component's CSS
|
|
119
|
-
cssContents.push(`/* ═══════════════════════════════════════════════════════════════
|
|
120
|
-
* Component: ${componentName}
|
|
121
|
-
* Source: ${relPath}
|
|
122
|
-
* ═══════════════════════════════════════════════════════════════ */\n`);
|
|
176
|
+
cssContents.push(`/* Component: ${componentName} */\n`);
|
|
123
177
|
cssContents.push(content);
|
|
124
178
|
cssContents.push('\n');
|
|
125
|
-
|
|
126
179
|
components.push(componentName);
|
|
127
180
|
}
|
|
128
181
|
}
|
|
@@ -134,39 +187,35 @@ export class JuxCompiler {
|
|
|
134
187
|
return { bundlePath: null, components: [] };
|
|
135
188
|
}
|
|
136
189
|
|
|
137
|
-
// Create bundled CSS file
|
|
138
190
|
const cssDistDir = path.join(this.distDir, 'css');
|
|
139
191
|
fs.mkdirSync(cssDistDir, { recursive: true });
|
|
140
192
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
* Auto-generated - Do not edit directly
|
|
144
|
-
*
|
|
145
|
-
* Components included:
|
|
146
|
-
* ${components.map(c => ` - ${c}`).join('\n * ')}
|
|
147
|
-
*
|
|
148
|
-
* Generated: ${new Date().toISOString()}
|
|
149
|
-
*/\n\n`;
|
|
193
|
+
const bundledCss = `/* JUX Components CSS Bundle */\n\n` + cssContents.join('\n');
|
|
194
|
+
fs.writeFileSync(path.join(cssDistDir, 'jux-components.css'), bundledCss);
|
|
150
195
|
|
|
151
|
-
|
|
152
|
-
const bundlePath = path.join(cssDistDir, 'jux-components.css');
|
|
196
|
+
console.log(`📄 Bundled ${components.length} component CSS files`);
|
|
153
197
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
console.log(`📄 Bundled ${components.length} CSS files → css/jux-components.css`);
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
bundlePath: './css/jux-components.css',
|
|
160
|
-
components
|
|
161
|
-
};
|
|
198
|
+
return { bundlePath: './css/jux-components.css', components };
|
|
162
199
|
}
|
|
163
200
|
|
|
164
201
|
/**
|
|
165
|
-
* Generate
|
|
202
|
+
* Generate CSS links including theme if configured
|
|
166
203
|
*/
|
|
167
204
|
generateCssLinks(cssBundle) {
|
|
168
|
-
|
|
169
|
-
|
|
205
|
+
const links = [];
|
|
206
|
+
|
|
207
|
+
// JUX components CSS
|
|
208
|
+
if (cssBundle.bundlePath) {
|
|
209
|
+
links.push(` <link rel="stylesheet" href="${cssBundle.bundlePath}">`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// User theme CSS
|
|
213
|
+
if (this.themePath) {
|
|
214
|
+
const themeName = path.basename(this.themePath);
|
|
215
|
+
links.push(` <link rel="stylesheet" href="./themes/${themeName}" id="jux-theme">`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return links.join('\n');
|
|
170
219
|
}
|
|
171
220
|
|
|
172
221
|
async loadJuxscriptExports() {
|
|
@@ -178,7 +227,6 @@ export class JuxCompiler {
|
|
|
178
227
|
const indexContent = fs.readFileSync(juxscriptPath, 'utf8');
|
|
179
228
|
const exports = new Set();
|
|
180
229
|
|
|
181
|
-
// Parse export statements
|
|
182
230
|
for (const match of indexContent.matchAll(/export\s*\{\s*([^}]+)\s*\}/g)) {
|
|
183
231
|
match[1].split(',').forEach(exp => {
|
|
184
232
|
const name = exp.trim().split(/\s+as\s+/)[0].trim();
|
|
@@ -215,16 +263,12 @@ export class JuxCompiler {
|
|
|
215
263
|
return issues;
|
|
216
264
|
}
|
|
217
265
|
|
|
218
|
-
const juxscriptImports = new Set();
|
|
219
266
|
const allImports = new Set();
|
|
220
267
|
|
|
221
268
|
walk(ast, {
|
|
222
269
|
ImportDeclaration(node) {
|
|
223
270
|
node.specifiers.forEach(spec => {
|
|
224
271
|
allImports.add(spec.local.name);
|
|
225
|
-
if (node.source.value === 'juxscript') {
|
|
226
|
-
juxscriptImports.add(spec.local.name);
|
|
227
|
-
}
|
|
228
272
|
});
|
|
229
273
|
}
|
|
230
274
|
});
|
|
@@ -251,6 +295,17 @@ export class JuxCompiler {
|
|
|
251
295
|
return issues;
|
|
252
296
|
}
|
|
253
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Extract layout function name from layout file
|
|
300
|
+
*/
|
|
301
|
+
getLayoutFunctionName() {
|
|
302
|
+
if (!this.layoutPath || !fs.existsSync(this.layoutPath)) return null;
|
|
303
|
+
|
|
304
|
+
const content = fs.readFileSync(this.layoutPath, 'utf8');
|
|
305
|
+
const match = content.match(/export\s+function\s+(\w+)/);
|
|
306
|
+
return match ? match[1] : null;
|
|
307
|
+
}
|
|
308
|
+
|
|
254
309
|
generateEntryPoint(views, dataModules, sharedModules) {
|
|
255
310
|
let entry = `// Auto-generated JUX entry point\n\n`;
|
|
256
311
|
const allIssues = [];
|
|
@@ -265,6 +320,16 @@ export class JuxCompiler {
|
|
|
265
320
|
}
|
|
266
321
|
});
|
|
267
322
|
|
|
323
|
+
// Check layout file for juxscript imports too
|
|
324
|
+
if (this.layoutPath && fs.existsSync(this.layoutPath)) {
|
|
325
|
+
const layoutContent = fs.readFileSync(this.layoutPath, 'utf8');
|
|
326
|
+
for (const match of layoutContent.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]juxscript['"]/g)) {
|
|
327
|
+
match[1].split(',').map(s => s.trim()).forEach(imp => {
|
|
328
|
+
if (imp) juxImports.add(imp);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
268
333
|
if (juxImports.size > 0) {
|
|
269
334
|
entry += `import { ${[...juxImports].sort().join(', ')} } from 'juxscript';\n\n`;
|
|
270
335
|
}
|
|
@@ -284,6 +349,25 @@ export class JuxCompiler {
|
|
|
284
349
|
entry += `\nObject.assign(window, { ${[...juxImports].join(', ')} });\n`;
|
|
285
350
|
}
|
|
286
351
|
|
|
352
|
+
// Add layout function if configured
|
|
353
|
+
const layoutFnName = this.getLayoutFunctionName();
|
|
354
|
+
if (layoutFnName && this.layoutPath) {
|
|
355
|
+
const layoutContent = fs.readFileSync(this.layoutPath, 'utf8');
|
|
356
|
+
const layoutCode = this.removeImports(layoutContent);
|
|
357
|
+
entry += `\n// --- DEFAULT LAYOUT ---\n`;
|
|
358
|
+
entry += layoutCode;
|
|
359
|
+
entry += `\n`;
|
|
360
|
+
|
|
361
|
+
// Store in source snapshot
|
|
362
|
+
const layoutFile = path.basename(this.layoutPath);
|
|
363
|
+
sourceSnapshot[layoutFile] = {
|
|
364
|
+
name: 'layout',
|
|
365
|
+
file: layoutFile,
|
|
366
|
+
content: layoutContent,
|
|
367
|
+
lines: layoutContent.split('\n')
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
287
371
|
entry += `\n// --- VIEW FUNCTIONS ---\n`;
|
|
288
372
|
|
|
289
373
|
views.forEach(v => {
|
|
@@ -312,6 +396,7 @@ export class JuxCompiler {
|
|
|
312
396
|
|
|
313
397
|
this._sourceSnapshot = sourceSnapshot;
|
|
314
398
|
this._validationIssues = allIssues;
|
|
399
|
+
this._layoutFnName = layoutFnName;
|
|
315
400
|
entry += this._generateRouter(views);
|
|
316
401
|
return entry;
|
|
317
402
|
}
|
|
@@ -341,6 +426,11 @@ export class JuxCompiler {
|
|
|
341
426
|
routeMap += ` '/${v.name.toLowerCase()}': render${cap},\n`;
|
|
342
427
|
});
|
|
343
428
|
|
|
429
|
+
// Initialize layout call if configured
|
|
430
|
+
const layoutInit = this._layoutFnName
|
|
431
|
+
? `\n// Initialize default layout\nif (typeof ${this._layoutFnName} === 'function') {\n ${this._layoutFnName}();\n}\n`
|
|
432
|
+
: '';
|
|
433
|
+
|
|
344
434
|
return `
|
|
345
435
|
// --- JUX SOURCE LOADER ---
|
|
346
436
|
var __juxSources = null;
|
|
@@ -355,7 +445,6 @@ async function __juxLoadSources() {
|
|
|
355
445
|
return __juxSources;
|
|
356
446
|
}
|
|
357
447
|
|
|
358
|
-
// Find source file from error stack
|
|
359
448
|
function __juxFindSource(stack) {
|
|
360
449
|
var match = stack.match(/render(\\w+)/);
|
|
361
450
|
if (match) {
|
|
@@ -366,6 +455,14 @@ function __juxFindSource(stack) {
|
|
|
366
455
|
}
|
|
367
456
|
}
|
|
368
457
|
}
|
|
458
|
+
// Also check for layout
|
|
459
|
+
if (stack.indexOf('Layout') > -1) {
|
|
460
|
+
for (var file in __juxSources || {}) {
|
|
461
|
+
if (file.indexOf('layout') > -1) {
|
|
462
|
+
return { file: file, source: __juxSources[file] };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
369
466
|
return null;
|
|
370
467
|
}
|
|
371
468
|
|
|
@@ -377,39 +474,21 @@ var __juxErrorOverlay = {
|
|
|
377
474
|
background: rgba(0, 0, 0, 0.4);
|
|
378
475
|
display: flex; align-items: center; justify-content: center;
|
|
379
476
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
|
|
380
|
-
opacity: 0;
|
|
381
|
-
transition: opacity 0.2s ease-out;
|
|
382
|
-
backdrop-filter: blur(2px);
|
|
477
|
+
opacity: 0; transition: opacity 0.2s ease-out; backdrop-filter: blur(2px);
|
|
383
478
|
}
|
|
384
479
|
#__jux-error-overlay.visible { opacity: 1; }
|
|
385
480
|
#__jux-error-overlay * { box-sizing: border-box; }
|
|
386
481
|
.__jux-modal {
|
|
387
|
-
background: #f8f9fa;
|
|
388
|
-
border-radius: 4px;
|
|
482
|
+
background: #f8f9fa; border-radius: 4px;
|
|
389
483
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
|
|
390
|
-
max-width: 80vw;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
overflow: hidden;
|
|
394
|
-
display: flex;
|
|
395
|
-
flex-direction: column;
|
|
396
|
-
transform: translateY(10px);
|
|
397
|
-
transition: transform 0.2s ease-out;
|
|
484
|
+
max-width: 80vw; width: 90%; max-height: 90vh;
|
|
485
|
+
overflow: hidden; display: flex; flex-direction: column;
|
|
486
|
+
transform: translateY(10px); transition: transform 0.2s ease-out;
|
|
398
487
|
}
|
|
399
488
|
#__jux-error-overlay.visible .__jux-modal { transform: translateY(0); }
|
|
400
|
-
.__jux-header {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
border-bottom: 1px solid #e5e7eb;
|
|
404
|
-
}
|
|
405
|
-
.__jux-header h3 {
|
|
406
|
-
margin: 0 0 6px; font-weight: 600; font-size: 11px;
|
|
407
|
-
color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px;
|
|
408
|
-
}
|
|
409
|
-
.__jux-header h1 {
|
|
410
|
-
margin: 0 0 8px; font-size: 18px; font-weight: 600; color: #dc2626;
|
|
411
|
-
line-height: 1.3;
|
|
412
|
-
}
|
|
489
|
+
.__jux-header { background: #fff; padding: 20px 24px; border-bottom: 1px solid #e5e7eb; }
|
|
490
|
+
.__jux-header h3 { margin: 0 0 6px; font-weight: 600; font-size: 11px; color: #9ca3af; text-transform: uppercase; }
|
|
491
|
+
.__jux-header h1 { margin: 0 0 8px; font-size: 18px; font-weight: 600; color: #dc2626; line-height: 1.3; }
|
|
413
492
|
.__jux-header .file-info { color: #6b7280; font-size: 13px; }
|
|
414
493
|
.__jux-header .file-info strong { color: #dc2626; font-weight: 600; }
|
|
415
494
|
.__jux-source { padding: 16px 24px; overflow: auto; flex: 1; background: #f8f9fa; }
|
|
@@ -418,138 +497,73 @@ var __juxErrorOverlay = {
|
|
|
418
497
|
.__jux-code-line.error { background: #fef2f2; }
|
|
419
498
|
.__jux-code-line.error .__jux-line-code { color: #dc2626; font-weight: 500; }
|
|
420
499
|
.__jux-code-line.context { background: #fefce8; }
|
|
421
|
-
.__jux-line-num {
|
|
422
|
-
min-width: 44px; padding: 4px 12px; text-align: right;
|
|
423
|
-
color: #9ca3af; background: #f9fafb; user-select: none;
|
|
424
|
-
border-right: 1px solid #e5e7eb; font-size: 12px;
|
|
425
|
-
}
|
|
500
|
+
.__jux-line-num { min-width: 44px; padding: 4px 12px; text-align: right; color: #9ca3af; background: #f9fafb; border-right: 1px solid #e5e7eb; font-size: 12px; }
|
|
426
501
|
.__jux-code-line.error .__jux-line-num { background: #fef2f2; color: #dc2626; }
|
|
427
502
|
.__jux-line-code { flex: 1; padding: 4px 16px; color: #374151; white-space: pre; overflow-x: auto; }
|
|
428
|
-
.__jux-footer {
|
|
429
|
-
padding: 16px 24px; background: #fff; border-top: 1px solid #e5e7eb;
|
|
430
|
-
display: flex; justify-content: space-between; align-items: center;
|
|
431
|
-
}
|
|
503
|
+
.__jux-footer { padding: 16px 24px; background: #fff; border-top: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center; }
|
|
432
504
|
.__jux-tip { color: #6b7280; font-size: 12px; }
|
|
433
|
-
.__jux-dismiss {
|
|
434
|
-
background: #f3f4f6; color: #374151; border: 1px solid #d1d5db;
|
|
435
|
-
padding: 8px 16px; border-radius: 6px; cursor: pointer;
|
|
436
|
-
font-size: 13px; font-weight: 500; transition: background 0.15s;
|
|
437
|
-
}
|
|
505
|
+
.__jux-dismiss { background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; }
|
|
438
506
|
.__jux-dismiss:hover { background: #e5e7eb; }
|
|
439
507
|
.__jux-no-source { color: #6b7280; padding: 24px; text-align: center; }
|
|
440
|
-
.__jux-no-source pre {
|
|
441
|
-
background: #fff; padding: 16px; border-radius: 8px; margin-top: 16px;
|
|
442
|
-
font-size: 11px; color: #6b7280; overflow-x: auto; text-align: left;
|
|
443
|
-
border: 1px solid #e5e7eb;
|
|
444
|
-
}
|
|
508
|
+
.__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; }
|
|
445
509
|
\`,
|
|
446
510
|
|
|
447
511
|
show: async function(error, title) {
|
|
448
512
|
title = title || 'Runtime Error';
|
|
449
|
-
|
|
450
513
|
var existing = document.getElementById('__jux-error-overlay');
|
|
451
514
|
if (existing) existing.remove();
|
|
452
|
-
|
|
453
515
|
await __juxLoadSources();
|
|
454
516
|
|
|
455
517
|
var overlay = document.createElement('div');
|
|
456
518
|
overlay.id = '__jux-error-overlay';
|
|
457
|
-
|
|
458
519
|
var stack = error && error.stack ? error.stack : '';
|
|
459
520
|
var msg = error && error.message ? error.message : String(error);
|
|
460
|
-
|
|
461
521
|
var found = __juxFindSource(stack);
|
|
462
|
-
var sourceHtml = '';
|
|
463
|
-
var fileInfo = '';
|
|
522
|
+
var sourceHtml = '', fileInfo = '';
|
|
464
523
|
|
|
465
524
|
if (found && found.source && found.source.lines) {
|
|
466
525
|
var lines = found.source.lines;
|
|
467
526
|
fileInfo = '<span class="file-info">in <strong>' + found.file + '</strong></span>';
|
|
468
|
-
|
|
469
527
|
var errorLineIndex = -1;
|
|
470
528
|
var errorMethod = msg.match(/\\.([a-zA-Z]+)\\s*is not a function/);
|
|
471
529
|
if (errorMethod) {
|
|
472
530
|
for (var i = 0; i < lines.length; i++) {
|
|
473
|
-
if (lines[i].indexOf('.' + errorMethod[1]) > -1) {
|
|
474
|
-
errorLineIndex = i;
|
|
475
|
-
break;
|
|
476
|
-
}
|
|
531
|
+
if (lines[i].indexOf('.' + errorMethod[1]) > -1) { errorLineIndex = i; break; }
|
|
477
532
|
}
|
|
478
533
|
}
|
|
479
|
-
|
|
480
534
|
if (errorLineIndex === -1) {
|
|
481
535
|
for (var i = 0; i < lines.length; i++) {
|
|
482
|
-
if (lines[i].indexOf('throw ') > -1
|
|
483
|
-
errorLineIndex = i;
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
536
|
+
if (lines[i].indexOf('throw ') > -1) { errorLineIndex = i; break; }
|
|
486
537
|
}
|
|
487
538
|
}
|
|
488
|
-
|
|
489
539
|
var contextStart = Math.max(0, errorLineIndex - 3);
|
|
490
540
|
var contextEnd = Math.min(lines.length - 1, errorLineIndex + 5);
|
|
491
|
-
|
|
492
|
-
if (errorLineIndex === -1) {
|
|
493
|
-
contextStart = 0;
|
|
494
|
-
contextEnd = Math.min(14, lines.length - 1);
|
|
495
|
-
}
|
|
541
|
+
if (errorLineIndex === -1) { contextStart = 0; contextEnd = Math.min(14, lines.length - 1); }
|
|
496
542
|
|
|
497
543
|
for (var i = contextStart; i <= contextEnd; i++) {
|
|
498
544
|
var isError = (i === errorLineIndex);
|
|
499
545
|
var isContext = !isError && errorLineIndex > -1 && Math.abs(i - errorLineIndex) <= 2;
|
|
500
546
|
var lineClass = isError ? ' error' : (isContext ? ' context' : '');
|
|
501
547
|
var lineCode = lines[i].replace(/</g, '<').replace(/>/g, '>') || ' ';
|
|
502
|
-
sourceHtml += '<div class="__jux-code-line' + lineClass + '">' +
|
|
503
|
-
'<span class="__jux-line-num">' + (i + 1) + '</span>' +
|
|
504
|
-
'<span class="__jux-line-code">' + lineCode + '</span>' +
|
|
505
|
-
'</div>';
|
|
548
|
+
sourceHtml += '<div class="__jux-code-line' + lineClass + '"><span class="__jux-line-num">' + (i + 1) + '</span><span class="__jux-line-code">' + lineCode + '</span></div>';
|
|
506
549
|
}
|
|
507
550
|
} else {
|
|
508
|
-
sourceHtml = '<div class="__jux-no-source">' +
|
|
509
|
-
'<p>Could not locate source file for this error.</p>' +
|
|
510
|
-
'<pre>' + stack + '</pre>' +
|
|
511
|
-
'</div>';
|
|
551
|
+
sourceHtml = '<div class="__jux-no-source"><p>Could not locate source file.</p><pre>' + stack + '</pre></div>';
|
|
512
552
|
}
|
|
513
553
|
|
|
514
|
-
overlay.innerHTML = '<style>' + this.styles + '</style>' +
|
|
515
|
-
'<div class="__jux-modal">' +
|
|
516
|
-
'<div class="__jux-header">' +
|
|
517
|
-
'<h3>' + title + '</h3>' +
|
|
518
|
-
'<h1>' + msg + '</h1>' +
|
|
519
|
-
fileInfo +
|
|
520
|
-
'</div>' +
|
|
521
|
-
'<div class="__jux-source">' +
|
|
522
|
-
'<div class="__jux-code">' + sourceHtml + '</div>' +
|
|
523
|
-
'</div>' +
|
|
524
|
-
'<div class="__jux-footer">' +
|
|
525
|
-
'<span class="__jux-tip">💡 Fix the error and save to reload</span>' +
|
|
526
|
-
'<button class="__jux-dismiss" onclick="document.getElementById(\\'__jux-error-overlay\\').remove()">Dismiss</button>' +
|
|
527
|
-
'</div>' +
|
|
528
|
-
'</div>';
|
|
529
|
-
|
|
554
|
+
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>';
|
|
530
555
|
document.body.appendChild(overlay);
|
|
531
|
-
|
|
532
|
-
// Trigger transition
|
|
533
|
-
requestAnimationFrame(function() {
|
|
534
|
-
overlay.classList.add('visible');
|
|
535
|
-
});
|
|
536
|
-
|
|
556
|
+
requestAnimationFrame(function() { overlay.classList.add('visible'); });
|
|
537
557
|
console.error(title + ':', error);
|
|
538
558
|
}
|
|
539
559
|
};
|
|
540
560
|
|
|
541
|
-
|
|
542
|
-
window.addEventListener('
|
|
543
|
-
__juxErrorOverlay.show(e.error || new Error(e.message), 'Uncaught Error');
|
|
544
|
-
}, true);
|
|
545
|
-
|
|
546
|
-
window.addEventListener('unhandledrejection', function(e) {
|
|
547
|
-
__juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection');
|
|
548
|
-
}, true);
|
|
561
|
+
window.addEventListener('error', function(e) { __juxErrorOverlay.show(e.error || new Error(e.message), 'Uncaught Error'); }, true);
|
|
562
|
+
window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.show(e.reason || new Error('Promise rejected'), 'Unhandled Promise Rejection'); }, true);
|
|
549
563
|
|
|
550
564
|
// --- JUX ROUTER ---
|
|
551
565
|
const routes = {\n${routeMap}};
|
|
552
|
-
|
|
566
|
+
${layoutInit}
|
|
553
567
|
async function navigate(path) {
|
|
554
568
|
const view = routes[path];
|
|
555
569
|
if (!view) {
|
|
@@ -557,15 +571,9 @@ async function navigate(path) {
|
|
|
557
571
|
return;
|
|
558
572
|
}
|
|
559
573
|
document.getElementById('app').innerHTML = '';
|
|
560
|
-
|
|
561
574
|
var overlay = document.getElementById('__jux-error-overlay');
|
|
562
575
|
if (overlay) overlay.remove();
|
|
563
|
-
|
|
564
|
-
try {
|
|
565
|
-
await view();
|
|
566
|
-
} catch (err) {
|
|
567
|
-
__juxErrorOverlay.show(err, 'Jux Render Error');
|
|
568
|
-
}
|
|
576
|
+
try { await view(); } catch (err) { __juxErrorOverlay.show(err, 'Jux Render Error'); }
|
|
569
577
|
}
|
|
570
578
|
|
|
571
579
|
document.addEventListener('click', e => {
|
|
@@ -600,9 +608,12 @@ navigate(location.pathname);
|
|
|
600
608
|
fs.mkdirSync(this.distDir, { recursive: true });
|
|
601
609
|
|
|
602
610
|
const { views, dataModules, sharedModules } = this.scanFiles();
|
|
603
|
-
console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data
|
|
611
|
+
console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
|
|
612
|
+
|
|
613
|
+
// Copy source assets (themes, layouts, assets)
|
|
614
|
+
this.copySourceAssets();
|
|
604
615
|
|
|
605
|
-
//
|
|
616
|
+
// Bundle juxscript component CSS
|
|
606
617
|
const cssBundle = this.copyJuxscriptCss();
|
|
607
618
|
|
|
608
619
|
// Copy data/shared modules to dist
|
|
@@ -616,7 +627,6 @@ navigate(location.pathname);
|
|
|
616
627
|
const entryPath = path.join(this.distDir, 'entry.js');
|
|
617
628
|
fs.writeFileSync(entryPath, entryContent);
|
|
618
629
|
|
|
619
|
-
// Write source snapshot for dev tools
|
|
620
630
|
const snapshotPath = path.join(this.distDir, '__jux_sources.json');
|
|
621
631
|
fs.writeFileSync(snapshotPath, JSON.stringify(this._sourceSnapshot, null, 2));
|
|
622
632
|
console.log(`📸 Source snapshot written`);
|
|
@@ -650,7 +660,7 @@ navigate(location.pathname);
|
|
|
650
660
|
return { success: false, errors: [{ message: err.message }], warnings: [] };
|
|
651
661
|
}
|
|
652
662
|
|
|
653
|
-
// Generate
|
|
663
|
+
// Generate HTML with layout container sibling to app
|
|
654
664
|
const html = `<!DOCTYPE html>
|
|
655
665
|
<html lang="en">
|
|
656
666
|
<head>
|
|
@@ -661,12 +671,12 @@ ${this.generateCssLinks(cssBundle)}
|
|
|
661
671
|
<script type="module" src="./bundle.js"></script>
|
|
662
672
|
</head>
|
|
663
673
|
<body>
|
|
674
|
+
<div id="jux-layout"></div>
|
|
664
675
|
<div id="app"></div>
|
|
665
676
|
</body>
|
|
666
677
|
</html>`;
|
|
667
678
|
fs.writeFileSync(path.join(this.distDir, 'index.html'), html);
|
|
668
679
|
|
|
669
|
-
// Cleanup temp files
|
|
670
680
|
fs.unlinkSync(entryPath);
|
|
671
681
|
fs.rmSync(juxDistDir, { recursive: true, force: true });
|
|
672
682
|
|