slicejs-cli 2.7.7 → 2.7.9

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.
@@ -19,6 +19,10 @@ export default async function bundle(options = {}) {
19
19
  // Validate that it's a Slice.js project
20
20
  await validateProject(projectRoot);
21
21
 
22
+ // Create default bundle config if needed
23
+ const bundleGenerator = new BundleGenerator(import.meta.url, null);
24
+ await bundleGenerator.createDefaultBundleConfig();
25
+
22
26
  // Phase 1: Analysis
23
27
  Print.buildProgress('Analyzing project...');
24
28
  const analyzer = new DependencyAnalyzer(import.meta.url);
@@ -110,7 +114,8 @@ function printSummary(result, startTime) {
110
114
 
111
115
  for (const [key, bundle] of Object.entries(bundles.routes)) {
112
116
  console.log(` ${key}:`);
113
- console.log(` Route: ${bundle.path}`);
117
+ const routeDisplay = Array.isArray(bundle.path) ? `${bundle.path.length} routes` : (bundle.path || key);
118
+ console.log(` Route: ${routeDisplay}`);
114
119
  console.log(` Components: ${bundle.components.length}`);
115
120
  console.log(` Size: ${(bundle.size / 1024).toFixed(1)} KB`);
116
121
  console.log(` File: ${bundle.file}`);
@@ -9,6 +9,7 @@ export default class BundleGenerator {
9
9
  this.moduleUrl = moduleUrl;
10
10
  this.analysisData = analysisData;
11
11
  this.srcPath = getSrcPath(moduleUrl);
12
+ this.bundlesPath = path.join(this.srcPath, 'bundles');
12
13
  this.componentsPath = path.dirname(getComponentsJsPath(moduleUrl));
13
14
 
14
15
  // Configuration
@@ -35,6 +36,9 @@ export default class BundleGenerator {
35
36
  async generate() {
36
37
  console.log('🔨 Generating bundles...');
37
38
 
39
+ // 0. Create bundles directory
40
+ await fs.ensureDir(this.bundlesPath);
41
+
38
42
  // 1. Determine optimal strategy
39
43
  this.determineStrategy();
40
44
 
@@ -70,9 +74,9 @@ export default class BundleGenerator {
70
74
  if (totalComponents < 20 || sharedPercentage > 60) {
71
75
  this.config.strategy = 'global';
72
76
  console.log('📦 Strategy: Global Bundle (small project or highly shared)');
73
- } else if (totalComponents < 60) {
77
+ } else if (totalComponents < 100) {
74
78
  this.config.strategy = 'hybrid';
75
- console.log('📦 Strategy: Hybrid (critical + per route)');
79
+ console.log('📦 Strategy: Hybrid (critical + grouped routes)');
76
80
  } else {
77
81
  this.config.strategy = 'per-route';
78
82
  console.log('📦 Strategy: Per Route (large project)');
@@ -95,8 +99,8 @@ export default class BundleGenerator {
95
99
  const isStructural = comp.categoryType === 'Structural' ||
96
100
  ['Navbar', 'Footer', 'Layout'].includes(comp.name);
97
101
 
98
- // Small and highly used components
99
- const isSmallAndUseful = comp.size < 5000 && comp.routes.size >= 2;
102
+ // Small and highly used components (only if used in 3+ routes)
103
+ const isSmallAndUseful = comp.size < 2000 && comp.routes.size >= 3;
100
104
 
101
105
  return isShared || isStructural || isSmallAndUseful;
102
106
  })
@@ -109,13 +113,27 @@ export default class BundleGenerator {
109
113
 
110
114
  // Fill critical bundle up to limit
111
115
  for (const comp of candidates) {
112
- const wouldExceedSize = this.bundles.critical.size + comp.size > this.config.maxCriticalSize;
113
- const wouldExceedCount = this.bundles.critical.components.length >= this.config.maxCriticalComponents;
116
+ const dependencies = this.getComponentDependencies(comp);
117
+ const totalSize = comp.size + dependencies.reduce((sum, dep) => sum + dep.size, 0);
118
+ const totalCount = 1 + dependencies.length;
119
+
120
+ const wouldExceedSize = this.bundles.critical.size + totalSize > this.config.maxCriticalSize;
121
+ const wouldExceedCount = this.bundles.critical.components.length + totalCount > this.config.maxCriticalComponents;
122
+
123
+ if (wouldExceedSize || wouldExceedCount) continue;
114
124
 
115
- if (wouldExceedSize || wouldExceedCount) break;
125
+ // Add component and its dependencies
126
+ if (!this.bundles.critical.components.find(c => c.name === comp.name)) {
127
+ this.bundles.critical.components.push(comp);
128
+ this.bundles.critical.size += comp.size;
129
+ }
116
130
 
117
- this.bundles.critical.components.push(comp);
118
- this.bundles.critical.size += comp.size;
131
+ for (const dep of dependencies) {
132
+ if (!this.bundles.critical.components.find(c => c.name === dep.name)) {
133
+ this.bundles.critical.components.push(dep);
134
+ this.bundles.critical.size += dep.size;
135
+ }
136
+ }
119
137
  }
120
138
 
121
139
  console.log(`✓ Critical bundle: ${this.bundles.critical.components.length} components, ${(this.bundles.critical.size / 1024).toFixed(1)} KB`);
@@ -127,12 +145,34 @@ export default class BundleGenerator {
127
145
  assignRouteComponents() {
128
146
  const criticalNames = new Set(this.bundles.critical.components.map(c => c.name));
129
147
 
130
- for (const [routePath, route] of this.analysisData.routes) {
148
+ if (this.config.strategy === 'hybrid') {
149
+ this.assignHybridBundles(criticalNames);
150
+ } else {
151
+ this.assignPerRouteBundles(criticalNames);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Assigns components to per-route bundles
157
+ */
158
+ assignPerRouteBundles(criticalNames) {
159
+ for (const route of this.analysisData.routes) {
160
+ const routePath = route.path;
131
161
  // Get all route dependencies
132
162
  const routeComponents = this.getRouteComponents(route.component);
133
163
 
164
+ // Include dependencies for all route components
165
+ const allComponents = new Set();
166
+ for (const comp of routeComponents) {
167
+ allComponents.add(comp);
168
+ const dependencies = this.getComponentDependencies(comp);
169
+ for (const dep of dependencies) {
170
+ allComponents.add(dep);
171
+ }
172
+ }
173
+
134
174
  // Filter those already in critical
135
- const uniqueComponents = routeComponents.filter(comp =>
175
+ const uniqueComponents = Array.from(allComponents).filter(comp =>
136
176
  !criticalNames.has(comp.name)
137
177
  );
138
178
 
@@ -152,6 +192,168 @@ export default class BundleGenerator {
152
192
  }
153
193
  }
154
194
 
195
+ /**
196
+ * Gets all component dependencies transitively
197
+ */
198
+ getComponentDependencies(component, visited = new Set()) {
199
+ if (visited.has(component.name)) return [];
200
+ visited.add(component.name);
201
+
202
+ const dependencies = [];
203
+
204
+ // Add direct dependencies
205
+ for (const depName of component.dependencies) {
206
+ const depComp = this.analysisData.components.find(c => c.name === depName);
207
+ if (depComp && !visited.has(depName)) {
208
+ dependencies.push(depComp);
209
+ // Add transitive dependencies
210
+ dependencies.push(...this.getComponentDependencies(depComp, visited));
211
+ }
212
+ }
213
+
214
+ return dependencies;
215
+ }
216
+
217
+ /**
218
+ * Assigns components to hybrid bundles (grouped by category)
219
+ */
220
+ assignHybridBundles(criticalNames) {
221
+ const routeGroups = new Map();
222
+
223
+ // First, handle MultiRoute groups
224
+ if (this.analysisData.routeGroups) {
225
+ for (const [groupKey, groupData] of this.analysisData.routeGroups) {
226
+ if (groupData.type === 'multiroute') {
227
+ // Create a bundle for this MultiRoute group
228
+ const allComponents = new Set();
229
+
230
+ // Add the main component (MultiRoute handler)
231
+ const mainComponent = this.analysisData.components.find(c => c.name === groupData.component);
232
+ if (mainComponent) {
233
+ allComponents.add(mainComponent);
234
+
235
+ // Add all components used by this MultiRoute
236
+ const routeComponents = this.getRouteComponents(mainComponent.name);
237
+ for (const comp of routeComponents) {
238
+ allComponents.add(comp);
239
+ // Add transitive dependencies
240
+ const dependencies = this.getComponentDependencies(comp);
241
+ for (const dep of dependencies) {
242
+ allComponents.add(dep);
243
+ }
244
+ }
245
+ }
246
+
247
+ // Filter those already in critical
248
+ const uniqueComponents = Array.from(allComponents).filter(comp =>
249
+ !criticalNames.has(comp.name)
250
+ );
251
+
252
+ if (uniqueComponents.length > 0) {
253
+ const totalSize = uniqueComponents.reduce((sum, c) => sum + c.size, 0);
254
+
255
+ this.bundles.routes[groupKey] = {
256
+ paths: groupData.routes,
257
+ components: uniqueComponents,
258
+ size: totalSize,
259
+ file: `slice-bundle.${groupKey}.js`
260
+ };
261
+
262
+ console.log(`✓ Bundle ${groupKey}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB (${groupData.routes.length} routes)`);
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ // Group remaining routes by category (skip those already handled by MultiRoute)
269
+ for (const route of this.analysisData.routes) {
270
+ // Check if this route is already handled by a MultiRoute group
271
+ const isHandledByMultiRoute = this.analysisData.routeGroups &&
272
+ Array.from(this.analysisData.routeGroups.values()).some(group =>
273
+ group.type === 'multiroute' && group.routes.includes(route.path)
274
+ );
275
+
276
+ if (!isHandledByMultiRoute) {
277
+ const category = this.categorizeRoute(route.path);
278
+ if (!routeGroups.has(category)) {
279
+ routeGroups.set(category, []);
280
+ }
281
+ routeGroups.get(category).push(route);
282
+ }
283
+ }
284
+
285
+ // Create bundles for each group
286
+ for (const [category, routes] of routeGroups) {
287
+ const allComponents = new Set();
288
+
289
+ // Collect all unique components for this category (including dependencies)
290
+ for (const route of routes) {
291
+ const routeComponents = this.getRouteComponents(route.component);
292
+ for (const comp of routeComponents) {
293
+ allComponents.add(comp);
294
+ // Add transitive dependencies
295
+ const dependencies = this.getComponentDependencies(comp);
296
+ for (const dep of dependencies) {
297
+ allComponents.add(dep);
298
+ }
299
+ }
300
+ }
301
+
302
+ // Filter those already in critical
303
+ const uniqueComponents = Array.from(allComponents).filter(comp =>
304
+ !criticalNames.has(comp.name)
305
+ );
306
+
307
+ if (uniqueComponents.length === 0) continue;
308
+
309
+ const totalSize = uniqueComponents.reduce((sum, c) => sum + c.size, 0);
310
+ const routePaths = routes.map(r => r.path);
311
+
312
+ this.bundles.routes[category] = {
313
+ paths: routePaths,
314
+ components: uniqueComponents,
315
+ size: totalSize,
316
+ file: `slice-bundle.${category}.js`
317
+ };
318
+
319
+ console.log(`✓ Bundle ${category}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB (${routes.length} routes)`);
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Categorizes a route path for grouping, considering MultiRoute context
325
+ */
326
+ categorizeRoute(routePath) {
327
+ // Check if this route belongs to a MultiRoute handler
328
+ if (this.analysisData.routeGroups) {
329
+ for (const [groupKey, groupData] of this.analysisData.routeGroups) {
330
+ if (groupData.type === 'multiroute' && groupData.routes.includes(routePath)) {
331
+ return groupKey; // Return the MultiRoute group key
332
+ }
333
+ }
334
+ }
335
+
336
+ // Default categorization
337
+ const path = routePath.toLowerCase();
338
+
339
+ if (path === '/' || path === '/home') return 'home';
340
+ if (path.includes('docum') || path.includes('documentation')) return 'documentation';
341
+ if (path.includes('component') || path.includes('visual') || path.includes('card') ||
342
+ path.includes('button') || path.includes('input') || path.includes('switch') ||
343
+ path.includes('checkbox') || path.includes('select') || path.includes('details') ||
344
+ path.includes('grid') || path.includes('loading') || path.includes('layout') ||
345
+ path.includes('navbar') || path.includes('treeview') || path.includes('multiroute')) return 'components';
346
+ if (path.includes('theme') || path.includes('slice') || path.includes('config')) return 'configuration';
347
+ if (path.includes('routing') || path.includes('guard')) return 'routing';
348
+ if (path.includes('service') || path.includes('command')) return 'services';
349
+ if (path.includes('structural') || path.includes('lifecycle') || path.includes('static') ||
350
+ path.includes('build')) return 'advanced';
351
+ if (path.includes('playground') || path.includes('creator')) return 'tools';
352
+ if (path.includes('about') || path.includes('404')) return 'misc';
353
+
354
+ return 'general';
355
+ }
356
+
155
357
  /**
156
358
  * Gets all components needed for a route
157
359
  */
@@ -199,7 +401,7 @@ export default class BundleGenerator {
199
401
  const routeFile = await this.createBundleFile(
200
402
  bundle.components,
201
403
  'route',
202
- bundle.path
404
+ bundle.path || routeKey // Use routeKey as fallback for hybrid bundles
203
405
  );
204
406
  files.push(routeFile);
205
407
  }
@@ -213,7 +415,7 @@ export default class BundleGenerator {
213
415
  async createBundleFile(components, type, routePath) {
214
416
  const routeKey = routePath ? this.routeToFileName(routePath) : 'critical';
215
417
  const fileName = `slice-bundle.${routeKey}.js`;
216
- const filePath = path.join(this.srcPath, fileName);
418
+ const filePath = path.join(this.bundlesPath, fileName);
217
419
 
218
420
  const bundleContent = await this.generateBundleContent(
219
421
  components,
@@ -235,6 +437,50 @@ export default class BundleGenerator {
235
437
  };
236
438
  }
237
439
 
440
+ /**
441
+ * Analyzes dependencies of a JavaScript file using simple regex
442
+ */
443
+ analyzeDependencies(jsContent, componentPath) {
444
+ const dependencies = [];
445
+
446
+ try {
447
+ // Simple regex to find import statements
448
+ const importRegex = /import\s+.*?\s+from\s+['"`]([^'"`]+)['"`]/g;
449
+ let match;
450
+
451
+ while ((match = importRegex.exec(jsContent)) !== null) {
452
+ const importPath = match[1];
453
+
454
+ // Only process relative imports (starting with ./ or ../)
455
+ if (importPath.startsWith('./') || importPath.startsWith('../')) {
456
+ // Resolve the absolute path
457
+ const resolvedPath = path.resolve(componentPath, importPath);
458
+
459
+ // If no extension, try common extensions
460
+ let finalPath = resolvedPath;
461
+ const ext = path.extname(resolvedPath);
462
+ if (!ext) {
463
+ const extensions = ['.js', '.json', '.mjs'];
464
+ for (const ext of extensions) {
465
+ if (fs.existsSync(resolvedPath + ext)) {
466
+ finalPath = resolvedPath + ext;
467
+ break;
468
+ }
469
+ }
470
+ }
471
+
472
+ if (fs.existsSync(finalPath)) {
473
+ dependencies.push(finalPath);
474
+ }
475
+ }
476
+ }
477
+ } catch (error) {
478
+ console.warn(`Warning: Could not analyze dependencies for ${componentPath}:`, error.message);
479
+ }
480
+
481
+ return dependencies;
482
+ }
483
+
238
484
  /**
239
485
  * Generates the content of a bundle
240
486
  */
@@ -242,10 +488,23 @@ export default class BundleGenerator {
242
488
  const componentsData = {};
243
489
 
244
490
  for (const comp of components) {
245
- const jsContent = await fs.readFile(
246
- path.join(comp.path, `${comp.name}.js`),
247
- 'utf-8'
248
- );
491
+ const jsPath = path.join(comp.path, `${comp.name}.js`);
492
+ const jsContent = await fs.readFile(jsPath, 'utf-8');
493
+
494
+ // Analyze dependencies
495
+ const dependencies = this.analyzeDependencies(jsContent, comp.path);
496
+ const dependencyContents = {};
497
+
498
+ // Read all dependency files
499
+ for (const depPath of dependencies) {
500
+ try {
501
+ const depContent = await fs.readFile(depPath, 'utf-8');
502
+ const depName = path.basename(depPath, path.extname(depPath));
503
+ dependencyContents[depName] = depContent;
504
+ } catch (error) {
505
+ console.warn(`Warning: Could not read dependency ${depPath}:`, error.message);
506
+ }
507
+ }
249
508
 
250
509
  let htmlContent = null;
251
510
  let cssContent = null;
@@ -265,11 +524,12 @@ export default class BundleGenerator {
265
524
  name: comp.name,
266
525
  category: comp.category,
267
526
  categoryType: comp.categoryType,
268
- js: this.cleanJavaScript(jsContent),
527
+ js: this.cleanJavaScript(jsContent, comp.name),
528
+ externalDependencies: dependencyContents, // Files imported with import statements
529
+ componentDependencies: Array.from(comp.dependencies), // Other components this one depends on
269
530
  html: htmlContent,
270
531
  css: cssContent,
271
- size: comp.size,
272
- dependencies: Array.from(comp.dependencies)
532
+ size: comp.size
273
533
  };
274
534
  }
275
535
 
@@ -287,15 +547,28 @@ export default class BundleGenerator {
287
547
  }
288
548
 
289
549
  /**
290
- * Cleans JavaScript code by removing imports/exports
550
+ * Cleans JavaScript code by removing imports/exports and ensuring class is available globally
291
551
  */
292
- cleanJavaScript(code) {
552
+ cleanJavaScript(code, componentName) {
293
553
  // Remove export default
294
554
  code = code.replace(/export\s+default\s+/g, '');
295
555
 
296
556
  // Remove imports (components will already be available)
297
557
  code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
298
558
 
559
+ // Make sure the class is available globally for bundle evaluation
560
+ // Preserve original customElements.define if it exists
561
+ if (code.includes('customElements.define')) {
562
+ // Add global assignment before customElements.define
563
+ code = code.replace(/customElements\.define\([^;]+\);?\s*$/, `window.${componentName} = ${componentName};\n$&`);
564
+ } else {
565
+ // If no customElements.define found, just assign to global
566
+ code += `\nwindow.${componentName} = ${componentName};`;
567
+ }
568
+
569
+ // Add return statement for bundle evaluation compatibility
570
+ code += `\nreturn ${componentName};`;
571
+
299
572
  return code;
300
573
  }
301
574
 
@@ -355,7 +628,7 @@ if (window.slice && window.slice.controller) {
355
628
 
356
629
  for (const [key, bundle] of Object.entries(this.bundles.routes)) {
357
630
  config.bundles.routes[key] = {
358
- path: bundle.path,
631
+ path: bundle.path || bundle.paths || key, // Support both single path and array of paths, fallback to key
359
632
  file: bundle.file,
360
633
  size: bundle.size,
361
634
  components: bundle.components.map(c => c.name),
@@ -382,8 +655,76 @@ if (window.slice && window.slice.controller) {
382
655
  * Saves the configuration to file
383
656
  */
384
657
  async saveBundleConfig(config) {
385
- const configPath = path.join(this.srcPath, 'bundle.config.json');
658
+ // Ensure bundles directory exists
659
+ await fs.ensureDir(this.bundlesPath);
660
+
661
+ // Save JSON config
662
+ const configPath = path.join(this.bundlesPath, 'bundle.config.json');
386
663
  await fs.writeJson(configPath, config, { spaces: 2 });
664
+
665
+ // Generate JavaScript module for direct import
666
+ const jsConfigPath = path.join(this.bundlesPath, 'bundle.config.js');
667
+ const jsConfig = this.generateBundleConfigJS(config);
668
+ await fs.writeFile(jsConfigPath, jsConfig, 'utf-8');
669
+
387
670
  console.log(`✓ Configuration saved to ${configPath}`);
671
+ console.log(`✓ JavaScript config generated: ${jsConfigPath}`);
672
+ }
673
+
674
+ /**
675
+ * Creates a default bundle config file if none exists
676
+ */
677
+ async createDefaultBundleConfig() {
678
+ const defaultConfigPath = path.join(this.srcPath, 'bundles', 'bundle.config.js');
679
+
680
+ // Only create if it doesn't exist
681
+ if (await fs.pathExists(defaultConfigPath)) {
682
+ return;
683
+ }
684
+
685
+ await fs.ensureDir(path.dirname(defaultConfigPath));
686
+
687
+ const defaultConfig = `/**
688
+ * Slice.js Bundle Configuration
689
+ * Default empty configuration - no bundles available
690
+ * Run 'slice bundle' to generate optimized bundles
691
+ */
692
+
693
+ // No bundles available - using individual component loading
694
+ export const SLICE_BUNDLE_CONFIG = null;
695
+
696
+ // No auto-initialization needed for default config
697
+ `;
698
+
699
+ await fs.writeFile(defaultConfigPath, defaultConfig, 'utf-8');
700
+ console.log(`✓ Default bundle config created: ${defaultConfigPath}`);
701
+ }
702
+
703
+ /**
704
+ * Generates JavaScript module for direct import
705
+ */
706
+ generateBundleConfigJS(config) {
707
+ return `/**
708
+ * Slice.js Bundle Configuration
709
+ * Generated: ${new Date().toISOString()}
710
+ * Strategy: ${config.strategy}
711
+ */
712
+
713
+ // Direct bundle configuration (no fetch required)
714
+ export const SLICE_BUNDLE_CONFIG = ${JSON.stringify(config, null, 2)};
715
+
716
+ // Auto-initialization if slice is available
717
+ if (typeof window !== 'undefined' && window.slice && window.slice.controller) {
718
+ window.slice.controller.bundleConfig = SLICE_BUNDLE_CONFIG;
719
+
720
+ // Load critical bundle automatically
721
+ if (SLICE_BUNDLE_CONFIG.bundles.critical && !window.slice.controller.criticalBundleLoaded) {
722
+ import('./slice-bundle.critical.js').catch(err =>
723
+ console.warn('Failed to load critical bundle:', err)
724
+ );
725
+ window.slice.controller.criticalBundleLoaded = true;
726
+ }
727
+ }
728
+ `;
388
729
  }
389
730
  }
@@ -45,6 +45,7 @@ export default class DependencyAnalyzer {
45
45
  components: Array.from(this.components.values()),
46
46
  routes: Array.from(this.routes.values()),
47
47
  dependencyGraph: this.dependencyGraph,
48
+ routeGroups: this.routeGroups,
48
49
  metrics
49
50
  };
50
51
  }
@@ -62,8 +63,8 @@ export default class DependencyAnalyzer {
62
63
  // Read and parse components.js
63
64
  const content = await fs.readFile(componentsConfigPath, 'utf-8');
64
65
 
65
- // Extract configuration using simple regex
66
- const configMatch = content.match(/export default\s*({[\s\S]*})/);
66
+ // Extract configuration using simple regex - look for the components object
67
+ const configMatch = content.match(/const components\s*=\s*({[\s\S]*?});/);
67
68
  if (!configMatch) {
68
69
  throw new Error('Could not parse components.js');
69
70
  }
@@ -71,9 +72,26 @@ export default class DependencyAnalyzer {
71
72
  // Evaluate safely (in production use a more robust parser)
72
73
  const config = eval(`(${configMatch[1]})`);
73
74
 
74
- // Save category metadata
75
- for (const [category, info] of Object.entries(config)) {
76
- const categoryPath = path.join(this.componentsPath, info.path.replace('../', ''));
75
+ // Group components by category
76
+ const categoryMap = new Map();
77
+
78
+ // Build category map from component assignments
79
+ for (const [componentName, categoryName] of Object.entries(config)) {
80
+ if (!categoryMap.has(categoryName)) {
81
+ categoryMap.set(categoryName, []);
82
+ }
83
+ categoryMap.get(categoryName).push(componentName);
84
+ }
85
+
86
+ // Process each category
87
+ for (const [categoryName, componentList] of categoryMap) {
88
+ // Determine category type based on category name
89
+ let categoryType = 'Visual'; // default
90
+ if (categoryName === 'Service') categoryType = 'Service';
91
+ if (categoryName === 'AppComponents') categoryType = 'Visual'; // AppComponents are visual
92
+
93
+ // Find category path
94
+ const categoryPath = path.join(this.componentsPath, categoryName);
77
95
 
78
96
  if (await fs.pathExists(categoryPath)) {
79
97
  const files = await fs.readdir(categoryPath);
@@ -82,11 +100,11 @@ export default class DependencyAnalyzer {
82
100
  const componentPath = path.join(categoryPath, file);
83
101
  const stat = await fs.stat(componentPath);
84
102
 
85
- if (stat.isDirectory()) {
103
+ if (stat.isDirectory() && componentList.includes(file)) {
86
104
  this.components.set(file, {
87
105
  name: file,
88
- category,
89
- categoryType: info.type,
106
+ category: categoryName,
107
+ categoryType: categoryType,
90
108
  path: componentPath,
91
109
  dependencies: new Set(),
92
110
  usedBy: new Set(),
@@ -136,8 +154,33 @@ export default class DependencyAnalyzer {
136
154
  CallExpression(path) {
137
155
  const { callee, arguments: args } = path.node;
138
156
 
139
- // slice.build('ComponentName', ...)
157
+ // slice.build('MultiRoute', { routes: [...] })
140
158
  if (
159
+ callee.type === 'MemberExpression' &&
160
+ callee.object.name === 'slice' &&
161
+ callee.property.name === 'build' &&
162
+ args[0]?.type === 'StringLiteral' &&
163
+ args[0].value === 'MultiRoute' &&
164
+ args[1]?.type === 'ObjectExpression'
165
+ ) {
166
+ // Add MultiRoute itself
167
+ dependencies.add('MultiRoute');
168
+
169
+ // Extract routes from MultiRoute props
170
+ const routesProp = args[1].properties.find(p => p.key?.name === 'routes');
171
+ if (routesProp?.value?.type === 'ArrayExpression') {
172
+ routesProp.value.elements.forEach(routeElement => {
173
+ if (routeElement.type === 'ObjectExpression') {
174
+ const componentProp = routeElement.properties.find(p => p.key?.name === 'component');
175
+ if (componentProp?.value?.type === 'StringLiteral') {
176
+ dependencies.add(componentProp.value.value);
177
+ }
178
+ }
179
+ });
180
+ }
181
+ }
182
+ // Regular slice.build() calls
183
+ else if (
141
184
  callee.type === 'MemberExpression' &&
142
185
  callee.object.name === 'slice' &&
143
186
  callee.property.name === 'build' &&
@@ -164,7 +207,7 @@ export default class DependencyAnalyzer {
164
207
  }
165
208
 
166
209
  /**
167
- * Analyzes the routes file
210
+ * Analyzes the routes file and detects route groups
168
211
  */
169
212
  async analyzeRoutes() {
170
213
  if (!await fs.pathExists(this.routesPath)) {
@@ -172,7 +215,7 @@ export default class DependencyAnalyzer {
172
215
  }
173
216
 
174
217
  const content = await fs.readFile(this.routesPath, 'utf-8');
175
-
218
+
176
219
  try {
177
220
  const ast = parse(content, {
178
221
  sourceType: 'module',
@@ -208,6 +251,10 @@ export default class DependencyAnalyzer {
208
251
  }
209
252
  }
210
253
  });
254
+
255
+ // Detect and store route groups based on MultiRoute usage
256
+ this.routeGroups = this.detectRouteGroups();
257
+
211
258
  } catch (error) {
212
259
  console.warn(`⚠️ Error parseando rutas: ${error.message}`);
213
260
  }
@@ -232,6 +279,40 @@ export default class DependencyAnalyzer {
232
279
  }
233
280
  }
234
281
 
282
+ /**
283
+ * Detects route groups based on MultiRoute usage
284
+ */
285
+ detectRouteGroups() {
286
+ const routeGroups = new Map();
287
+
288
+ for (const [componentName, component] of this.components) {
289
+ // Check if component uses MultiRoute
290
+ const hasMultiRoute = Array.from(component.dependencies).includes('MultiRoute');
291
+
292
+ if (hasMultiRoute) {
293
+ // Find all routes that point to this component
294
+ const relatedRoutes = Array.from(this.routes.values())
295
+ .filter(route => route.component === componentName);
296
+
297
+ if (relatedRoutes.length > 1) {
298
+ // Group these routes together
299
+ const groupKey = `multiroute-${componentName}`;
300
+ routeGroups.set(groupKey, {
301
+ component: componentName,
302
+ routes: relatedRoutes.map(r => r.path),
303
+ type: 'multiroute'
304
+ });
305
+
306
+ // Mark component as multiroute handler
307
+ component.isMultiRouteHandler = true;
308
+ component.multiRoutePaths = relatedRoutes.map(r => r.path);
309
+ }
310
+ }
311
+ }
312
+
313
+ return routeGroups;
314
+ }
315
+
235
316
  /**
236
317
  * Gets all dependencies of a component (recursive)
237
318
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-cli",
3
- "version": "2.7.7",
3
+ "version": "2.7.9",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "bin": {