juxscript 1.0.81 → 1.0.83

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.
@@ -189,6 +189,12 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
189
189
  if (isJux) {
190
190
  fileToFunction.set(relativePath, cleanFunctionName);
191
191
  fileToFunction.set(relativePath.split(path.sep).join('/'), cleanFunctionName);
192
+
193
+ // ✅ FIX: Register lookup without extension to match juxconfig routes like "/path/to/view"
194
+ const noExt = relativePath.replace(/\.jux$/, '');
195
+ const noExtNormalized = noExt.split(path.sep).join('/');
196
+ fileToFunction.set(noExt, cleanFunctionName);
197
+ fileToFunction.set(noExtNormalized, cleanFunctionName);
192
198
  }
193
199
 
194
200
  const routePath = routePrefix + '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
@@ -250,12 +256,20 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
250
256
 
251
257
  if (pages) {
252
258
  const resolveAndAddRoute = (urlPath, targetFile) => {
259
+ // ✅ FIX: Handle ./ and / prefixes in config paths
253
260
  const cleanTarget = targetFile.replace(/^(\.\/|\/)/, '');
254
- const funcName = fileToFunction.get(cleanTarget);
261
+ let funcName = fileToFunction.get(cleanTarget);
262
+
263
+ // Try appending .jux if not found and no extension provided
264
+ if (!funcName && !cleanTarget.endsWith('.jux')) {
265
+ funcName = fileToFunction.get(cleanTarget + '.jux');
266
+ }
267
+
255
268
  if (funcName) {
269
+ console.log(` 📍 Custom Route: ${urlPath.padEnd(25)} -> ${funcName} (${targetFile})`);
256
270
  routes.unshift({ path: urlPath, functionName: funcName });
257
271
  } else {
258
- console.warn(` ⚠️ Route target not found: ${targetFile}`);
272
+ console.warn(` ⚠️ Route target not found: ${targetFile} (Cleaned: ${cleanTarget})`);
259
273
  }
260
274
  };
261
275
  Object.entries(pages).forEach(([key, value]) => {
@@ -287,44 +301,78 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
287
301
  }
288
302
 
289
303
  function extractSharedModule(juxContent, moduleName, sourceFilePath) {
290
- const exportLines = [];
291
- try {
292
- const ast = acorn.parse(juxContent, { ecmaVersion: 'latest', sourceType: 'module' });
293
- ast.body.forEach(node => {
294
- if (node.type === 'ExportNamedDeclaration' && node.declaration) {
295
- const exportCode = juxContent.slice(node.start, node.end).replace(/^export\s+/, '');
296
- exportLines.push(`// From: ${sourceFilePath}\n${namespaceExportedIdentifiers(exportCode, sourceFilePath)}`);
297
- } else if (node.type === 'ExportDefaultDeclaration') {
298
- const exportCode = juxContent.slice(node.start, node.end).replace(/^export\s+default\s+/, '');
299
- exportLines.push(`// From: ${sourceFilePath}\n${namespaceExportedIdentifiers(exportCode, sourceFilePath)}`);
300
- }
301
- });
302
- } catch (err) {
303
- throw new Error(`Invalid JavaScript syntax in ${sourceFilePath}.`);
304
- }
305
- return exportLines.join('\n\n');
306
- }
307
-
308
- function namespaceExportedIdentifiers(code, sourceFilePath) {
309
- // ✅ FIX: Strip .jux AND .js extensions for stable namespacing
304
+ // 1. Calculate Namespace
305
+ // Ensure we have a valid JS identifier suffix
310
306
  const namespace = sourceFilePath.replace(/\.(jux|js)$/, '').replace(/[\/\\]/g, '$').replace(/[^a-zA-Z0-9$]/g, '_');
307
+
308
+ let ast;
311
309
  try {
312
- const ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
313
- const identifiers = [];
314
- ast.body.forEach(node => {
315
- if (node.type === 'FunctionDeclaration' && node.id) identifiers.push(node.id);
316
- else if (node.type === 'VariableDeclaration') node.declarations.forEach(d => { if (d.id.type === 'Identifier') identifiers.push(d.id); });
317
- else if (node.type === 'ClassDeclaration' && node.id) identifiers.push(node.id);
318
- });
319
- identifiers.sort((a, b) => b.start - a.start);
320
- let result = code;
321
- identifiers.forEach(id => {
322
- result = result.slice(0, id.start) + `${id.name}$${namespace}` + result.slice(id.end);
323
- });
324
- return result;
310
+ ast = acorn.parse(juxContent, { ecmaVersion: 'latest', sourceType: 'module' });
325
311
  } catch (err) {
326
- return code;
312
+ throw new Error(`Invalid JavaScript syntax in ${sourceFilePath}: ${err.message}`);
327
313
  }
314
+
315
+ // 2. Identify ALL Top-Level Identifiers (Exported or Local) to rename
316
+ // We must rename everything to avoid global collision in the flat bundle
317
+ const identifiersToRename = new Map();
318
+
319
+ const addId = (idNode) => {
320
+ if (idNode && idNode.type === 'Identifier') {
321
+ identifiersToRename.set(idNode.name, `${idNode.name}$${namespace}`);
322
+ }
323
+ };
324
+
325
+ ast.body.forEach(node => {
326
+ if (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') {
327
+ addId(node.id);
328
+ } else if (node.type === 'VariableDeclaration') {
329
+ node.declarations.forEach(d => addId(d.id));
330
+ } else if ((node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') && node.declaration) {
331
+ const d = node.declaration;
332
+ if (d.type === 'FunctionDeclaration' || d.type === 'ClassDeclaration') addId(d.id);
333
+ else if (d.type === 'VariableDeclaration') d.declarations.forEach(v => addId(v.id));
334
+ }
335
+ });
336
+
337
+ // 3. Reconstruct Code Line-by-Line (Node-by-Node)
338
+ const outputLines = [`// From: ${sourceFilePath}`];
339
+
340
+ // Sort keys by length desc to avoid partial replacement issues
341
+ const sortedKeys = Array.from(identifiersToRename.keys()).sort((a, b) => b.length - a.length);
342
+
343
+ ast.body.forEach(node => {
344
+ // Skip Imports (handled globally in bundling phase)
345
+ if (node.type === 'ImportDeclaration') return;
346
+ // Skip "export { a, b }" (re-exports) as 'a' and 'b' are already declared/renamed locally
347
+ if (node.type === 'ExportNamedDeclaration' && !node.declaration) return;
348
+
349
+ // Extract relevant code slice
350
+ let code = '';
351
+ if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') {
352
+ if (node.declaration) {
353
+ code = juxContent.slice(node.declaration.start, node.declaration.end);
354
+ }
355
+ } else {
356
+ code = juxContent.slice(node.start, node.end);
357
+ }
358
+
359
+ if (!code) return;
360
+
361
+ // Replace Identifiers in this block
362
+ // Regex:
363
+ // (?<!\.) -> Negative lookbehind to avoid property access (obj.foo)
364
+ // \bkey\b -> Word boundary
365
+ // (?!\s*:) -> Negative lookahead to avoid object keys ({ foo: 1 })
366
+ for (const key of sortedKeys) {
367
+ const namespaced = identifiersToRename.get(key);
368
+ const regex = new RegExp(`(?<!\\.)\\b${key}\\b(?!\\s*:)`, 'g');
369
+ code = code.replace(regex, namespaced);
370
+ }
371
+
372
+ outputLines.push(code);
373
+ });
374
+
375
+ return outputLines.join('\n\n');
328
376
  }
329
377
 
330
378
  function transformJuxToViewFunction(juxContent, functionName, pageName, relativePath, sharedModules, bundledPaths) {
@@ -350,9 +398,11 @@ function transformJuxToViewFunction(juxContent, functionName, pageName, relative
350
398
  importReplacements.set(spec.local.name, `${spec.imported.name}$${namespace}`);
351
399
  }
352
400
  });
353
- // Remove local imports as they will be inlined in main.js
354
- nodesToRemove.push({ start: node.start, end: node.end });
355
401
  }
402
+
403
+ // 🔥 FIX: Always remove ImportDeclaration from the view function contents.
404
+ // Leftover imports inside a function body cause SyntaxError.
405
+ nodesToRemove.push({ start: node.start, end: node.end });
356
406
  }
357
407
  if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') {
358
408
  nodesToRemove.push({ start: node.start, end: node.end });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.81",
3
+ "version": "1.0.83",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",