juxscript 1.0.61 → 1.0.63

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.
Files changed (141) hide show
  1. package/bin/cli.js +161 -293
  2. package/docs/v2comps/HEADLESS.md +83 -0
  3. package/docs/v2comps/ISOMORPHISM.md +10 -0
  4. package/juxconfig.example.js +63 -58
  5. package/lib/componentsv2/base/BaseEngine.js +258 -0
  6. package/lib/componentsv2/base/BaseEngine.js.map +1 -0
  7. package/lib/componentsv2/base/BaseEngine.ts +303 -0
  8. package/lib/componentsv2/base/BaseSkin.js +108 -0
  9. package/lib/componentsv2/base/BaseSkin.js.map +1 -0
  10. package/lib/componentsv2/base/BaseSkin.ts +137 -0
  11. package/lib/componentsv2/base/GlobalBus.js +56 -0
  12. package/lib/componentsv2/base/GlobalBus.js.map +1 -0
  13. package/lib/componentsv2/base/GlobalBus.ts +60 -0
  14. package/lib/componentsv2/base/State.js +68 -0
  15. package/lib/componentsv2/base/State.js.map +1 -0
  16. package/lib/componentsv2/base/State.ts +62 -0
  17. package/lib/componentsv2/grid/component.js +41 -0
  18. package/lib/componentsv2/grid/component.js.map +1 -0
  19. package/lib/componentsv2/grid/component.ts +67 -0
  20. package/lib/componentsv2/grid/engine.js +73 -0
  21. package/lib/componentsv2/grid/engine.js.map +1 -0
  22. package/lib/componentsv2/grid/engine.ts +110 -0
  23. package/lib/componentsv2/grid/skin.js +95 -0
  24. package/lib/componentsv2/grid/skin.js.map +1 -0
  25. package/lib/componentsv2/grid/skin.ts +105 -0
  26. package/lib/componentsv2/grid/structure.css +58 -0
  27. package/lib/componentsv2/index.js +218 -0
  28. package/lib/componentsv2/index.js.map +1 -0
  29. package/lib/componentsv2/index.ts +253 -0
  30. package/lib/componentsv2/input/component.js +21 -0
  31. package/lib/componentsv2/input/component.js.map +1 -0
  32. package/lib/componentsv2/input/component.ts +28 -0
  33. package/lib/componentsv2/input/engine.js +50 -0
  34. package/lib/componentsv2/input/engine.js.map +1 -0
  35. package/lib/componentsv2/input/engine.ts +76 -0
  36. package/lib/componentsv2/input/skin.js +91 -0
  37. package/lib/componentsv2/input/skin.js.map +1 -0
  38. package/lib/componentsv2/input/skin.ts +91 -0
  39. package/lib/componentsv2/input/structure.css +47 -0
  40. package/lib/componentsv2/list/component.js +83 -0
  41. package/lib/componentsv2/list/component.js.map +1 -0
  42. package/lib/componentsv2/list/component.ts +97 -0
  43. package/lib/componentsv2/list/engine.js +261 -0
  44. package/lib/componentsv2/list/engine.js.map +1 -0
  45. package/lib/componentsv2/list/engine.ts +345 -0
  46. package/lib/componentsv2/list/skin.js +343 -0
  47. package/lib/componentsv2/list/skin.js.map +1 -0
  48. package/lib/componentsv2/list/skin.ts +367 -0
  49. package/lib/componentsv2/list/structure.css +359 -0
  50. package/lib/componentsv2/plugins/ClientSQLitePlugin.js +130 -0
  51. package/lib/componentsv2/plugins/ClientSQLitePlugin.js.map +1 -0
  52. package/lib/componentsv2/plugins/ClientSQLitePlugin.ts +154 -0
  53. package/lib/componentsv2/plugins/IndexedDBPlugin.js +75 -0
  54. package/lib/componentsv2/plugins/IndexedDBPlugin.js.map +1 -0
  55. package/lib/componentsv2/plugins/IndexedDBPlugin.ts +96 -0
  56. package/lib/componentsv2/plugins/LocalStoragePlugin.js +65 -0
  57. package/lib/componentsv2/plugins/LocalStoragePlugin.js.map +1 -0
  58. package/lib/componentsv2/plugins/LocalStoragePlugin.ts +86 -0
  59. package/lib/componentsv2/plugins/ServerSQLitePlugin.js +70 -0
  60. package/lib/componentsv2/plugins/ServerSQLitePlugin.js.map +1 -0
  61. package/lib/componentsv2/plugins/ServerSQLitePlugin.ts +99 -0
  62. package/lib/componentsv2/stubs/ComponentComposition.ts.stub +32 -0
  63. package/lib/componentsv2/stubs/ComponentEngine.ts.stub +36 -0
  64. package/lib/componentsv2/stubs/ComponentSkin.ts.stub +34 -0
  65. package/lib/componentsv2/stubs/ComponentStructure.css.stub +13 -0
  66. package/lib/componentsv2/tools/CreateSkin.js +62 -0
  67. package/lib/componentsv2/tools/DocSpam.js +134 -0
  68. package/lib/componentsv2/tools/FluencyAudit.js +141 -0
  69. package/lib/componentsv2/tools/OptionsAudit.js +177 -0
  70. package/lib/componentsv2/tools/Scaffold.js +140 -0
  71. package/lib/utils/fetch.js +428 -0
  72. package/lib/utils/fetch.js.map +1 -0
  73. package/machinery/build.js +2 -1
  74. package/machinery/compiler.js +200 -37
  75. package/machinery/config.js +93 -6
  76. package/machinery/diagnose.js +72 -0
  77. package/machinery/jux-module-pattern.md +118 -0
  78. package/machinery/server.js +23 -7
  79. package/machinery/verifier.js +143 -0
  80. package/machinery/watcher.js +53 -64
  81. package/package.json +11 -2
  82. package/lib/components/alert.ts +0 -200
  83. package/lib/components/app.ts +0 -258
  84. package/lib/components/badge.ts +0 -101
  85. package/lib/components/base/BaseComponent.ts +0 -417
  86. package/lib/components/base/FormInput.ts +0 -227
  87. package/lib/components/button.ts +0 -178
  88. package/lib/components/card.ts +0 -173
  89. package/lib/components/chart.ts +0 -231
  90. package/lib/components/checkbox.ts +0 -242
  91. package/lib/components/code.ts +0 -123
  92. package/lib/components/container.ts +0 -140
  93. package/lib/components/data.ts +0 -135
  94. package/lib/components/datepicker.ts +0 -234
  95. package/lib/components/dialog.ts +0 -172
  96. package/lib/components/divider.ts +0 -100
  97. package/lib/components/dropdown.ts +0 -186
  98. package/lib/components/element.ts +0 -267
  99. package/lib/components/error-handler.ts +0 -285
  100. package/lib/components/fileupload.ts +0 -309
  101. package/lib/components/grid.ts +0 -291
  102. package/lib/components/guard.ts +0 -92
  103. package/lib/components/heading.ts +0 -96
  104. package/lib/components/helpers.ts +0 -41
  105. package/lib/components/hero.ts +0 -224
  106. package/lib/components/icon.ts +0 -160
  107. package/lib/components/icons.ts +0 -175
  108. package/lib/components/include.ts +0 -440
  109. package/lib/components/input.ts +0 -457
  110. package/lib/components/list.ts +0 -419
  111. package/lib/components/loading.ts +0 -100
  112. package/lib/components/menu.ts +0 -260
  113. package/lib/components/modal.ts +0 -239
  114. package/lib/components/nav.ts +0 -257
  115. package/lib/components/paragraph.ts +0 -97
  116. package/lib/components/progress.ts +0 -139
  117. package/lib/components/radio.ts +0 -278
  118. package/lib/components/req.ts +0 -302
  119. package/lib/components/script.ts +0 -43
  120. package/lib/components/select.ts +0 -252
  121. package/lib/components/sidebar.ts +0 -167
  122. package/lib/components/style.ts +0 -43
  123. package/lib/components/switch.ts +0 -246
  124. package/lib/components/table.ts +0 -1249
  125. package/lib/components/tabs.ts +0 -250
  126. package/lib/components/theme-toggle.ts +0 -300
  127. package/lib/components/token-calculator.ts +0 -313
  128. package/lib/components/tooltip.ts +0 -144
  129. package/lib/components/view.ts +0 -190
  130. package/lib/components/write.ts +0 -272
  131. package/lib/jux.ts +0 -365
  132. package/lib/layouts/default.css +0 -260
  133. package/lib/layouts/figma.css +0 -334
  134. package/lib/reactivity/state.ts +0 -78
  135. package/machinery/bundleAssets.js +0 -0
  136. package/machinery/bundleJux.js +0 -0
  137. package/machinery/bundleVendors.js +0 -0
  138. package/presets/default/all.jux +0 -343
  139. package/presets/default/index.jux +0 -90
  140. package/presets/default/layout.css +0 -1612
  141. package/presets/default/layout.jux +0 -55
