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
|
-
|
|
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 <
|
|
77
|
+
} else if (totalComponents < 100) {
|
|
74
78
|
this.config.strategy = 'hybrid';
|
|
75
|
-
console.log('📦 Strategy: Hybrid (critical +
|
|
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 <
|
|
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
|
|
113
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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(/
|
|
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
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
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:
|
|
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('
|
|
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
|
*/
|