slicejs-cli 2.7.8 → 2.8.0
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.
- package/README.md +26 -2
- package/client.js +27 -19
- package/commands/startServer/startServer.js +40 -13
- package/commands/startServer/watchServer.js +19 -7
- package/commands/utils/bundling/BundleGenerator.js +195 -14
- package/commands/utils/bundling/DependencyAnalyzer.js +67 -3
- package/package.json +3 -4
- package/post.js +7 -7
package/README.md
CHANGED
|
@@ -17,13 +17,37 @@
|
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
### Local (Recommended)
|
|
21
|
+
|
|
22
|
+
1. Install as a development dependency:
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
25
|
npm install slicejs-cli --save-dev
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
2. Add to your `package.json` scripts:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "slice dev",
|
|
34
|
+
"build": "slice build",
|
|
35
|
+
"slice": "slice"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. usage:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm run dev
|
|
44
|
+
# or pass arguments
|
|
45
|
+
npm run slice -- get Button
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Global (Not Recommended)
|
|
49
|
+
|
|
50
|
+
Global installations can lead to version mismatches and "works on my machine" issues.
|
|
27
51
|
|
|
28
52
|
```bash
|
|
29
53
|
npm install -g slicejs-cli
|
package/client.js
CHANGED
|
@@ -132,6 +132,33 @@ sliceClient
|
|
|
132
132
|
await versionChecker.showVersionInfo();
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
// BUNDLE COMMAND
|
|
136
|
+
const bundleCommand = sliceClient.command("bundle")
|
|
137
|
+
.description("Build component bundles for production")
|
|
138
|
+
.action(async (options) => {
|
|
139
|
+
await runWithVersionCheck(async () => {
|
|
140
|
+
await bundle(options);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
bundleCommand
|
|
145
|
+
.command("clean")
|
|
146
|
+
.description("Remove all generated bundles")
|
|
147
|
+
.action(async () => {
|
|
148
|
+
await cleanBundles();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
bundleCommand
|
|
152
|
+
.command("info")
|
|
153
|
+
.description("Show information about generated bundles")
|
|
154
|
+
.action(async () => {
|
|
155
|
+
await bundleInfo();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
bundleCommand
|
|
159
|
+
.option("-a, --analyze", "Analyze project dependencies without bundling")
|
|
160
|
+
.option("-v, --verbose", "Show detailed output");
|
|
161
|
+
|
|
135
162
|
// DEV COMMAND (DEVELOPMENT) - COMANDO PRINCIPAL
|
|
136
163
|
sliceClient
|
|
137
164
|
.command("dev")
|
|
@@ -453,25 +480,6 @@ sliceClient
|
|
|
453
480
|
subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage()
|
|
454
481
|
});
|
|
455
482
|
|
|
456
|
-
sliceClient
|
|
457
|
-
.command('bundle')
|
|
458
|
-
.description('Generate production bundles for optimal loading')
|
|
459
|
-
.option('-a, --analyze', 'Only analyze without generating bundles')
|
|
460
|
-
.option('-v, --verbose', 'Show detailed information')
|
|
461
|
-
.action(bundle);
|
|
462
|
-
|
|
463
|
-
// Subcomando: limpiar bundles
|
|
464
|
-
sliceClient
|
|
465
|
-
.command('bundle:clean')
|
|
466
|
-
.description('Remove all generated bundles')
|
|
467
|
-
.action(cleanBundles);
|
|
468
|
-
|
|
469
|
-
// Subcomando: información
|
|
470
|
-
sliceClient
|
|
471
|
-
.command('bundle:info')
|
|
472
|
-
.description('Show information about generated bundles')
|
|
473
|
-
.action(bundleInfo);
|
|
474
|
-
|
|
475
483
|
|
|
476
484
|
// Custom help - SIMPLIFICADO para development only
|
|
477
485
|
sliceClient.addHelpText('after', `
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// commands/startServer/startServer.js - MEJORADO CON VALIDACIÓN Y FEEDBACK
|
|
2
2
|
|
|
3
|
+
import bundle from '../bundle/bundle.js';
|
|
3
4
|
import fs from 'fs-extra';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import { fileURLToPath } from 'url';
|
|
@@ -184,19 +185,17 @@ export default async function startServer(options = {}) {
|
|
|
184
185
|
throw new Error('Project structure not found. Run "slice init" first.');
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
` 2. Use a different port: slice ${mode === 'development' ? 'dev' : mode === 'bundled' ? 'start --bundled' : 'start'} -p <port>`
|
|
196
|
-
);
|
|
188
|
+
let actualPort = await isPortAvailable(port) ? port : port + 1; // Try one more port
|
|
189
|
+
if(actualPort !== port) {
|
|
190
|
+
// Check if the fallback is available
|
|
191
|
+
const fallbackAvailable = await isPortAvailable(actualPort);
|
|
192
|
+
if(!fallbackAvailable) {
|
|
193
|
+
throw new Error(`Ports ${port} and ${actualPort} are in use.`);
|
|
194
|
+
}
|
|
195
|
+
Print.info(`ℹ️ Port ${port} in use, using ${actualPort} instead.`);
|
|
197
196
|
}
|
|
198
197
|
|
|
199
|
-
Print.serverStatus('checking',
|
|
198
|
+
Print.serverStatus('checking', `Port ${actualPort} available ✓`);
|
|
200
199
|
Print.newLine();
|
|
201
200
|
|
|
202
201
|
if (mode === 'production') {
|
|
@@ -214,12 +213,40 @@ export default async function startServer(options = {}) {
|
|
|
214
213
|
Print.newLine();
|
|
215
214
|
|
|
216
215
|
// Iniciar el servidor con argumentos
|
|
217
|
-
|
|
216
|
+
let serverProcess = await startNodeServer(actualPort, mode);
|
|
218
217
|
|
|
219
218
|
// Configurar watch mode si está habilitado
|
|
220
219
|
if (watch) {
|
|
221
220
|
Print.newLine();
|
|
222
|
-
const watcher = setupWatcher(serverProcess)
|
|
221
|
+
const watcher = setupWatcher(serverProcess, async (changedPath) => {
|
|
222
|
+
if (serverProcess) {
|
|
223
|
+
serverProcess.kill();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Short delay to ensure port is freed
|
|
227
|
+
await new Promise(r => setTimeout(r, 500));
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// If we are in bundled mode, regenerate bundles before restarting
|
|
231
|
+
if (mode === 'bundled') {
|
|
232
|
+
Print.info('🔄 File changed. Regenerating bundles...');
|
|
233
|
+
try {
|
|
234
|
+
await bundle({ verbose: false });
|
|
235
|
+
} catch (err) {
|
|
236
|
+
Print.error('Bundle generation failed during watch restart');
|
|
237
|
+
console.error(err);
|
|
238
|
+
// We continue restarting anyway to show error in browser if possible,
|
|
239
|
+
// or maybe just to keep process alive.
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
Print.info('🔄 File changed. Restarting server...');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
serverProcess = await startNodeServer(actualPort, mode);
|
|
246
|
+
} catch (e) {
|
|
247
|
+
Print.error(`Failed to restart server: ${e.message}`);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
223
250
|
|
|
224
251
|
// Cleanup en exit
|
|
225
252
|
const cleanup = () => {
|
|
@@ -7,7 +7,7 @@ import Print from '../Print.js';
|
|
|
7
7
|
* @param {ChildProcess} serverProcess - Proceso del servidor
|
|
8
8
|
* @returns {FSWatcher} - Watcher de chokidar
|
|
9
9
|
*/
|
|
10
|
-
export default function setupWatcher(serverProcess) {
|
|
10
|
+
export default function setupWatcher(serverProcess, onRestart) {
|
|
11
11
|
Print.info('Watch mode enabled - monitoring file changes...');
|
|
12
12
|
Print.newLine();
|
|
13
13
|
|
|
@@ -16,6 +16,7 @@ export default function setupWatcher(serverProcess) {
|
|
|
16
16
|
/(^|[\/\\])\../, // archivos ocultos
|
|
17
17
|
'**/node_modules/**',
|
|
18
18
|
'**/dist/**',
|
|
19
|
+
'**/bundles/**',
|
|
19
20
|
'**/*.log'
|
|
20
21
|
],
|
|
21
22
|
persistent: true,
|
|
@@ -30,19 +31,30 @@ export default function setupWatcher(serverProcess) {
|
|
|
30
31
|
|
|
31
32
|
watcher
|
|
32
33
|
.on('change', (path) => {
|
|
33
|
-
console.log(chalk.cyan(`📝 File changed: ${path}`));
|
|
34
|
-
|
|
35
34
|
// Debounce para evitar múltiples reloads
|
|
36
35
|
clearTimeout(reloadTimeout);
|
|
37
36
|
reloadTimeout = setTimeout(() => {
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
if(onRestart) {
|
|
38
|
+
console.log(chalk.yellow('🔄 Changes detected, restarting server...'));
|
|
39
|
+
onRestart(path);
|
|
40
|
+
} else {
|
|
41
|
+
console.log(chalk.yellow('🔄 Changes detected, server will reload automatically... (No handler)'));
|
|
42
|
+
}
|
|
43
|
+
}, 500);
|
|
40
44
|
})
|
|
41
45
|
.on('add', (path) => {
|
|
42
|
-
|
|
46
|
+
// console.log(chalk.green(`➕ New file added: ${path}`));
|
|
47
|
+
clearTimeout(reloadTimeout);
|
|
48
|
+
reloadTimeout = setTimeout(() => {
|
|
49
|
+
if (onRestart) onRestart(path);
|
|
50
|
+
}, 500);
|
|
43
51
|
})
|
|
44
52
|
.on('unlink', (path) => {
|
|
45
|
-
|
|
53
|
+
// console.log(chalk.red(`➖ File removed: ${path}`));
|
|
54
|
+
clearTimeout(reloadTimeout);
|
|
55
|
+
reloadTimeout = setTimeout(() => {
|
|
56
|
+
if (onRestart) onRestart(path);
|
|
57
|
+
}, 500);
|
|
46
58
|
})
|
|
47
59
|
.on('error', (error) => {
|
|
48
60
|
Print.error(`Watcher error: ${error.message}`);
|
|
@@ -220,13 +220,66 @@ export default class BundleGenerator {
|
|
|
220
220
|
assignHybridBundles(criticalNames) {
|
|
221
221
|
const routeGroups = new Map();
|
|
222
222
|
|
|
223
|
-
//
|
|
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)
|
|
224
269
|
for (const route of this.analysisData.routes) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
routeGroups.
|
|
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);
|
|
228
282
|
}
|
|
229
|
-
routeGroups.get(category).push(route);
|
|
230
283
|
}
|
|
231
284
|
|
|
232
285
|
// Create bundles for each group
|
|
@@ -268,9 +321,19 @@ export default class BundleGenerator {
|
|
|
268
321
|
}
|
|
269
322
|
|
|
270
323
|
/**
|
|
271
|
-
* Categorizes a route path for grouping
|
|
324
|
+
* Categorizes a route path for grouping, considering MultiRoute context
|
|
272
325
|
*/
|
|
273
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
|
|
274
337
|
const path = routePath.toLowerCase();
|
|
275
338
|
|
|
276
339
|
if (path === '/' || path === '/home') return 'home';
|
|
@@ -461,7 +524,7 @@ export default class BundleGenerator {
|
|
|
461
524
|
name: comp.name,
|
|
462
525
|
category: comp.category,
|
|
463
526
|
categoryType: comp.categoryType,
|
|
464
|
-
js: this.cleanJavaScript(jsContent),
|
|
527
|
+
js: this.cleanJavaScript(jsContent, comp.name, comp.path),
|
|
465
528
|
externalDependencies: dependencyContents, // Files imported with import statements
|
|
466
529
|
componentDependencies: Array.from(comp.dependencies), // Other components this one depends on
|
|
467
530
|
html: htmlContent,
|
|
@@ -484,16 +547,134 @@ export default class BundleGenerator {
|
|
|
484
547
|
}
|
|
485
548
|
|
|
486
549
|
/**
|
|
487
|
-
* Cleans JavaScript code by
|
|
550
|
+
* Cleans JavaScript code by processing imports and ensuring class is available globally
|
|
488
551
|
*/
|
|
489
|
-
cleanJavaScript(code) {
|
|
490
|
-
|
|
491
|
-
|
|
552
|
+
cleanJavaScript(code, componentName, componentPath) {
|
|
553
|
+
let newCode = code;
|
|
554
|
+
|
|
555
|
+
// 1. In-line local imports to support data files and helpers
|
|
556
|
+
// Matches: import [clause] from '[path]'
|
|
557
|
+
const importRegex = /import\s+([\w\s{},*]+)\s+from\s+['"`]([\.\/][^'"`]+)['"`];?/g;
|
|
558
|
+
|
|
559
|
+
newCode = newCode.replace(importRegex, (match, importClause, importPath) => {
|
|
560
|
+
try {
|
|
561
|
+
const absolutePath = path.resolve(componentPath, importPath);
|
|
562
|
+
|
|
563
|
+
// Try different extensions if file doesn't exist
|
|
564
|
+
let finalPath = absolutePath;
|
|
565
|
+
if (!fs.existsSync(finalPath)) {
|
|
566
|
+
if (fs.existsSync(finalPath + '.js')) finalPath += '.js';
|
|
567
|
+
else if (fs.existsSync(finalPath + '.json')) finalPath += '.json';
|
|
568
|
+
else {
|
|
569
|
+
console.warn(`BundleGenerator: Could not resolve ${importPath} in ${componentName}`);
|
|
570
|
+
return `/* Fail: File not found ${importPath} */`; // return comment so regular import stripper deletes it
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
let fileContent = fs.readFileSync(finalPath, 'utf-8');
|
|
575
|
+
|
|
576
|
+
// Parse Import Clause
|
|
577
|
+
let defaultImport = null;
|
|
578
|
+
let namedImports = [];
|
|
579
|
+
|
|
580
|
+
const cleanClause = importClause.trim();
|
|
581
|
+
|
|
582
|
+
if (cleanClause.includes('{')) {
|
|
583
|
+
// Has named imports
|
|
584
|
+
const parts = cleanClause.split('{');
|
|
585
|
+
const preBrace = parts[0].trim(); // "Default, " or ""
|
|
586
|
+
const braceContent = parts[1].replace('}', '').trim(); // "A, B as C"
|
|
587
|
+
|
|
588
|
+
if (preBrace) {
|
|
589
|
+
defaultImport = preBrace.replace(',', '').trim();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (braceContent) {
|
|
593
|
+
namedImports = braceContent.split(',').map(i => {
|
|
594
|
+
const [name, alias] = i.split(/\s+as\s+/).map(s => s.trim());
|
|
595
|
+
return { name, alias: alias || name };
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
if (cleanClause.includes('*')) return match; // Skip namespace
|
|
600
|
+
defaultImport = cleanClause;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Transform exported content to local variables
|
|
604
|
+
|
|
605
|
+
// STRATEGY:
|
|
606
|
+
// 1. Convert all 'const/let' to 'var' to allow redeclaration (resolves naming collisions)
|
|
607
|
+
// 2. Wrap exports
|
|
608
|
+
|
|
609
|
+
// Replace const/let with var globally to prevent "Identifier already declared" errors
|
|
610
|
+
// Use a sophisticated regex that handles:
|
|
611
|
+
// - Start of line or indentation
|
|
612
|
+
// - Previous statement termination (; or })
|
|
613
|
+
// - Optional export keyword
|
|
614
|
+
fileContent = fileContent.replace(/(^|[\s;}])(export\s+)?(const|let)(?=\s+)/gm, '$1$2var');
|
|
615
|
+
|
|
616
|
+
const defaultExportName = `__default_${path.basename(finalPath, path.extname(finalPath)).replace(/\W/g, '_')}_${Math.random().toString(36).substr(2, 5)}`;
|
|
617
|
+
|
|
618
|
+
// Handle export default
|
|
619
|
+
if (fileContent.includes('export default')) {
|
|
620
|
+
fileContent = fileContent.replace(/export\s+default\s+/g, `var ${defaultExportName} = `);
|
|
621
|
+
} else {
|
|
622
|
+
// If no export default, but we are importing default, try to find a variable with the same name
|
|
623
|
+
// This happens in legacy data files like: const data = ...; (no export default)
|
|
624
|
+
if (defaultImport) {
|
|
625
|
+
// Check if there is a variable matching the import name in the file content
|
|
626
|
+
const varRegex = new RegExp(`var\\s+${defaultImport}\\s*=`);
|
|
627
|
+
if (varRegex.test(fileContent)) {
|
|
628
|
+
// Found it, map it to our default export name for consistency
|
|
629
|
+
fileContent += `\nvar ${defaultExportName} = ${defaultImport};\n`;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Handle named exports: remove 'export' keyword
|
|
635
|
+
fileContent = fileContent.replace(/^\s*export\s+/gm, '');
|
|
636
|
+
// Also clean up export { ... } statements
|
|
637
|
+
fileContent = fileContent.replace(/^\s*export\s*\{[^}]+\};?/gm, '');
|
|
638
|
+
|
|
639
|
+
let aliasCode = '';
|
|
640
|
+
|
|
641
|
+
// Map Default Import
|
|
642
|
+
if (defaultImport) {
|
|
643
|
+
// Use var for alias to be safe against existing variable
|
|
644
|
+
aliasCode += `if (typeof ${defaultImport} === 'undefined') { var ${defaultImport} = (typeof ${defaultExportName} !== 'undefined') ? ${defaultExportName} : undefined; }\n`;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Map Named Imports with Aliases
|
|
648
|
+
namedImports.forEach(({ name, alias }) => {
|
|
649
|
+
if (name !== alias) {
|
|
650
|
+
aliasCode += `var ${alias} = ${name};\n`;
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
return `/* Inlined: ${importPath} */\n${fileContent}\n${aliasCode}`;
|
|
655
|
+
} catch (e) {
|
|
656
|
+
console.warn(`Failed to inline ${importPath} in ${componentName}: ${e.message}`);
|
|
657
|
+
return `/* Error inlining ${importPath}: ${e.message} */`;
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// 2. Remove any remaining import statements (non-local or failed ones)
|
|
662
|
+
newCode = newCode.replace(/^.*import\s+.*from\s+['"`].*['"`];?.*$/gm, '');
|
|
663
|
+
|
|
664
|
+
// 3. Remove export default from the component itself
|
|
665
|
+
newCode = newCode.replace(/export\s+default\s+/g, '');
|
|
666
|
+
|
|
667
|
+
// 4. Make sure the class is available globally for bundle evaluation
|
|
668
|
+
if (newCode.includes('customElements.define')) {
|
|
669
|
+
newCode = newCode.replace(/customElements\.define\([^;]+\);?\s*$/, `window.${componentName} = ${componentName};\n$&`);
|
|
670
|
+
} else {
|
|
671
|
+
newCode += `\nwindow.${componentName} = ${componentName};`;
|
|
672
|
+
}
|
|
492
673
|
|
|
493
|
-
//
|
|
494
|
-
|
|
674
|
+
// 5. Add return statement for bundle evaluation compatibility
|
|
675
|
+
newCode += `\nreturn ${componentName};`;
|
|
495
676
|
|
|
496
|
-
return
|
|
677
|
+
return newCode;
|
|
497
678
|
}
|
|
498
679
|
|
|
499
680
|
/**
|
|
@@ -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
|
}
|
|
@@ -153,8 +154,33 @@ export default class DependencyAnalyzer {
|
|
|
153
154
|
CallExpression(path) {
|
|
154
155
|
const { callee, arguments: args } = path.node;
|
|
155
156
|
|
|
156
|
-
// slice.build('
|
|
157
|
+
// slice.build('MultiRoute', { routes: [...] })
|
|
157
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 (
|
|
158
184
|
callee.type === 'MemberExpression' &&
|
|
159
185
|
callee.object.name === 'slice' &&
|
|
160
186
|
callee.property.name === 'build' &&
|
|
@@ -181,7 +207,7 @@ export default class DependencyAnalyzer {
|
|
|
181
207
|
}
|
|
182
208
|
|
|
183
209
|
/**
|
|
184
|
-
* Analyzes the routes file
|
|
210
|
+
* Analyzes the routes file and detects route groups
|
|
185
211
|
*/
|
|
186
212
|
async analyzeRoutes() {
|
|
187
213
|
if (!await fs.pathExists(this.routesPath)) {
|
|
@@ -189,7 +215,7 @@ export default class DependencyAnalyzer {
|
|
|
189
215
|
}
|
|
190
216
|
|
|
191
217
|
const content = await fs.readFile(this.routesPath, 'utf-8');
|
|
192
|
-
|
|
218
|
+
|
|
193
219
|
try {
|
|
194
220
|
const ast = parse(content, {
|
|
195
221
|
sourceType: 'module',
|
|
@@ -225,6 +251,10 @@ export default class DependencyAnalyzer {
|
|
|
225
251
|
}
|
|
226
252
|
}
|
|
227
253
|
});
|
|
254
|
+
|
|
255
|
+
// Detect and store route groups based on MultiRoute usage
|
|
256
|
+
this.routeGroups = this.detectRouteGroups();
|
|
257
|
+
|
|
228
258
|
} catch (error) {
|
|
229
259
|
console.warn(`⚠️ Error parseando rutas: ${error.message}`);
|
|
230
260
|
}
|
|
@@ -249,6 +279,40 @@ export default class DependencyAnalyzer {
|
|
|
249
279
|
}
|
|
250
280
|
}
|
|
251
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
|
+
|
|
252
316
|
/**
|
|
253
317
|
* Gets all dependencies of a component (recursive)
|
|
254
318
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slicejs-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Command client for developing web applications with Slice.js framework",
|
|
5
5
|
"main": "client.js",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"author": "vkneider",
|
|
28
28
|
"type": "module",
|
|
29
|
+
"preferGlobal": false,
|
|
29
30
|
"license": "ISC",
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"chalk": "^5.6.2",
|
|
@@ -38,9 +39,7 @@
|
|
|
38
39
|
"inquirer": "^12.4.2",
|
|
39
40
|
"ora": "^8.2.0",
|
|
40
41
|
"slicejs-web-framework": "latest",
|
|
41
|
-
"terser": "^5.43.1"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
42
|
+
"terser": "^5.43.1",
|
|
44
43
|
"@babel/parser": "^7.28.5",
|
|
45
44
|
"@babel/traverse": "^7.28.5"
|
|
46
45
|
}
|
package/post.js
CHANGED
|
@@ -12,14 +12,14 @@ const targetRoot = initCwd || path.resolve(__dirname, '../../');
|
|
|
12
12
|
const projectPackageJsonPath = path.join(targetRoot, 'package.json');
|
|
13
13
|
|
|
14
14
|
if (isGlobal) {
|
|
15
|
-
console.log('
|
|
16
|
-
console.log('
|
|
17
|
-
console.log('
|
|
18
|
-
console.log(' slice get Button');
|
|
15
|
+
console.log('⚠️ Global installation of slicejs-cli detected.');
|
|
16
|
+
console.log(' We strongly recommend using a local installation to avoid version mismatches.');
|
|
17
|
+
console.log(' Uninstall global: npm uninstall -g slicejs-cli');
|
|
19
18
|
process.exit(0);
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
console.log('
|
|
23
|
-
console.log('
|
|
24
|
-
console.log('
|
|
21
|
+
console.log('✅ slicejs-cli installed successfully.');
|
|
22
|
+
console.log(' Add the CLI to your package.json scripts:');
|
|
23
|
+
console.log(' "dev": "slice dev"');
|
|
24
|
+
console.log(' Then run: npm run dev');
|
|
25
25
|
process.exit(0);
|