@@ -1,5 +1,5 @@
1
1
  import fs from 'fs';
2
- import path from 'path';
2
+ import path, { parse } from 'path';
3
3
  import esbuild from 'esbuild';
4
4
  import * as acorn from 'acorn';
5
5
 
@@ -260,11 +260,11 @@ function findProjectFiles(dir, extensions, fileList = [], rootDir = dir, exclude
260
260
 
261
261
  /**
262
262
  * Bundle all .jux files into a single router-based main.js
263
- * ✅ MODIFIED: Vendor dependencies locally
263
+ * ✅ MODIFIED: Vendor dependencies locally + User Routes + Smart Import Merging
264
264
  */
265
265
  export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {}) {
266
266
  const startTime = performance.now();
267
- const { routePrefix = '' } = options;
267
+ const { routePrefix = '', config } = options;
268
268
 
269
269
  console.log('🔀 Bundling .jux files into router...');
270
270
 
@@ -281,15 +281,20 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
281
281
  };
282
282
  }
283
283
 
284
- console.log(` Found ${juxFiles.length} .jux file(s)`);
284
+ const pages = config?.pages || {};
285
285
 
286
286
  const fileTimings = [];
287
287
  const views = [];
288
288
  const routes = [];
289
289
  const sharedModules = new Map(); // ✅ Map<filePath, exportCode>
