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 CHANGED
@@ -1,63 +1,4 @@
1
- import { Element, Grid } from 'juxscript';
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
@@ -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
- findFiles(filePath, extension, fileList);
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
- if (juxFiles.length === 0) {
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 juxFile of juxFiles) {
167
- const relativePath = path.relative(projectRoot, juxFile);
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
- fileToFunction.set(relativePath, cleanFunctionName);
173
- fileToFunction.set(relativePath.split(path.sep).join('/'), cleanFunctionName);
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(juxContent, { ecmaVersion: 'latest', sourceType: 'module' });
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 = juxContent.slice(node.start, node.end);
184
- allImports.push({ code: importStatement, filePath: relativePath });
204
+ const importStatement = fileContent.slice(node.start, node.end);
185
205
  const moduleName = node.source.value;
186
- if (!moduleName.startsWith('.') && !moduleName.startsWith('/') && !moduleName.startsWith('http') && !moduleName.startsWith('juxscript')) {
187
- externalModules.add(moduleName);
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
- const hasExports = /^\s*export\s+(const|let|function|class|{)/m.test(juxContent);
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(juxContent, rawFunctionName, relativePath);
234
+ const exportCode = extractSharedModule(fileContent, rawFunctionName, relativePath);
199
235
  if (exportCode.trim()) sharedModules.set(exportKey, exportCode);
200
236
  }
201
237
 
202
- const viewFunction = transformJuxToViewFunction(juxContent, rawFunctionName, parsedPath.name, relativePath, sharedModules);
203
- views.push(viewFunction);
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
- if (config?.defaults?.autoRoute !== false) {
206
- routes.push({ path: cleanRoutePath, functionName: cleanFunctionName });
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
- const namespace = sourceFilePath.replace(/\.jux$/, '').replace(/[\/\\]/g, '$').replace(/[^a-zA-Z0-9$]/g, '_');
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
- if (importPath.endsWith('.jux')) {
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
- const resolvedPath = resolveImportPath(importPath, relativePath);
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
- return joinedPath.replace(/\\/g, '/');
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.70",
3
+ "version": "1.0.72",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",