slicejs-cli 2.7.4 → 2.7.6
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/client.js +50 -8
- package/commands/bundle/bundle.js +226 -0
- package/commands/doctor/doctor.js +11 -11
- package/commands/getComponent/getComponent.js +74 -36
- package/commands/init/init.js +56 -57
- package/commands/startServer/startServer.js +6 -2
- package/commands/utils/PathHelper.js +10 -0
- package/commands/utils/VersionChecker.js +2 -2
- package/commands/utils/bundling/BundleGenerator.js +389 -0
- package/commands/utils/bundling/DependencyAnalyzer.js +340 -0
- package/commands/utils/updateManager.js +87 -87
- package/package.json +1 -1
- package/refactor.md +271 -0
package/commands/init/init.js
CHANGED
|
@@ -48,30 +48,29 @@ export default async function initializeProject(projectType) {
|
|
|
48
48
|
const srcDir = path.join(sliceBaseDir, 'src');
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
|
-
|
|
52
|
-
if (fs.existsSync(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
51
|
+
if (fs.existsSync(destinationApi)) throw new Error(`The "api" directory already exists: ${destinationApi}`);
|
|
52
|
+
if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
Print.error('Validating destination directories:', error.message);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
58
57
|
|
|
59
58
|
// 1. COPIAR LA CARPETA API (mantener lógica original)
|
|
60
|
-
const apiSpinner = ora('Copying API structure...').start();
|
|
61
|
-
try {
|
|
62
|
-
if (!fs.existsSync(apiDir)) throw new Error(`
|
|
63
|
-
await fs.copy(apiDir, destinationApi, { recursive: true });
|
|
64
|
-
apiSpinner.succeed('API structure created successfully');
|
|
65
|
-
} catch (error) {
|
|
66
|
-
apiSpinner.fail('Error copying API structure');
|
|
67
|
-
Print.error(error.message);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
59
|
+
const apiSpinner = ora('Copying API structure...').start();
|
|
60
|
+
try {
|
|
61
|
+
if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
|
|
62
|
+
await fs.copy(apiDir, destinationApi, { recursive: true });
|
|
63
|
+
apiSpinner.succeed('API structure created successfully');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
apiSpinner.fail('Error copying API structure');
|
|
66
|
+
Print.error(error.message);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
70
69
|
|
|
71
70
|
// 2. CREAR ESTRUCTURA SRC BÁSICA (sin copiar componentes Visual)
|
|
72
|
-
const srcSpinner = ora('Creating src structure...').start();
|
|
73
|
-
try {
|
|
74
|
-
if (!fs.existsSync(srcDir)) throw new Error(`
|
|
71
|
+
const srcSpinner = ora('Creating src structure...').start();
|
|
72
|
+
try {
|
|
73
|
+
if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
|
|
75
74
|
|
|
76
75
|
// Copiar solo los archivos base de src, excluyendo Components/Visual
|
|
77
76
|
await fs.ensureDir(destinationSrc);
|
|
@@ -95,22 +94,22 @@ export default async function initializeProject(projectType) {
|
|
|
95
94
|
const destComponentItemPath = path.join(destItemPath, componentItem);
|
|
96
95
|
|
|
97
96
|
if (componentItem !== 'Visual') {
|
|
98
|
-
//
|
|
99
|
-
await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
|
|
100
|
-
} else {
|
|
101
|
-
//
|
|
102
|
-
await fs.ensureDir(destComponentItemPath);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
//
|
|
107
|
-
await fs.copy(srcItemPath, destItemPath, { recursive: true });
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
//
|
|
111
|
-
await fs.copy(srcItemPath, destItemPath);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
97
|
+
// Copy Service and other component types
|
|
98
|
+
await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
|
|
99
|
+
} else {
|
|
100
|
+
// Only create empty Visual directory
|
|
101
|
+
await fs.ensureDir(destComponentItemPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// Copy other folders normally
|
|
106
|
+
await fs.copy(srcItemPath, destItemPath, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// Copy files normally
|
|
110
|
+
await fs.copy(srcItemPath, destItemPath);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
114
113
|
|
|
115
114
|
srcSpinner.succeed('Source structure created successfully');
|
|
116
115
|
} catch (error) {
|
|
@@ -143,15 +142,15 @@ export default async function initializeProject(projectType) {
|
|
|
143
142
|
if (successful > 0 && failed === 0) {
|
|
144
143
|
componentsSpinner.succeed(`All ${successful} Visual components installed successfully`);
|
|
145
144
|
} else if (successful > 0) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
|
|
146
|
+
Print.info('You can install failed components later using "slice get <component-name>"');
|
|
147
|
+
} else {
|
|
148
|
+
componentsSpinner.fail('Failed to install Visual components');
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
componentsSpinner.warn('No Visual components found in registry');
|
|
152
|
+
Print.info('You can add components later using "slice get <component-name>"');
|
|
153
|
+
}
|
|
155
154
|
|
|
156
155
|
} catch (error) {
|
|
157
156
|
componentsSpinner.fail('Could not download Visual components from official repository');
|
|
@@ -223,21 +222,21 @@ export default async function initializeProject(projectType) {
|
|
|
223
222
|
console.log(' npm run get - Install components');
|
|
224
223
|
console.log(' npm run browse - Browse components');
|
|
225
224
|
} catch (error) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
225
|
+
pkgSpinner.fail('Failed to configure npm scripts');
|
|
226
|
+
Print.error(error.message);
|
|
227
|
+
}
|
|
229
228
|
|
|
230
|
-
Print.success('
|
|
231
|
-
Print.newLine();
|
|
232
|
-
Print.info('Next steps:');
|
|
233
|
-
console.log(' slice browse - View available components');
|
|
234
|
-
console.log(' slice get Button - Install specific components');
|
|
235
|
-
console.log(' slice sync - Update all components to latest versions');
|
|
229
|
+
Print.success('Project initialized successfully.');
|
|
230
|
+
Print.newLine();
|
|
231
|
+
Print.info('Next steps:');
|
|
232
|
+
console.log(' slice browse - View available components');
|
|
233
|
+
console.log(' slice get Button - Install specific components');
|
|
234
|
+
console.log(' slice sync - Update all components to latest versions');
|
|
236
235
|
|
|
237
236
|
} catch (error) {
|
|
238
|
-
Print.error('
|
|
239
|
-
}
|
|
240
|
-
}
|
|
237
|
+
Print.error('Unexpected error initializing project:', error.message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
241
240
|
|
|
242
241
|
/**
|
|
243
242
|
* Obtiene TODOS los componentes Visual disponibles en el registry
|
|
@@ -86,6 +86,8 @@ function startNodeServer(port, mode) {
|
|
|
86
86
|
const args = [apiIndexPath];
|
|
87
87
|
if (mode === 'production') {
|
|
88
88
|
args.push('--production');
|
|
89
|
+
} else if (mode === 'bundled') {
|
|
90
|
+
args.push('--bundled');
|
|
89
91
|
} else {
|
|
90
92
|
args.push('--development');
|
|
91
93
|
}
|
|
@@ -171,7 +173,7 @@ export default async function startServer(options = {}) {
|
|
|
171
173
|
const config = loadConfig();
|
|
172
174
|
const defaultPort = config?.server?.port || 3000;
|
|
173
175
|
|
|
174
|
-
const { mode = 'development', port = defaultPort, watch = false } = options;
|
|
176
|
+
const { mode = 'development', port = defaultPort, watch = false, bundled = false } = options;
|
|
175
177
|
|
|
176
178
|
try {
|
|
177
179
|
Print.title(`🚀 Starting Slice.js ${mode} server...`);
|
|
@@ -190,7 +192,7 @@ export default async function startServer(options = {}) {
|
|
|
190
192
|
throw new Error(
|
|
191
193
|
`Port ${port} is already in use. Please:\n` +
|
|
192
194
|
` 1. Stop the process using port ${port}, or\n` +
|
|
193
|
-
` 2. Use a different port: slice ${mode === 'development' ? 'dev' : 'start'} -p <port>`
|
|
195
|
+
` 2. Use a different port: slice ${mode === 'development' ? 'dev' : mode === 'bundled' ? 'start --bundled' : 'start'} -p <port>`
|
|
194
196
|
);
|
|
195
197
|
}
|
|
196
198
|
|
|
@@ -203,6 +205,8 @@ export default async function startServer(options = {}) {
|
|
|
203
205
|
throw new Error('No production build found. Run "slice build" first.');
|
|
204
206
|
}
|
|
205
207
|
Print.info('Production mode: serving optimized files from /dist');
|
|
208
|
+
} else if (mode === 'bundled') {
|
|
209
|
+
Print.info('Bundled mode: serving with generated bundles for optimized loading');
|
|
206
210
|
} else {
|
|
207
211
|
Print.info('Development mode: serving files from /src with hot reload');
|
|
208
212
|
}
|
|
@@ -14,6 +14,16 @@ function candidates(moduleUrl) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function resolveProjectRoot(moduleUrl) {
|
|
17
|
+
const initCwd = process.env.INIT_CWD
|
|
18
|
+
if (initCwd && fs.pathExistsSync(initCwd)) {
|
|
19
|
+
return initCwd
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const cwd = process.cwd()
|
|
23
|
+
if (cwd && fs.pathExistsSync(cwd)) {
|
|
24
|
+
return cwd
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
const dirs = candidates(moduleUrl)
|
|
18
28
|
for (const root of dirs) {
|
|
19
29
|
const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
|
|
@@ -111,7 +111,7 @@ class VersionChecker {
|
|
|
111
111
|
|
|
112
112
|
if (!silent && (cliStatus === 'outdated' || frameworkStatus === 'outdated')) {
|
|
113
113
|
console.log(''); // Line break
|
|
114
|
-
Print.warning('📦
|
|
114
|
+
Print.warning('📦 Available Updates:');
|
|
115
115
|
|
|
116
116
|
if (cliStatus === 'outdated') {
|
|
117
117
|
console.log(` 🔧 CLI: ${current.cli} → ${latest.cli}`);
|
|
@@ -142,7 +142,7 @@ class VersionChecker {
|
|
|
142
142
|
const current = await this.getCurrentVersions();
|
|
143
143
|
const latest = await this.getLatestVersions();
|
|
144
144
|
|
|
145
|
-
console.log('\n📋
|
|
145
|
+
console.log('\n📋 Version Information:');
|
|
146
146
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
147
147
|
|
|
148
148
|
if (current?.cli) {
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
// cli/utils/bundling/BundleGenerator.js
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import { getSrcPath, getComponentsJsPath } from '../PathHelper.js';
|
|
6
|
+
|
|
7
|
+
export default class BundleGenerator {
|
|
8
|
+
constructor(moduleUrl, analysisData) {
|
|
9
|
+
this.moduleUrl = moduleUrl;
|
|
10
|
+
this.analysisData = analysisData;
|
|
11
|
+
this.srcPath = getSrcPath(moduleUrl);
|
|
12
|
+
this.componentsPath = path.dirname(getComponentsJsPath(moduleUrl));
|
|
13
|
+
|
|
14
|
+
// Configuration
|
|
15
|
+
this.config = {
|
|
16
|
+
maxCriticalSize: 50 * 1024, // 50KB
|
|
17
|
+
maxCriticalComponents: 15,
|
|
18
|
+
minSharedUsage: 3, // Minimum routes to be considered "shared"
|
|
19
|
+
strategy: 'hybrid' // 'global', 'hybrid', 'per-route'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
this.bundles = {
|
|
23
|
+
critical: {
|
|
24
|
+
components: [],
|
|
25
|
+
size: 0,
|
|
26
|
+
file: 'slice-bundle.critical.js'
|
|
27
|
+
},
|
|
28
|
+
routes: {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generates all bundles
|
|
34
|
+
*/
|
|
35
|
+
async generate() {
|
|
36
|
+
console.log('🔨 Generating bundles...');
|
|
37
|
+
|
|
38
|
+
// 1. Determine optimal strategy
|
|
39
|
+
this.determineStrategy();
|
|
40
|
+
|
|
41
|
+
// 2. Identify critical components
|
|
42
|
+
this.identifyCriticalComponents();
|
|
43
|
+
|
|
44
|
+
// 3. Assign components to routes
|
|
45
|
+
this.assignRouteComponents();
|
|
46
|
+
|
|
47
|
+
// 4. Generate bundle files
|
|
48
|
+
const files = await this.generateBundleFiles();
|
|
49
|
+
|
|
50
|
+
// 5. Generate configuration
|
|
51
|
+
const config = this.generateBundleConfig();
|
|
52
|
+
|
|
53
|
+
console.log('✅ Bundles generated successfully');
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
bundles: this.bundles,
|
|
57
|
+
config,
|
|
58
|
+
files
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Determines the optimal bundling strategy
|
|
64
|
+
*/
|
|
65
|
+
determineStrategy() {
|
|
66
|
+
const { metrics } = this.analysisData;
|
|
67
|
+
const { totalComponents, sharedPercentage } = metrics;
|
|
68
|
+
|
|
69
|
+
// Strategy based on size and usage pattern
|
|
70
|
+
if (totalComponents < 20 || sharedPercentage > 60) {
|
|
71
|
+
this.config.strategy = 'global';
|
|
72
|
+
console.log('📦 Strategy: Global Bundle (small project or highly shared)');
|
|
73
|
+
} else if (totalComponents < 60) {
|
|
74
|
+
this.config.strategy = 'hybrid';
|
|
75
|
+
console.log('📦 Strategy: Hybrid (critical + per route)');
|
|
76
|
+
} else {
|
|
77
|
+
this.config.strategy = 'per-route';
|
|
78
|
+
console.log('📦 Strategy: Per Route (large project)');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Identifies critical components for the initial bundle
|
|
84
|
+
*/
|
|
85
|
+
identifyCriticalComponents() {
|
|
86
|
+
const { components } = this.analysisData;
|
|
87
|
+
|
|
88
|
+
// Filter critical candidates
|
|
89
|
+
const candidates = components
|
|
90
|
+
.filter(comp => {
|
|
91
|
+
// Shared components (used in 3+ routes)
|
|
92
|
+
const isShared = comp.routes.size >= this.config.minSharedUsage;
|
|
93
|
+
|
|
94
|
+
// Structural components (Navbar, Footer, etc.)
|
|
95
|
+
const isStructural = comp.categoryType === 'Structural' ||
|
|
96
|
+
['Navbar', 'Footer', 'Layout'].includes(comp.name);
|
|
97
|
+
|
|
98
|
+
// Small and highly used components
|
|
99
|
+
const isSmallAndUseful = comp.size < 5000 && comp.routes.size >= 2;
|
|
100
|
+
|
|
101
|
+
return isShared || isStructural || isSmallAndUseful;
|
|
102
|
+
})
|
|
103
|
+
.sort((a, b) => {
|
|
104
|
+
// Prioritize by: (usage * 10) - size
|
|
105
|
+
const priorityA = (a.routes.size * 10) - (a.size / 1000);
|
|
106
|
+
const priorityB = (b.routes.size * 10) - (b.size / 1000);
|
|
107
|
+
return priorityB - priorityA;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Fill critical bundle up to limit
|
|
111
|
+
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;
|
|
114
|
+
|
|
115
|
+
if (wouldExceedSize || wouldExceedCount) break;
|
|
116
|
+
|
|
117
|
+
this.bundles.critical.components.push(comp);
|
|
118
|
+
this.bundles.critical.size += comp.size;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(`✓ Critical bundle: ${this.bundles.critical.components.length} components, ${(this.bundles.critical.size / 1024).toFixed(1)} KB`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Assigns remaining components to route bundles
|
|
126
|
+
*/
|
|
127
|
+
assignRouteComponents() {
|
|
128
|
+
const criticalNames = new Set(this.bundles.critical.components.map(c => c.name));
|
|
129
|
+
|
|
130
|
+
for (const [routePath, route] of this.analysisData.routes) {
|
|
131
|
+
// Get all route dependencies
|
|
132
|
+
const routeComponents = this.getRouteComponents(route.component);
|
|
133
|
+
|
|
134
|
+
// Filter those already in critical
|
|
135
|
+
const uniqueComponents = routeComponents.filter(comp =>
|
|
136
|
+
!criticalNames.has(comp.name)
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (uniqueComponents.length === 0) continue;
|
|
140
|
+
|
|
141
|
+
const routeKey = this.routeToFileName(routePath);
|
|
142
|
+
const totalSize = uniqueComponents.reduce((sum, c) => sum + c.size, 0);
|
|
143
|
+
|
|
144
|
+
this.bundles.routes[routeKey] = {
|
|
145
|
+
path: routePath,
|
|
146
|
+
components: uniqueComponents,
|
|
147
|
+
size: totalSize,
|
|
148
|
+
file: `slice-bundle.${routeKey}.js`
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
console.log(`✓ Bundle ${routeKey}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Gets all components needed for a route
|
|
157
|
+
*/
|
|
158
|
+
getRouteComponents(componentName) {
|
|
159
|
+
const result = [];
|
|
160
|
+
const visited = new Set();
|
|
161
|
+
|
|
162
|
+
const traverse = (name) => {
|
|
163
|
+
if (visited.has(name)) return;
|
|
164
|
+
visited.add(name);
|
|
165
|
+
|
|
166
|
+
const component = this.analysisData.components.find(c => c.name === name);
|
|
167
|
+
if (!component) return;
|
|
168
|
+
|
|
169
|
+
result.push(component);
|
|
170
|
+
|
|
171
|
+
// Add dependencies recursively
|
|
172
|
+
for (const dep of component.dependencies) {
|
|
173
|
+
traverse(dep);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
traverse(componentName);
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Generates the physical bundle files
|
|
183
|
+
*/
|
|
184
|
+
async generateBundleFiles() {
|
|
185
|
+
const files = [];
|
|
186
|
+
|
|
187
|
+
// 1. Critical bundle
|
|
188
|
+
if (this.bundles.critical.components.length > 0) {
|
|
189
|
+
const criticalFile = await this.createBundleFile(
|
|
190
|
+
this.bundles.critical.components,
|
|
191
|
+
'critical',
|
|
192
|
+
null
|
|
193
|
+
);
|
|
194
|
+
files.push(criticalFile);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 2. Route bundles
|
|
198
|
+
for (const [routeKey, bundle] of Object.entries(this.bundles.routes)) {
|
|
199
|
+
const routeFile = await this.createBundleFile(
|
|
200
|
+
bundle.components,
|
|
201
|
+
'route',
|
|
202
|
+
bundle.path
|
|
203
|
+
);
|
|
204
|
+
files.push(routeFile);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return files;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Creates a bundle file
|
|
212
|
+
*/
|
|
213
|
+
async createBundleFile(components, type, routePath) {
|
|
214
|
+
const routeKey = routePath ? this.routeToFileName(routePath) : 'critical';
|
|
215
|
+
const fileName = `slice-bundle.${routeKey}.js`;
|
|
216
|
+
const filePath = path.join(this.srcPath, fileName);
|
|
217
|
+
|
|
218
|
+
const bundleContent = await this.generateBundleContent(
|
|
219
|
+
components,
|
|
220
|
+
type,
|
|
221
|
+
routePath
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
await fs.writeFile(filePath, bundleContent, 'utf-8');
|
|
225
|
+
|
|
226
|
+
const hash = crypto.createHash('md5').update(bundleContent).digest('hex').substring(0, 12);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
name: routeKey,
|
|
230
|
+
file: fileName,
|
|
231
|
+
path: filePath,
|
|
232
|
+
size: Buffer.byteLength(bundleContent, 'utf-8'),
|
|
233
|
+
hash,
|
|
234
|
+
componentCount: components.length
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Generates the content of a bundle
|
|
240
|
+
*/
|
|
241
|
+
async generateBundleContent(components, type, routePath) {
|
|
242
|
+
const componentsData = {};
|
|
243
|
+
|
|
244
|
+
for (const comp of components) {
|
|
245
|
+
const jsContent = await fs.readFile(
|
|
246
|
+
path.join(comp.path, `${comp.name}.js`),
|
|
247
|
+
'utf-8'
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
let htmlContent = null;
|
|
251
|
+
let cssContent = null;
|
|
252
|
+
|
|
253
|
+
const htmlPath = path.join(comp.path, `${comp.name}.html`);
|
|
254
|
+
const cssPath = path.join(comp.path, `${comp.name}.css`);
|
|
255
|
+
|
|
256
|
+
if (await fs.pathExists(htmlPath)) {
|
|
257
|
+
htmlContent = await fs.readFile(htmlPath, 'utf-8');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (await fs.pathExists(cssPath)) {
|
|
261
|
+
cssContent = await fs.readFile(cssPath, 'utf-8');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
componentsData[comp.name] = {
|
|
265
|
+
name: comp.name,
|
|
266
|
+
category: comp.category,
|
|
267
|
+
categoryType: comp.categoryType,
|
|
268
|
+
js: this.cleanJavaScript(jsContent),
|
|
269
|
+
html: htmlContent,
|
|
270
|
+
css: cssContent,
|
|
271
|
+
size: comp.size,
|
|
272
|
+
dependencies: Array.from(comp.dependencies)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const metadata = {
|
|
277
|
+
version: '2.0.0',
|
|
278
|
+
type,
|
|
279
|
+
route: routePath,
|
|
280
|
+
generated: new Date().toISOString(),
|
|
281
|
+
totalSize: components.reduce((sum, c) => sum + c.size, 0),
|
|
282
|
+
componentCount: components.length,
|
|
283
|
+
strategy: this.config.strategy
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return this.formatBundleFile(componentsData, metadata);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Cleans JavaScript code by removing imports/exports
|
|
291
|
+
*/
|
|
292
|
+
cleanJavaScript(code) {
|
|
293
|
+
// Remove export default
|
|
294
|
+
code = code.replace(/export\s+default\s+/g, '');
|
|
295
|
+
|
|
296
|
+
// Remove imports (components will already be available)
|
|
297
|
+
code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
|
|
298
|
+
|
|
299
|
+
return code;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Formats the bundle file
|
|
304
|
+
*/
|
|
305
|
+
formatBundleFile(componentsData, metadata) {
|
|
306
|
+
return `/**
|
|
307
|
+
* Slice.js Bundle
|
|
308
|
+
* Type: ${metadata.type}
|
|
309
|
+
* Generated: ${metadata.generated}
|
|
310
|
+
* Strategy: ${metadata.strategy}
|
|
311
|
+
* Components: ${metadata.componentCount}
|
|
312
|
+
* Total Size: ${(metadata.totalSize / 1024).toFixed(1)} KB
|
|
313
|
+
*/
|
|
314
|
+
|
|
315
|
+
export const SLICE_BUNDLE = {
|
|
316
|
+
metadata: ${JSON.stringify(metadata, null, 2)},
|
|
317
|
+
|
|
318
|
+
components: ${JSON.stringify(componentsData, null, 2)}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Auto-registration of components
|
|
322
|
+
if (window.slice && window.slice.controller) {
|
|
323
|
+
slice.controller.registerBundle(SLICE_BUNDLE);
|
|
324
|
+
}
|
|
325
|
+
`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Generates the bundle configuration
|
|
330
|
+
*/
|
|
331
|
+
generateBundleConfig() {
|
|
332
|
+
const config = {
|
|
333
|
+
version: '2.0.0',
|
|
334
|
+
strategy: this.config.strategy,
|
|
335
|
+
generated: new Date().toISOString(),
|
|
336
|
+
|
|
337
|
+
stats: {
|
|
338
|
+
totalComponents: this.analysisData.metrics.totalComponents,
|
|
339
|
+
totalRoutes: this.analysisData.metrics.totalRoutes,
|
|
340
|
+
sharedComponents: this.bundles.critical.components.length,
|
|
341
|
+
sharedPercentage: this.analysisData.metrics.sharedPercentage,
|
|
342
|
+
totalSize: this.analysisData.metrics.totalSize,
|
|
343
|
+
criticalSize: this.bundles.critical.size
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
bundles: {
|
|
347
|
+
critical: {
|
|
348
|
+
file: this.bundles.critical.file,
|
|
349
|
+
size: this.bundles.critical.size,
|
|
350
|
+
components: this.bundles.critical.components.map(c => c.name)
|
|
351
|
+
},
|
|
352
|
+
routes: {}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
for (const [key, bundle] of Object.entries(this.bundles.routes)) {
|
|
357
|
+
config.bundles.routes[key] = {
|
|
358
|
+
path: bundle.path,
|
|
359
|
+
file: bundle.file,
|
|
360
|
+
size: bundle.size,
|
|
361
|
+
components: bundle.components.map(c => c.name),
|
|
362
|
+
dependencies: ['critical']
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return config;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Converts a route to filename
|
|
371
|
+
*/
|
|
372
|
+
routeToFileName(routePath) {
|
|
373
|
+
if (routePath === '/') return 'home';
|
|
374
|
+
return routePath
|
|
375
|
+
.replace(/^\//, '')
|
|
376
|
+
.replace(/\//g, '-')
|
|
377
|
+
.replace(/[^a-zA-Z0-9-]/g, '')
|
|
378
|
+
.toLowerCase();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Saves the configuration to file
|
|
383
|
+
*/
|
|
384
|
+
async saveBundleConfig(config) {
|
|
385
|
+
const configPath = path.join(this.srcPath, 'bundle.config.json');
|
|
386
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
387
|
+
console.log(`✓ Configuration saved to ${configPath}`);
|
|
388
|
+
}
|
|
389
|
+
}
|