290
- const allImports = new Set();
290
+
291
+ // ✅ CHANGE: imports collection now stores context { code, filePath }
292
+ const allImports = [];
291
293
  const externalModules = new Set(); // ✅ NEW: Track external modules directly
292
294
 
295
+ // ✅ MAP for user config routing: Key = relative file path, Value = CleanFunctionName
296
+ const fileToFunction = new Map();
297
+
293
298
  for (const juxFile of juxFiles) {
294
299
  const fileStartTime = performance.now();
295
300
 
@@ -306,6 +311,11 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
306
311
  .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
307
312
  .join('');
308
313
 
314
+ // ✅ Store mapping for Config Route Resolution
315
+ // Store both 'path/file.jux' and normalized versions
316
+ fileToFunction.set(relativePath, cleanFunctionName);
317
+ fileToFunction.set(relativePath.split(path.sep).join('/'), cleanFunctionName);
318
+
309
319
  const routePath = routePrefix + '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
310
320
  const cleanRoutePath = routePath.replace(/\/+/g, '/');
311
321
 
@@ -321,7 +331,12 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
321
331
  ast.body.forEach(node => {
322
332
  if (node.type === 'ImportDeclaration') {
323
333
  const importStatement = juxContent.slice(node.start, node.end);
324
- allImports.add(importStatement);
334
+
335
+ // ✅ Store Object with context instead of raw string
336
+ allImports.push({
337
+ code: importStatement,
338
+ filePath: relativePath // relative to projectRoot
339
+ });
325
340
 
326
341
  const moduleName = node.source.value;
327
342
  if (!moduleName.startsWith('.') &&
@@ -359,13 +374,52 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
359
374
  );
360
375
 
361
376
  views.push(viewFunction);
362
- routes.push({ path: cleanRoutePath, functionName: cleanFunctionName });
377
+
378
+ // Auto-route based on filesystem
379
+ if (config?.defaults?.autoRoute !== false) {
380
+ routes.push({ path: cleanRoutePath, functionName: cleanFunctionName });
381
+ }
363
382
 
364
383
  const fileTime = performance.now() - fileStartTime;
365
384
  fileTimings.push({ file: relativePath, time: fileTime });
366
385
 
367
386
  const exportNote = hasExports ? ' [+exports]' : '';
368
- console.log(` ✓ ${relativePath} → ${cleanFunctionName}()${exportNote} (${fileTime.toFixed(1)}ms)`);
387
+ // console.log(` ✓ ${relativePath} → ${cleanFunctionName}()${exportNote} (${fileTime.toFixed(1)}ms)`);
388
+ }
389
+
390
+ // ✅ PROCESS USER CONFIGURED ROUTES
391
+ if (pages) {
392
+ console.log(' 🗺️ Mapping user-configured routes...');
393
+
394
+ const resolveAndAddRoute = (urlPath, targetFile) => {
395
+ // Clean up target file path (remove leading ./ and /)
396
+ const cleanTarget = targetFile.replace(/^(\.\/|\/)/, '');
397
+ const funcName = fileToFunction.get(cleanTarget);
398
+
399
+ if (funcName) {
400
+ // User routes take precedence (prepend to list)
401
+ routes.unshift({ path: urlPath, functionName: funcName });
402
+ console.log(` ➕ ${urlPath} → ${funcName}`);
403
+ } else {
404
+ console.warn(` ⚠️ Route target not found: ${targetFile} (Available keys: ${fileToFunction.size})`);
405
+ }
406
+ };
407
+
408
+ Object.entries(pages).forEach(([key, value]) => {
409
+ if (typeof value === 'string') {
410
+ // Direct mapping: '/' -> './experiments/state.jux'
411
+ resolveAndAddRoute(key, value);
412
+ } else if (typeof value === 'object') {
413
+ // Group mapping
414
+ const prefix = value.prefix || '';
415
+ const groupRoutes = value.routes || {};
416
+
417
+ Object.entries(groupRoutes).forEach(([routePath, targetFile]) => {
418
+ const fullPath = (prefix + routePath).replace(/\/+/g, '/');
419
+ resolveAndAddRoute(fullPath, targetFile);
420
+ });
421
+ }
422
+ });
369
423
  }
370
424
 
371
425
  // ✅ Show slowest files if any took >50ms
@@ -386,7 +440,7 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
386
440
 
387
441
  const vendoredPaths = await vendorExternalDependencies(externalModules, distDir);
388
442
 
389
- const routerCode = generateRouterBundle(views, routes, sharedModules, allImports);
443
+ const routerCode = generateRouterBundle(views, routes, sharedModules, allImports, projectRoot);
390
444
  const bundleGenTime = performance.now() - bundleStartTime;
391
445
 
392
446
  const mainJsFilename = 'main.js';
@@ -578,6 +632,8 @@ function transformJuxToViewFunction(juxContent, functionName, pageName, relative
578
632
  });
579
633
  }
580
634
 
635
+ // ✅ CRITICAL FIX: Remove ALL imports so they don't break function scope
636
+ // They are hoisted by generateRouterBundle
581
637
  nodesToRemove.push({ start: node.start, end: node.end });
582
638
  }
