juxscript 1.0.82 → 1.0.84

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.
@@ -238,7 +238,8 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
238
238
  const hasExports = /^\s*export\s+(const|let|function|class|{)/m.test(fileContent);
239
239
  if (hasExports) {
240
240
  const exportKey = relativePath;
241
- const exportCode = extractSharedModule(fileContent, rawFunctionName, relativePath);
241
+ // FIX: Pass bundledPaths so imports can be resolved and rewritten in shared code
242
+ const exportCode = extractSharedModule(fileContent, rawFunctionName, relativePath, bundledPaths);
242
243
  if (exportCode.trim()) sharedModules.set(exportKey, exportCode);
243
244
  }
244
245
 
@@ -300,45 +301,97 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
300
301
  };
301
302
  }
302
303
 
303
- function extractSharedModule(juxContent, moduleName, sourceFilePath) {
304
- const exportLines = [];
305
- try {
306
- const ast = acorn.parse(juxContent, { ecmaVersion: 'latest', sourceType: 'module' });
307
- ast.body.forEach(node => {
308
- if (node.type === 'ExportNamedDeclaration' && node.declaration) {
309
- const exportCode = juxContent.slice(node.start, node.end).replace(/^export\s+/, '');
310
- exportLines.push(`// From: ${sourceFilePath}\n${namespaceExportedIdentifiers(exportCode, sourceFilePath)}`);
311
- } else if (node.type === 'ExportDefaultDeclaration') {
312
- const exportCode = juxContent.slice(node.start, node.end).replace(/^export\s+default\s+/, '');
313
- exportLines.push(`// From: ${sourceFilePath}\n${namespaceExportedIdentifiers(exportCode, sourceFilePath)}`);
314
- }
315
- });
316
- } catch (err) {
317
- throw new Error(`Invalid JavaScript syntax in ${sourceFilePath}.`);
318
- }
319
- return exportLines.join('\n\n');
320
- }
321
-
322
- function namespaceExportedIdentifiers(code, sourceFilePath) {
323
- // ✅ FIX: Strip .jux AND .js extensions for stable namespacing
304
+ function extractSharedModule(juxContent, moduleName, sourceFilePath, bundledPaths) {
305
+ // 1. Calculate Namespace
324
306
  const namespace = sourceFilePath.replace(/\.(jux|js)$/, '').replace(/[\/\\]/g, '$').replace(/[^a-zA-Z0-9$]/g, '_');
307
+
308
+ let ast;
325
309
  try {
326
- const ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
327
- const identifiers = [];
328
- ast.body.forEach(node => {
329
- if (node.type === 'FunctionDeclaration' && node.id) identifiers.push(node.id);
330
- else if (node.type === 'VariableDeclaration') node.declarations.forEach(d => { if (d.id.type === 'Identifier') identifiers.push(d.id); });
331
- else if (node.type === 'ClassDeclaration' && node.id) identifiers.push(node.id);
332
- });
333
- identifiers.sort((a, b) => b.start - a.start);
334
- let result = code;
335
- identifiers.forEach(id => {
336
- result = result.slice(0, id.start) + `${id.name}$${namespace}` + result.slice(id.end);
337
- });
338
- return result;
310
+ ast = acorn.parse(juxContent, { ecmaVersion: 'latest', sourceType: 'module' });
339
311
  } catch (err) {
340
- return code;
312
+ throw new Error(`Invalid JavaScript syntax in ${sourceFilePath}: ${err.message}`);
341
313
  }
314
+
315
+ // 2. Identify ALL Identifiers to rename (Local defs + Imports)
316
+ // We must rename everything to avoid global collision in the flat bundle
317
+ const identifiersToRename = new Map();
318
+
319
+ // A. Local Declarations (Self-namespacing)
320
+ const addId = (idNode) => {
321
+ if (idNode && idNode.type === 'Identifier') {
322
+ identifiersToRename.set(idNode.name, `${idNode.name}$${namespace}`);
323
+ }
324
+ };
325
+
326
+ ast.body.forEach(node => {
327
+ if (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') {
328
+ addId(node.id);
329
+ } else if (node.type === 'VariableDeclaration') {
330
+ node.declarations.forEach(d => addId(d.id));
331
+ } else if ((node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') && node.declaration) {
332
+ const d = node.declaration;
333
+ if (d.type === 'FunctionDeclaration' || d.type === 'ClassDeclaration') addId(d.id);
334
+ else if (d.type === 'VariableDeclaration') d.declarations.forEach(v => addId(v.id));
335
+ } else if (node.type === 'ImportDeclaration') {
336
+ // B. Imports (Rewriting references to other bundled modules)
337
+ const importPath = node.source.value;
338
+ const resolvedPath = resolveImportPath(importPath, sourceFilePath);
339
+ // Check if this import is pointing to a bundled file
340
+ const isBundled = (bundledPaths && bundledPaths.has(resolvedPath)) || importPath.endsWith('.jux');
341
+
342
+ if (isBundled) {
343
+ const targetNamespace = resolvedPath.replace(/\.(jux|js)$/, '').replace(/[\/\\]/g, '$').replace(/[^a-zA-Z0-9$]/g, '_');
344
+ node.specifiers.forEach(spec => {
345
+ if (spec.type === 'ImportSpecifier') {
346
+ identifiersToRename.set(spec.local.name, `${spec.imported.name}$${targetNamespace}`);
347
+ } else if (spec.type === 'ImportDefaultSpecifier') {
348
+ // Best effort for defaults in this flat bundle scheme usually implies named export match or similar convention
349
+ // For now, assuming standard View exports which don't usually default export to shared
350
+ }
351
+ });
352
+ }
353
+ }
354
+ });
355
+
356
+ // 3. Reconstruct Code Line-by-Line (Node-by-Node)
357
+ const outputLines = [`// From: ${sourceFilePath}`];
358
+
359
+ // Sort keys by length desc to avoid partial replacement issues
360
+ const sortedKeys = Array.from(identifiersToRename.keys()).sort((a, b) => b.length - a.length);
361
+
362
+ ast.body.forEach(node => {
363
+ // Skip Imports (handled globally in bundling phase)
364
+ if (node.type === 'ImportDeclaration') return;
365
+ // Skip "export { a, b }" (re-exports) as 'a' and 'b' are already declared/renamed locally
366
+ if (node.type === 'ExportNamedDeclaration' && !node.declaration) return;
367
+
368
+ // Extract relevant code slice
369
+ let code = '';
370
+ if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') {
371
+ if (node.declaration) {
372
+ code = juxContent.slice(node.declaration.start, node.declaration.end);
373
+ }
374
+ } else {
375
+ code = juxContent.slice(node.start, node.end);
376
+ }
377
+
378
+ if (!code) return;
379
+
380
+ // Replace Identifiers in this block
381
+ // Regex:
382
+ // (?<!\.) -> Negative lookbehind to avoid property access (obj.foo)
383
+ // \bkey\b -> Word boundary
384
+ // (?!\s*:) -> Negative lookahead to avoid object keys ({ foo: 1 })
385
+ for (const key of sortedKeys) {
386
+ const namespaced = identifiersToRename.get(key);
387
+ const regex = new RegExp(`(?<!\\.)\\b${key}\\b(?!\\s*:)`, 'g');
388
+ code = code.replace(regex, namespaced);
389
+ }
390
+
391
+ outputLines.push(code);
392
+ });
393
+
394
+ return outputLines.join('\n\n');
342
395
  }
343
396
 
344
397
  function transformJuxToViewFunction(juxContent, functionName, pageName, relativePath, sharedModules, bundledPaths) {
@@ -367,9 +420,7 @@ function transformJuxToViewFunction(juxContent, functionName, pageName, relative
367
420
  }
368
421
 
369
422
  // 🔥 FIX: Always remove ImportDeclaration from the view function contents.
370
- // External imports are hoisted to 'allImports' and placed at the top of main.js.
371
- // Local/Bundled imports are hoisted via sharedModules mechanism with namespacing.
372
- // Leaving import statements inside the function body causes SyntaxError.
423
+ // Leftover imports inside a function body cause SyntaxError.
373
424
  nodesToRemove.push({ start: node.start, end: node.end });
374
425
  }
375
426
  if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.82",
3
+ "version": "1.0.84",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",