juxscript 1.0.70 → 1.0.72
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/create/layout.jux +1 -60
- package/machinery/compiler.js +78 -26
- package/package.json +1 -1
package/create/layout.jux
CHANGED
|
@@ -1,63 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
// Import the layout styles - testing link.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
7
|
-
// GRID LAYOUT - INITIALIZATION FUNCTION
|
|
8
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
9
|
-
// Note: #app is automatically created by the Jux compiler
|
|
10
|
-
|
|
11
|
-
export function initializeGrid() {
|
|
12
|
-
// add the base jux styles via style tag import
|
|
13
|
-
Element('layout-css', { tag: 'style' })
|
|
14
|
-
.text('@import "style.css";')
|
|
15
|
-
.render(document.head);
|
|
16
|
-
|
|
17
|
-
// Header area
|
|
18
|
-
const appHeader = Element('appheader').render('#app');
|
|
19
|
-
const appHeaderContent = Element('appheader-content').render('#appheader');
|
|
20
|
-
const appHeaderLogo = Element('appheader-logo').render('#appheader-content');
|
|
21
|
-
const appHeaderNav = Element('appheader-nav').render('#appheader-content');
|
|
22
|
-
const appHeaderActions = Element('appheader-actions').render('#appheader-content');
|
|
23
|
-
|
|
24
|
-
// Left sidebar
|
|
25
|
-
const appAside = Element('appaside').render('#app');
|
|
26
|
-
|
|
27
|
-
// Main content area
|
|
28
|
-
const appMain = Element('appmain').render('#app');
|
|
29
|
-
const appMainContent = Element('appmain-content').render('#appmain');
|
|
30
|
-
|
|
31
|
-
// Right sidebar (optional - starts hidden)
|
|
32
|
-
const appSidebar = Element('appsidebar').render('#app');
|
|
33
|
-
const appSidebarHeader = Element('appsidebar-header').render('#appsidebar');
|
|
34
|
-
const appSidebarContent = Element('appsidebar-content').render('#appsidebar');
|
|
35
|
-
const appSidebarFooter = Element('appsidebar-footer').render('#appsidebar');
|
|
36
|
-
|
|
37
|
-
// Footer area
|
|
38
|
-
const appFooter = Element('appfooter').render('#app');
|
|
39
|
-
const appFooterContent = Element('appfooter-content').render('#appfooter');
|
|
40
|
-
const appFooterLegal = Element('appfooter-legal').render('#appfooter-content');
|
|
41
|
-
|
|
42
|
-
// Return references to all containers
|
|
43
|
-
return {
|
|
44
|
-
appHeader,
|
|
45
|
-
appHeaderContent,
|
|
46
|
-
appHeaderLogo,
|
|
47
|
-
appHeaderNav,
|
|
48
|
-
appHeaderActions,
|
|
49
|
-
appAside,
|
|
50
|
-
appMain,
|
|
51
|
-
appMainContent,
|
|
52
|
-
appSidebar,
|
|
53
|
-
appSidebarHeader,
|
|
54
|
-
appSidebarContent,
|
|
55
|
-
appSidebarFooter,
|
|
56
|
-
appFooter,
|
|
57
|
-
appFooterContent,
|
|
58
|
-
appFooterLegal
|
|
59
|
-
};
|
|
60
|
-
}
|
|
1
|
+
import { Grid } from 'juxscript';
|
|
61
2
|
|
|
62
3
|
export function LandingLayout() {
|
|
63
4
|
// Defines a classic landing page structure using JUX Grid
|
package/machinery/compiler.js
CHANGED
|
@@ -89,12 +89,15 @@ export async function copyPresetsToOutput(packageRoot, distDir) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function findFiles(dir, extension, fileList = []) {
|
|
92
|
+
if (!fs.existsSync(dir)) return fileList;
|
|
92
93
|
const files = fs.readdirSync(dir);
|
|
93
94
|
files.forEach(file => {
|
|
94
95
|
const filePath = path.join(dir, file);
|
|
95
96
|
const stat = fs.statSync(filePath);
|
|
96
97
|
if (stat.isDirectory()) {
|
|
97
|
-
|
|
98
|
+
if (file !== 'node_modules' && file !== '.git' && file !== 'jux-dist') {
|
|
99
|
+
findFiles(filePath, extension, fileList);
|
|
100
|
+
}
|
|
98
101
|
} else if (file.endsWith(extension)) {
|
|
99
102
|
fileList.push(filePath);
|
|
100
103
|
}
|
|
@@ -149,12 +152,22 @@ function findProjectFiles(dir, extensions, fileList = [], rootDir = dir, exclude
|
|
|
149
152
|
*/
|
|
150
153
|
export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {}) {
|
|
151
154
|
const { routePrefix = '', config } = options;
|
|
155
|
+
|
|
156
|
+
// ✅ 1. Find all processable source files (.jux AND .js)
|
|
157
|
+
// This ensures locally imported helper JS files are bundled and namespaced correctly.
|
|
152
158
|
const juxFiles = findFiles(projectRoot, '.jux');
|
|
159
|
+
const jsFiles = findFiles(projectRoot, '.js');
|
|
153
160
|
|
|
154
|
-
|
|
161
|
+
// Combine, filtering duplicates if any
|
|
162
|
+
const allSourceFiles = Array.from(new Set([...juxFiles, ...jsFiles]));
|
|
163
|
+
|
|
164
|
+
if (allSourceFiles.length === 0) {
|
|
155
165
|
return { mainJsFilename: 'main.js', routes: [], external: new Set(), vendoredPaths: {} };
|
|
156
166
|
}
|
|
157
167
|
|
|
168
|
+
// Set of relative paths being bundled (used to decide if an import should be skipped)
|
|
169
|
+
const bundledPaths = new Set(allSourceFiles.map(f => path.relative(projectRoot, f).replace(/\\/g, '/')));
|
|
170
|
+
|
|
158
171
|
const pages = config?.pages || {};
|
|
159
172
|
const views = [];
|
|
160
173
|
const routes = [];
|
|
@@ -163,28 +176,50 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
|
|
|
163
176
|
const externalModules = new Set();
|
|
164
177
|
const fileToFunction = new Map();
|
|
165
178
|
|
|
166
|
-
for (const
|
|
167
|
-
const
|
|
179
|
+
for (const sourceFile of allSourceFiles) {
|
|
180
|
+
const isJux = sourceFile.endsWith('.jux');
|
|
181
|
+
const relativePath = path.relative(projectRoot, sourceFile);
|
|
168
182
|
const parsedPath = path.parse(relativePath);
|
|
183
|
+
|
|
184
|
+
// Normalize function name: experiments/my-data -> ExperimentsMyData
|
|
169
185
|
const rawFunctionName = parsedPath.dir ? `${parsedPath.dir.replace(/\//g, '_')}_${parsedPath.name}` : parsedPath.name;
|
|
170
186
|
const cleanFunctionName = rawFunctionName.replace(/[-_]/g, ' ').split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
171
187
|
|
|
172
|
-
|
|
173
|
-
|
|
188
|
+
// Only .jux files generate Routes/Views
|
|
189
|
+
if (isJux) {
|
|
190
|
+
fileToFunction.set(relativePath, cleanFunctionName);
|
|
191
|
+
fileToFunction.set(relativePath.split(path.sep).join('/'), cleanFunctionName);
|
|
192
|
+
}
|
|
174
193
|
|
|
175
194
|
const routePath = routePrefix + '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
|
|
176
195
|
const cleanRoutePath = routePath.replace(/\/+/g, '/');
|
|
177
|
-
const juxContent = fs.readFileSync(juxFile, 'utf-8');
|
|
178
196
|
|
|
197
|
+
const fileContent = fs.readFileSync(sourceFile, 'utf-8');
|
|
198
|
+
|
|
199
|
+
// Parse Imports
|
|
179
200
|
try {
|
|
180
|
-
const ast = acorn.parse(
|
|
201
|
+
const ast = acorn.parse(fileContent, { ecmaVersion: 'latest', sourceType: 'module' });
|
|
181
202
|
ast.body.forEach(node => {
|
|
182
203
|
if (node.type === 'ImportDeclaration') {
|
|
183
|
-
const importStatement =
|
|
184
|
-
allImports.push({ code: importStatement, filePath: relativePath });
|
|
204
|
+
const importStatement = fileContent.slice(node.start, node.end);
|
|
185
205
|
const moduleName = node.source.value;
|
|
186
|
-
|
|
187
|
-
|
|
206
|
+
|
|
207
|
+
// ✅ CHECK: Is this import pointing to a file we are bundling?
|
|
208
|
+
let isBundledLocal = false;
|
|
209
|
+
if (moduleName.startsWith('.')) {
|
|
210
|
+
const resolved = resolveImportPath(moduleName, relativePath);
|
|
211
|
+
if (bundledPaths.has(resolved)) {
|
|
212
|
+
isBundledLocal = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ONLY add to global imports if it is NOT a local bundled file
|
|
217
|
+
if (!isBundledLocal) {
|
|
218
|
+
allImports.push({ code: importStatement, filePath: relativePath });
|
|
219
|
+
|
|
220
|
+
if (!moduleName.startsWith('.') && !moduleName.startsWith('/') && !moduleName.startsWith('http') && !moduleName.startsWith('juxscript')) {
|
|
221
|
+
externalModules.add(moduleName);
|
|
222
|
+
}
|
|
188
223
|
}
|
|
189
224
|
}
|
|
190
225
|
});
|
|
@@ -192,18 +227,23 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
|
|
|
192
227
|
console.warn(` ⚠️ Failed to parse imports in ${relativePath}, skipping`);
|
|
193
228
|
}
|
|
194
229
|
|
|
195
|
-
|
|
230
|
+
// Process Exports -> Shared Modules (Code Hoisting)
|
|
231
|
+
const hasExports = /^\s*export\s+(const|let|function|class|{)/m.test(fileContent);
|
|
196
232
|
if (hasExports) {
|
|
197
233
|
const exportKey = relativePath;
|
|
198
|
-
const exportCode = extractSharedModule(
|
|
234
|
+
const exportCode = extractSharedModule(fileContent, rawFunctionName, relativePath);
|
|
199
235
|
if (exportCode.trim()) sharedModules.set(exportKey, exportCode);
|
|
200
236
|
}
|
|
201
237
|
|
|
202
|
-
|
|
203
|
-
|
|
238
|
+
// Process .jux -> View Functions
|
|
239
|
+
if (isJux) {
|
|
240
|
+
// Pass bundledPaths so we know which imports to rewrite
|
|
241
|
+
const viewFunction = transformJuxToViewFunction(fileContent, rawFunctionName, parsedPath.name, relativePath, sharedModules, bundledPaths);
|
|
242
|
+
views.push(viewFunction);
|
|
204
243
|
|
|
205
|
-
|
|
206
|
-
|
|
244
|
+
if (config?.defaults?.autoRoute !== false) {
|
|
245
|
+
routes.push({ path: cleanRoutePath, functionName: cleanFunctionName });
|
|
246
|
+
}
|
|
207
247
|
}
|
|
208
248
|
}
|
|
209
249
|
|
|
@@ -265,7 +305,8 @@ function extractSharedModule(juxContent, moduleName, sourceFilePath) {
|
|
|
265
305
|
}
|
|
266
306
|
|
|
267
307
|
function namespaceExportedIdentifiers(code, sourceFilePath) {
|
|
268
|
-
|
|
308
|
+
// ✅ FIX: Strip .jux AND .js extensions for stable namespacing
|
|
309
|
+
const namespace = sourceFilePath.replace(/\.(jux|js)$/, '').replace(/[\/\\]/g, '$').replace(/[^a-zA-Z0-9$]/g, '_');
|
|
269
310
|
try {
|
|
270
311
|
const ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
|
|
271
312
|
const identifiers = [];
|
|
@@ -285,7 +326,7 @@ function namespaceExportedIdentifiers(code, sourceFilePath) {
|
|
|
285
326
|
}
|
|
286
327
|
}
|
|
287
328
|
|
|
288
|
-
function transformJuxToViewFunction(juxContent, functionName, pageName, relativePath, sharedModules) {
|
|
329
|
+
function transformJuxToViewFunction(juxContent, functionName, pageName, relativePath, sharedModules, bundledPaths) {
|
|
289
330
|
let result;
|
|
290
331
|
try {
|
|
291
332
|
const ast = acorn.parse(juxContent, { ecmaVersion: 'latest', sourceType: 'module' });
|
|
@@ -294,16 +335,22 @@ function transformJuxToViewFunction(juxContent, functionName, pageName, relative
|
|
|
294
335
|
ast.body.forEach(node => {
|
|
295
336
|
if (node.type === 'ImportDeclaration') {
|
|
296
337
|
const importPath = node.source.value;
|
|
297
|
-
|
|
338
|
+
const resolvedPath = resolveImportPath(importPath, relativePath);
|
|
339
|
+
|
|
340
|
+
// ✅ Check if this import is pointing to a bundled file
|
|
341
|
+
const isBundled = (bundledPaths && bundledPaths.has(resolvedPath)) || importPath.endsWith('.jux');
|
|
342
|
+
|
|
343
|
+
if (isBundled) {
|
|
298
344
|
node.specifiers.forEach(spec => {
|
|
299
345
|
if (spec.type === 'ImportSpecifier') {
|
|
300
|
-
|
|
301
|
-
const namespace = resolvedPath.replace(/\.jux$/, '').replace(/[\/\\]/g, '$').replace(/[^a-zA-Z0-9$]/g, '_');
|
|
346
|
+
// ✅ Apply namespace convention to .js imports too
|
|
347
|
+
const namespace = resolvedPath.replace(/\.(jux|js)$/, '').replace(/[\/\\]/g, '$').replace(/[^a-zA-Z0-9$]/g, '_');
|
|
302
348
|
importReplacements.set(spec.local.name, `${spec.imported.name}$${namespace}`);
|
|
303
349
|
}
|
|
304
350
|
});
|
|
351
|
+
// Remove local imports as they will be inlined in main.js
|
|
352
|
+
nodesToRemove.push({ start: node.start, end: node.end });
|
|
305
353
|
}
|
|
306
|
-
nodesToRemove.push({ start: node.start, end: node.end });
|
|
307
354
|
}
|
|
308
355
|
if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') {
|
|
309
356
|
nodesToRemove.push({ start: node.start, end: node.end });
|
|
@@ -341,7 +388,13 @@ function generateRouterBundle(views, routes, sharedModules = new Map(), allImpor
|
|
|
341
388
|
return 'juxscript' + subPath.replace(/\\/g, '/');
|
|
342
389
|
}
|
|
343
390
|
if (joinedPath.includes('lib/components')) return 'juxscript/components';
|
|
344
|
-
|
|
391
|
+
|
|
392
|
+
// ✅ Fix: Ensure local relative paths start with ./ or /
|
|
393
|
+
let normalized = joinedPath.replace(/\\/g, '/');
|
|
394
|
+
if (!normalized.startsWith('.') && !normalized.startsWith('/')) {
|
|
395
|
+
normalized = './' + normalized;
|
|
396
|
+
}
|
|
397
|
+
return normalized;
|
|
345
398
|
};
|
|
346
399
|
|
|
347
400
|
const importList = Array.isArray(allImports) ? allImports : Array.from(allImports);
|
|
@@ -353,7 +406,6 @@ function generateRouterBundle(views, routes, sharedModules = new Map(), allImpor
|
|
|
353
406
|
const node = ast.body[0];
|
|
354
407
|
if (node && node.type === 'ImportDeclaration') {
|
|
355
408
|
const rawSource = node.source.value;
|
|
356
|
-
if (rawSource.endsWith('.jux')) continue;
|
|
357
409
|
const source = getCanonicalSource(rawSource, filePath);
|
|
358
410
|
if (!mergedImports.has(source)) mergedImports.set(source, { defaults: new Set(), named: new Set(), namespace: null });
|
|
359
411
|
const storage = mergedImports.get(source);
|