583
639
 
@@ -652,35 +708,111 @@ function resolveImportPath(importPath, currentFilePath) {
652
708
 
653
709
  /**
654
710
  * Generate complete router bundle
711
+ * ✅ IMPROVED: Context-Aware Import Resolution (resolves ../paths before dedupe)
655
712
  */
656
- function generateRouterBundle(views, routes, sharedModules = new Map(), allImports = new Set()) {
657
- const libImport = './lib/jux.js';
713
+ function generateRouterBundle(views, routes, sharedModules = new Map(), allImports = [], projectRoot = '') {
714
+ const libImport = '/lib/jux.js'; // ✅ Use absolute path for core lib
658
715
 
659
- // Filter out redundant imports
660
- const filteredImports = Array.from(allImports)
661
- .filter(imp => {
662
- const impStr = imp.trim();
716
+ // Map<NormalizedSource, { defaults: Set<string>, named: Set<string>, namespace: string|null }>
717
+ const mergedImports = new Map();
663
718
 
664
- // Remove imports from 'juxscript' (already imported)
665
- if (impStr.includes("from 'juxscript'") || impStr.includes('from "juxscript"')) {
666
- return false;
667
- }
719
+ // Helper to resolve relative paths to a canonical string
720
+ const getCanonicalSource = (source, contextFilePath) => {
721
+ // 1. Keep externals/aliases/css/http as-is
722
+ if (!source.startsWith('.')) return source;
723
+ if (source.includes('.jux')) return source; // Skipped later anyway
668
724
 
669
- // Remove imports from 'juxscript/reactivity' (already available)
670
- if (impStr.includes("from 'juxscript/reactivity'") || impStr.includes('from "juxscript/reactivity"')) {
671
- return false;
672
- }
725
+ // 2. Resolve relative paths against the file they came from
726
+ // contextFilePath is relative to projectRoot (e.g. 'experiments/list.jux')
727
+ const dir = path.dirname(contextFilePath);
673
728
 
674
- // CRITICAL: Remove ANY import that references a .jux file
675
- if (impStr.includes('.jux')) {
676
- return false;
729
+ // joinedPath is relative to projectRoot (e.g. '../lib/componentsv2')
730
+ const joinedPath = path.join(dir, source);
731
+
732
+ // 3. Normalize to alias if it points to lib
733
+ // Matches 'lib/componentsv2' or '../lib/componentsv2' variants
734
+ if (joinedPath.includes('lib/componentsv2')) {
735
+ return 'juxscript/componentsv2';
736
+ }
737
+ if (joinedPath.includes('lib/components')) {
738
+ return 'juxscript/components';
739
+ }
740
+
741
+ // 4. Fallback: return as-is (but normalized separators)
742
+ return joinedPath.replace(/\\/g, '/');
743
+ };
744
+
745
+ const importList = Array.isArray(allImports) ? allImports : Array.from(allImports);
746
+
747
+ for (const item of importList) {
748
+ const code = typeof item === 'string' ? item : item.code;
749
+ const filePath = typeof item === 'string' ? '' : item.filePath; // Context file path
750
+
751
+ try {
752
+ const ast = acorn.parse(code.trim(), { ecmaVersion: 'latest', sourceType: 'module' });
753
+ const node = ast.body[0];
754
+
755
+ if (node && node.type === 'ImportDeclaration') {
756
+ const rawSource = node.source.value;
757
+
758
+ // Skip base libs handled manually
759
+ if (rawSource === 'juxscript' || rawSource === 'juxscript/reactivity') continue;
760
+ if (rawSource.endsWith('.jux')) continue;
761
+
762
+ // ✅ RESOLVE SOURCE USING CONTEXT (Fixes ./lib/.. vs ../../lib/..)
763
+ const source = getCanonicalSource(rawSource, filePath);
764
+
765
+ if (!mergedImports.has(source)) {
766
+ mergedImports.set(source, { defaults: new Set(), named: new Set(), namespace: null });
767
+ }
768
+ const storage = mergedImports.get(source);
769
+
770
+ node.specifiers.forEach(spec => {
771
+ if (spec.type === 'ImportDefaultSpecifier') {
772
+ storage.defaults.add(spec.local.name);
773
+ } else if (spec.type === 'ImportSpecifier') {
774
+ if (spec.imported.name !== spec.local.name) {
775
+ storage.named.add(`${spec.imported.name} as ${spec.local.name}`);
776
+ } else {
777
+ storage.named.add(spec.imported.name);
778
+ }
779
+ } else if (spec.type === 'ImportNamespaceSpecifier') {
780
+ storage.namespace = spec.local.name;
781
+ }
782
+ });
677
783
  }
784
+ } catch (e) {
785
+ console.warn('⚠️ Failed to parse collected import:', code);
786
+ }
787
+ }
678
788
 
679
- // Keep everything else (external libraries like axios, etc.)
680
- return impStr.length > 0;
681
- });
789
+ // Generate cleaned export list
790
+ const filteredImports = [];
791
+
792
+ mergedImports.forEach((storage, source) => {
793
+ // Namespace imports usually stand alone
794
+ if (storage.namespace) {
795
+ filteredImports.push(`import * as ${storage.namespace} from '${source}';`);
796
+ }
682
797
 
683
- // Generate shared modules section (deduplicated by file)
798
+ const parts = [];
799
+ if (storage.defaults.size > 0) {
800
+ const arr = Array.from(storage.defaults);
801
+ parts.push(arr[0]);
802
+ }
803
+
804
+ if (storage.named.size > 0) {
805
+ parts.push(`{ ${Array.from(storage.named).sort().join(', ')} }`);
806
+ }
807
+
808
+ if (storage.defaults.size === 0 && storage.named.size === 0 && !storage.namespace) {
809
+ filteredImports.push(`import '${source}';`);
810
+ } else if (parts.length > 0) {
811
+ filteredImports.push(`import ${parts.join(', ')} from '${source}';`);
812
+ }
813
+ });
814
+
815
+ // Generate shared modules section (deduplicated by file)
684
816
  const sharedModulesCode = Array.from(sharedModules.values())
685
817
  .filter(code => code.trim())
686
818
  .join('\n\n');
@@ -807,9 +939,9 @@ render();
807
939
 
808
940
  /**
809
941
  * Generate a unified index.html for router bundle
810
- * ✅ MODIFIED: Use vendored paths instead of CDN
942
+ * ✅ MODIFIED: Use vendored paths instead of CDN + Hot Reload Script injection
811
943
  */
812
- export function generateIndexHtml(distDir, bundleResult) {
944
+ export function generateIndexHtml(distDir, bundleResult, options = {}) {
813
945
  // ✅ ADD: Defensive check and fallback
814
946
  if (!bundleResult) {
815
947
  console.warn('⚠️ generateIndexHtml called without bundleResult, using defaults');
@@ -821,12 +953,36 @@ export function generateIndexHtml(distDir, bundleResult) {
821
953
  }
822
954
 
823
955
  const { mainJsFilename = 'main.js', routes = [], vendoredPaths = {} } = bundleResult;
956
+ const { isDev = false, wsPort = 3001 } = options;
824
957
 
825
958
  console.log('📄 Generating index.html...');
826
959
 
827
960
  // ✅ Use vendor import map
828
961
  const importMapScript = generateVendorImportMap(vendoredPaths);
829
962
 
963
+ // ✅ Client-side Hot Reload Script
964
+ const hotReloadScript = isDev ? `
965
+ <script>
966
+ (function() {
967
+ console.log('🔌 JUX Hot Reload: Connecting to port ${wsPort}...');
968
+ let ws = new WebSocket('ws://' + window.location.hostname + ':${wsPort}');
969
+
970
+ ws.onopen = () => console.log('✅ JUX Hot Reload: Connected');
971
+
972
+ ws.onmessage = (msg) => {
973
+ try {
974
+ const data = JSON.parse(msg.data);
975
+ if (data.type === 'reload') {
976
+ console.log('🔄 JUX Hot Reload: Refeshing...');
977
+ window.location.reload();
978
+ }
979
+ } catch(e) { console.error('HMR Error:', e); }
980
+ };
981
+
982
+ ws.onclose = () => console.log('❌ JUX Hot Reload: Disconnected');
983
+ })();
984
+ </script>` : '';
985
+
830
986
  const html = `<!DOCTYPE html>
831
987
  <html lang="en">
832
988
  <head>
@@ -838,6 +994,7 @@ export function generateIndexHtml(distDir, bundleResult) {
838
994
  <div id="app"></div>
839
995
  ${importMapScript}
840
996
  <script type="module" src="/${mainJsFilename}"></script>
997
+ ${hotReloadScript}
841
998
  </body>
842
999
  </html>`;
843
1000
 
@@ -932,15 +1089,21 @@ async function vendorExternalDependencies(externalDeps, distDir) {
932
1089
  * ✅ NEW: Generate import map with local vendor paths
933
1090
  */
934
1091
  function generateVendorImportMap(vendoredPaths) {
1092
+ // ✅ FIX: Use ABSOLUTE paths (start with /) to support nested routes (e.g. /app/dashboard)
1093
+ // When using ./, paths are resolved relative to the current URL, which breaks on sub-pages.
935
1094
  const imports = {
936
- "juxscript": "./lib/jux.js",
937
- "juxscript/": "./lib/",
938
- "juxscript/reactivity": "./lib/reactivity/state.js",
939
- "juxscript/presets/": "./presets/",
940
- "juxscript/components/": "./lib/components/",
1095
+ "juxscript/componentsv2": "/lib/componentsv2/index.js", //
1096
+ "juxscript/componentsv2/": "/lib/componentsv2/", //
941
1097
  ...vendoredPaths // ✅ These are LOCAL paths: "./vendor/axios.js"
942
1098
  };
943
1099
 
1100
+ // Ensure all paths start with / to survive pushState routing
1101
+ Object.keys(imports).forEach(key => {
1102
+ if (imports[key].startsWith('./')) {
1103
+ imports[key] = imports[key].substring(1);
1104
+ }
1105
+ });
1106
+
944
1107
  return `<script type="importmap">
945
1108
  ${JSON.stringify({ imports }, null, 2)}
946
1109
  </script>`;
@@ -23,12 +23,12 @@ export const defaultConfig = {
23
23
  // Load user configuration
24
24
  export async function loadConfig(projectRoot) {
25
25
  const configPath = path.join(projectRoot, 'juxconfig.js');
26
-
26
+
27
27
  if (fs.existsSync(configPath)) {
28
28
  try {
29
29
  const configModule = await import(`file://${configPath}`);
30
30
  const userConfig = configModule.default || configModule;
31
-
31
+
32
32
  return {
33
33
  ...defaultConfig,
34
34
  ...userConfig,
@@ -41,7 +41,7 @@ export async function loadConfig(projectRoot) {
41
41
  return defaultConfig;
42
42
  }
43
43
  }
44
-
44
+
45
45
  console.log('ℹ️ No juxconfig.js found, using defaults');
46
46
  return defaultConfig;
47
47
  }
@@ -51,9 +51,9 @@ export async function runBootstrap(bootstrapFunctions = []) {
51
51
  if (!Array.isArray(bootstrapFunctions) || bootstrapFunctions.length === 0) {
52
52
  return;
53
53
  }
54
-
54
+
55
55
  console.log('🚀 Running bootstrap functions...\n');
56
-
56
+
57
57
  for (const fn of bootstrapFunctions) {
58
58
  if (typeof fn === 'function') {
59
59
  try {
@@ -63,6 +63,93 @@ export async function runBootstrap(bootstrapFunctions = []) {
63
63
  }
64
64
  }
65
65
  }
66
-
66
+
67
67
  console.log('✅ Bootstrap complete\n');
68
+ }
69
+
70
+ /**
71
+ * Type helper for JUX Configuration (Identity function for Autocomplete)
72
+ * Used in juxconfig.js to provide Intellisense.
73
+ * @param {import('../types/config').JuxConfig} config
74
+ * @returns {import('../types/config').JuxConfig}
75
+ */
76
+ export function defineConfig(config) {
77
+ return config;
78
+ }
79
+
80
+ /**
81
+ * Normalizes user configuration into the strict format used by the compiler/server.
82
+ * Handles "DX Sugar" like:
83
+ * - Inferring file extensions (.jux, .css)
84
+ * - Mapping 'dist' -> 'distribution'
85
+ * - Resolving shortcuts (e.g. layouts)
86
+ * - Defaulting ports
87
+ */
88
+ export function resolveConfig(userConfig, projectRoot = process.cwd()) {
89
+ const root = userConfig.root || projectRoot;
90
+
91
+ // 1. Directories (Map 'dist' alias to 'distribution')
92
+ const dirs = userConfig.directories || {};
93
+ const directories = {
94
+ source: dirs.source || 'jux',
95
+ distribution: dirs.dist || dirs.distribution || '.jux-dist',
96
+ themes: dirs.themes || 'themes',
97
+ layouts: dirs.layouts || 'themes/layouts',
98
+ assets: dirs.assets || 'themes/assets'
99
+ };
100
+
101
+ // 2. Defaults (Infer extensions)
102
+ const defs = userConfig.defaults || {};
103
+ const httpPort = defs.port || defs.httpPort || 3000;
104
+
105
+ const defaults = {
106
+ httpPort,
107
+ wsPort: defs.wsPort || (httpPort + 1),
108
+ autoRoute: defs.autoRoute !== false, // default true
109
+ layout: ensureExt(defs.layout, '.jux') || 'base.jux',
110
+ theme: ensureExt(defs.theme, '.css') || 'base.css'
111
+ };
112
+
113
+ // 3. Pages Normalization (Recursively fix route paths)
114
+ const pages = normalizePages(userConfig.pages || {});
115
+
116
+ // 4. Return Normalized Config
117
+ return {
118
+ root,
119
+ directories,
120
+ defaults,
121
+ pages,
122
+ hooks: userConfig.hooks || {}
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Helper: Appends extension if missing
128
+ */
129
+ function ensureExt(file, ext) {
130
+ if (!file) return file;
131
+ return file.endsWith(ext) ? file : `${file}${ext}`;
132
+ }
133
+
134
+ /**
135
+ * Helper: Recursively normalizes page routes
136
+ */
137
+ function normalizePages(pages) {
138
+ const normalized = {};
139
+ for (const [key, value] of Object.entries(pages)) {
140
+ if (typeof value === 'string') {
141
+ // Direct route: '/' -> 'experiments/index'
142
+ // Becomes: '/' -> 'experiments/index.jux'
143
+ normalized[key] = ensureExt(value, '.jux');
144
+ } else if (typeof value === 'object') {
145
+ // Route Group
146
+ normalized[key] = {
147
+ ...value,
148
+ layout: value.layout ? ensureExt(value.layout, '.jux') : undefined,
149
+ theme: value.theme ? ensureExt(value.theme, '.css') : undefined,
150
+ routes: normalizePages(value.routes || {})
151
+ };
152
+ }
153
+ }
154
+ return normalized;
68
155
  }
@@ -0,0 +1,72 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { bundleJuxFilesToRouter, generateIndexHtml } from './compiler.js';
5
+ import { verifyStaticBuild } from './verifier.js';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const FIXTURES_DIR = path.join(__dirname, 'fixtures');
9
+ const DIST_DIR = path.join(__dirname, '.temp_dist_test');
10
+
11
+ // Setup temp dir
12
+ if (!fs.existsSync(DIST_DIR)) fs.mkdirSync(DIST_DIR);
13
+
14
+ async function runTest(fileName, description) {
15
+ console.log(`\n🧪 TEST: ${description} (${fileName})`);
16
+ console.log(' Context: Trying to build a broken file...');
17
+
18
+ const srcFile = path.join(FIXTURES_DIR, fileName);
19
+
20
+ // Mock finding files by explicitly passing our bad file as the "source"
21
+ // Since bundleJuxFilesToRouter takes a directory, we need to create a temp source dir
22
+ const tempSrcDir = path.join(__dirname, '.temp_src_' + Date.now());
23
+ fs.mkdirSync(tempSrcDir);
24
+ fs.copyFileSync(srcFile, path.join(tempSrcDir, 'index.jux'));
25
+
26
+ let buildSuccess = false;
27
+
28
+ try {
29
+ // 1. Attempt Bundle
30
+ const result = await bundleJuxFilesToRouter(tempSrcDir, DIST_DIR, { routePrefix: '' });
31
+
32
+ // 2. Generate HTML (needed for verifier to check import map)
33
+ generateIndexHtml(DIST_DIR, result);
34
+
35
+ // 3. Run Verifier
36
+ const verified = verifyStaticBuild(DIST_DIR);
37
+
38
+ if (!verified) {
39
+ console.log(' ✅ SUCCESS: Verifier correctly caught the error!');
40
+ buildSuccess = false;
41
+ } else {
42
+ console.log(' ❌ FAILURE: Build passed but should have failed verification.');
43
+ buildSuccess = true;
44
+ }
45
+
46
+ } catch (e) {
47
+ console.log(` ✅ SUCCESS: Compiler threw error as expected:`);
48
+ console.log(` "${e.message.split('\n')[0]}"`);
49
+ buildSuccess = false;
50
+ } finally {
51
+ // Cleanup
52
+ fs.rmSync(tempSrcDir, { recursive: true, force: true });
53
+ }
54
+
55
+ return !buildSuccess;
56
+ }
57
+
58
+ async function main() {
59
+ console.log('🩺 JUX FAILURE DIAGNOSTICS\n');
60
+
61
+ // Test 1: bad_syntax.jux (Should throw during compile)
62
+ await runTest('bad_syntax.jux', 'Syntax Error Detection');
63
+
64
+ // Test 2: ghost_dep.jux (Should pass compile, fail verification)
65
+ await runTest('ghost_dep.jux', 'Ghost Dependency Detection');
66
+
67
+ // Cleanup Dist
68
+ fs.rmSync(DIST_DIR, { recursive: true, force: true });
69
+ console.log('\n🏁 Diagnostics Complete');
70
+ }
71
+
72
+ main();
@@ -0,0 +1,118 @@
1
+ # JUX Module Pattern
2
+
3
+ ## Overview
4
+ A `.jux` file can export a component-like interface with:
5
+ - Configuration options
6
+ - Public API methods
7
+ - Event emissions (EDR pattern)
8
+ - Internal/shared state
9
+ - Props (getters)
10
+
11
+ ## Pattern Structure
12
+
13
+ ```javascript
14
+ // calendar.jux
15
+ import { jux, state } from 'juxscript';
16
+
17
+ // ═══════════════════════════════════════════════════════════════════
18
+ // MODULE DEFINITION
19
+ // ═══════════════════════════════════════════════════════════════════
20
+
21
+ export default function Calendar(id, options = {}) {
22
+ // Internal state
23
+ const today = new Date();
24
+ const currentMonth = state(options.initialMonth ?? today.getMonth());
25
+ const currentYear = state(options.initialYear ?? today.getFullYear());
26
+ const selectedDate = state(null);
27
+
28
+ // Event emitters (EDR pattern)
29
+ const events = {
30
+ dateSelect: [],
31
+ monthChange: [],
32
+ yearChange: []
33
+ };
34
+
35
+ // Emit helper
36
+ const emit = (eventName, data) => {
37
+ events[eventName]?.forEach(handler => handler(data));
38
+ };
39
+
40
+ // Public API
41
+ const api = {
42
+ // Props (getters)
43
+ get currentMonth() { return currentMonth.value; },
44
+ get currentYear() { return currentYear.value; },
45
+ get selectedDate() { return selectedDate.value; },
46
+
47
+ // Methods
48
+ nextMonth() {
49
+ let month = currentMonth.value + 1;
50
+ let year = currentYear.value;
51
+ if (month > 11) { month = 0; year++; }
52
+ currentMonth.set(month);
53
+ currentYear.set(year);
54
+ emit('monthChange', { month, year });
55
+ return api;
56
+ },
57
+
58
+ prevMonth() {
59
+ let month = currentMonth.value - 1;
60
+ let year = currentYear.value;
61
+ if (month < 0) { month = 11; year--; }
62
+ currentMonth.set(month);
63
+ currentYear.set(year);
64
+ emit('monthChange', { month, year });
65
+ return api;
66
+ },
67
+
68
+ selectDate(date) {
69
+ selectedDate.set(date);
70
+ emit('dateSelect', { date });
71
+ return api;
72
+ },
73
+
74
+ // Event binding (EDR consumer registration)
75
+ on(eventName, handler) {
76
+ if (events[eventName]) {
77
+ events[eventName].push(handler);
78
+ }
79
+ return api;
80
+ },
81
+
82
+ // Render
83
+ render(targetId) {
84
+ // Build UI components...
85
+ return api;
86
+ },
87
+
88
+ // Expose for chaining
89
+ id
90
+ };
91
+
92
+ return api;
93
+ }
94
+ ```
95
+
96
+ ## Usage
97
+
98
+ ```javascript
99
+ // app.jux
100
+ import Calendar from './calendar.jux';
101
+
102
+ const cal = Calendar('my-calendar', {
103
+ initialMonth: 11,
104
+ initialYear: 2024
105
+ })
106
+ .on('dateSelect', ({ date }) => {
107
+ console.log('Date selected:', date);
108
+ })
109
+ .on('monthChange', ({ month, year }) => {
110
+ console.log('Month changed:', month, year);
111
+ })
112
+ .render('#app');
113
+
114
+ // Public API
115
+ cal.nextMonth();
116
+ cal.prevMonth();
117
+ console.log(cal.currentMonth); // getter
118
+ ```