slicejs-cli 2.1.10 → 2.2.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/client.js +87 -2
- package/commands/Print.js +24 -0
- package/commands/buildProduction/buildProduction.js +493 -0
- package/commands/getComponent/getComponent.js +107 -54
- package/commands/init/init.js +113 -16
- package/commands/startServer/startServer.js +198 -0
- package/package.json +1 -1
- package/post.js +53 -16
package/client.js
CHANGED
|
@@ -6,6 +6,8 @@ import createComponent from "./commands/createComponent/createComponent.js";
|
|
|
6
6
|
import listComponents from "./commands/listComponents/listComponents.js";
|
|
7
7
|
import deleteComponent from "./commands/deleteComponent/deleteComponent.js";
|
|
8
8
|
import getComponent, { listComponents as listRemoteComponents, syncComponents } from "./commands/getComponent/getComponent.js";
|
|
9
|
+
import buildProduction, { buildCommand, serveProductionBuild } from "./commands/buildProduction/buildProduction.js";
|
|
10
|
+
import startServer from "./commands/startServer/startServer.js";
|
|
9
11
|
import versionChecker from "./commands/utils/versionChecker.js";
|
|
10
12
|
import fs from "fs";
|
|
11
13
|
import path from "path";
|
|
@@ -73,6 +75,80 @@ sliceClient
|
|
|
73
75
|
await versionChecker.showVersionInfo();
|
|
74
76
|
});
|
|
75
77
|
|
|
78
|
+
// BUILD COMMAND
|
|
79
|
+
sliceClient
|
|
80
|
+
.command("build")
|
|
81
|
+
.description("Build project for production")
|
|
82
|
+
.option("--serve", "Start production server after build")
|
|
83
|
+
.option("--preview", "Start preview server after build")
|
|
84
|
+
.option("--analyze", "Analyze build without building")
|
|
85
|
+
.option("--skip-clean", "Skip cleaning previous build")
|
|
86
|
+
.option("-p, --port <port>", "Port for preview/serve server", 3001)
|
|
87
|
+
.action(async (options) => {
|
|
88
|
+
await runWithVersionCheck(async () => {
|
|
89
|
+
const success = await buildCommand({
|
|
90
|
+
serve: options.serve,
|
|
91
|
+
preview: options.preview,
|
|
92
|
+
analyze: options.analyze,
|
|
93
|
+
skipClean: options.skipClean,
|
|
94
|
+
port: parseInt(options.port)
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!success) {
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// START COMMAND (PRODUCTION)
|
|
104
|
+
sliceClient
|
|
105
|
+
.command("start")
|
|
106
|
+
.description("Start production server (requires build first)")
|
|
107
|
+
.option("-p, --port <port>", "Port for production server", 3000)
|
|
108
|
+
.option("--build", "Build for production before starting")
|
|
109
|
+
.action(async (options) => {
|
|
110
|
+
await runWithVersionCheck(async () => {
|
|
111
|
+
const distDir = path.join(__dirname, "../../dist");
|
|
112
|
+
|
|
113
|
+
// Verificar si existe build de producción
|
|
114
|
+
if (!fs.existsSync(distDir) && !options.build) {
|
|
115
|
+
Print.error("No production build found");
|
|
116
|
+
Print.info("Run 'slice build' first or use 'slice start --build'");
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Si se solicita build, construir primero
|
|
121
|
+
if (options.build) {
|
|
122
|
+
Print.info("Building for production...");
|
|
123
|
+
const buildSuccess = await buildProduction();
|
|
124
|
+
if (!buildSuccess) {
|
|
125
|
+
Print.error("Build failed, cannot start production server");
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Iniciar servidor de producción
|
|
131
|
+
await startServer({
|
|
132
|
+
mode: 'production',
|
|
133
|
+
port: parseInt(options.port)
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// DEV COMMAND (DEVELOPMENT)
|
|
139
|
+
sliceClient
|
|
140
|
+
.command("dev")
|
|
141
|
+
.description("Start development server")
|
|
142
|
+
.option("-p, --port <port>", "Port for development server", 3000)
|
|
143
|
+
.action(async (options) => {
|
|
144
|
+
await runWithVersionCheck(async () => {
|
|
145
|
+
await startServer({
|
|
146
|
+
mode: 'development',
|
|
147
|
+
port: parseInt(options.port)
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
76
152
|
// COMPONENT COMMAND GROUP - For local component management
|
|
77
153
|
const componentCommand = sliceClient.command("component").alias("comp").description("Manage local project components");
|
|
78
154
|
|
|
@@ -286,18 +362,26 @@ sliceClient
|
|
|
286
362
|
sliceClient.addHelpText('after', `
|
|
287
363
|
Common Usage Examples:
|
|
288
364
|
slice init - Initialize new Slice.js project
|
|
365
|
+
slice dev - Start development server
|
|
366
|
+
slice build - Build for production
|
|
367
|
+
slice start - Start production server
|
|
289
368
|
slice get Button Card Input - Install Visual components from registry
|
|
290
369
|
slice get FetchManager -s - Install Service component from registry
|
|
291
370
|
slice browse - Browse all available components
|
|
292
371
|
slice sync - Update local components to latest versions
|
|
293
372
|
slice component create - Create new local component
|
|
294
|
-
slice update - Check for CLI/framework updates
|
|
295
373
|
|
|
296
374
|
Command Categories:
|
|
297
|
-
• init,
|
|
375
|
+
• init, dev, build, start - Project lifecycle
|
|
298
376
|
• get, browse, sync - Quick registry shortcuts
|
|
299
377
|
• component <cmd> - Local component management
|
|
300
378
|
• registry <cmd> - Official repository operations
|
|
379
|
+
• version, update - Maintenance commands
|
|
380
|
+
|
|
381
|
+
Development vs Production:
|
|
382
|
+
• slice dev - Development server (serves from /src)
|
|
383
|
+
• slice build - Create optimized /dist build
|
|
384
|
+
• slice start - Production server (serves from /dist)
|
|
301
385
|
|
|
302
386
|
More info: https://slice-js-docs.vercel.app/
|
|
303
387
|
`);
|
|
@@ -307,6 +391,7 @@ if (!process.argv.slice(2).length) {
|
|
|
307
391
|
program.outputHelp();
|
|
308
392
|
Print.newLine();
|
|
309
393
|
Print.info("Start with: slice init");
|
|
394
|
+
Print.commandExample("Development", "slice dev");
|
|
310
395
|
Print.commandExample("View available components", "slice browse");
|
|
311
396
|
}
|
|
312
397
|
|
package/commands/Print.js
CHANGED
|
@@ -90,4 +90,28 @@ export default class Print {
|
|
|
90
90
|
}
|
|
91
91
|
Print.separator();
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
// Método para mostrar resultados de minificación
|
|
95
|
+
static minificationResult(filename, originalSize, minifiedSize, savingsPercent) {
|
|
96
|
+
const originalKB = (originalSize / 1024).toFixed(1);
|
|
97
|
+
const minifiedKB = (minifiedSize / 1024).toFixed(1);
|
|
98
|
+
|
|
99
|
+
console.log('\x1b[32m', ` ✅ ${filename}`, '\x1b[0m');
|
|
100
|
+
console.log('\x1b[90m', ` ${originalKB}KB → ${minifiedKB}KB (${savingsPercent}% saved)`, '\x1b[0m');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Método para mostrar progreso de build
|
|
104
|
+
static buildProgress(message) {
|
|
105
|
+
console.log('\x1b[36m', `🔄 ${message}`, '\x1b[0m');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Método para mostrar estadísticas de servidor
|
|
109
|
+
static serverStats(mode, port, directory) {
|
|
110
|
+
Print.newLine();
|
|
111
|
+
console.log('\x1b[35m', `🌐 Server Configuration:`, '\x1b[0m');
|
|
112
|
+
console.log('\x1b[90m', ` Mode: ${mode}`, '\x1b[0m');
|
|
113
|
+
console.log('\x1b[90m', ` Port: ${port}`, '\x1b[0m');
|
|
114
|
+
console.log('\x1b[90m', ` Serving: /${directory}`, '\x1b[0m');
|
|
115
|
+
Print.newLine();
|
|
116
|
+
}
|
|
93
117
|
}
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
// commands/buildProduction/buildProduction.js
|
|
2
|
+
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { minify as terserMinify } from 'terser';
|
|
7
|
+
import CleanCSS from 'clean-css';
|
|
8
|
+
import htmlMinifier from 'html-minifier-terser';
|
|
9
|
+
import Print from '../Print.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Opciones de minificación para diferentes tipos de archivos
|
|
15
|
+
*/
|
|
16
|
+
const getMinificationOptions = () => ({
|
|
17
|
+
js: {
|
|
18
|
+
compress: {
|
|
19
|
+
dead_code: true,
|
|
20
|
+
drop_console: true, // Remover console.log en producción
|
|
21
|
+
drop_debugger: true,
|
|
22
|
+
pure_funcs: ['console.log', 'console.info', 'console.warn'],
|
|
23
|
+
passes: 2
|
|
24
|
+
},
|
|
25
|
+
mangle: {
|
|
26
|
+
toplevel: true,
|
|
27
|
+
reserved: ['Slice', 'Controller', 'StylesManager'] // Preservar clases principales
|
|
28
|
+
},
|
|
29
|
+
output: {
|
|
30
|
+
comments: false,
|
|
31
|
+
beautify: false
|
|
32
|
+
},
|
|
33
|
+
toplevel: true
|
|
34
|
+
},
|
|
35
|
+
css: {
|
|
36
|
+
level: 2, // Optimización agresiva
|
|
37
|
+
returnPromise: false
|
|
38
|
+
},
|
|
39
|
+
html: {
|
|
40
|
+
collapseWhitespace: true,
|
|
41
|
+
removeComments: true,
|
|
42
|
+
removeRedundantAttributes: true,
|
|
43
|
+
removeEmptyAttributes: true,
|
|
44
|
+
minifyCSS: true,
|
|
45
|
+
minifyJS: true,
|
|
46
|
+
useShortDoctype: true,
|
|
47
|
+
removeAttributeQuotes: true,
|
|
48
|
+
removeOptionalTags: true
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Minifica un archivo JavaScript
|
|
54
|
+
*/
|
|
55
|
+
async function minifyJavaScript(content, filename) {
|
|
56
|
+
try {
|
|
57
|
+
const options = getMinificationOptions().js;
|
|
58
|
+
const result = await terserMinify(content, options);
|
|
59
|
+
|
|
60
|
+
if (result.error) {
|
|
61
|
+
throw result.error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const originalSize = Buffer.byteLength(content, 'utf8');
|
|
65
|
+
const minifiedSize = Buffer.byteLength(result.code, 'utf8');
|
|
66
|
+
const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
|
|
67
|
+
|
|
68
|
+
Print.minificationResult(filename, originalSize, minifiedSize, savings);
|
|
69
|
+
|
|
70
|
+
return result.code;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
Print.error(`Error minifying ${filename}: ${error.message}`);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Minifica un archivo CSS
|
|
79
|
+
*/
|
|
80
|
+
async function minifyCSS(content, filename) {
|
|
81
|
+
try {
|
|
82
|
+
const cleanCSS = new CleanCSS(getMinificationOptions().css);
|
|
83
|
+
const result = cleanCSS.minify(content);
|
|
84
|
+
|
|
85
|
+
if (result.errors && result.errors.length > 0) {
|
|
86
|
+
throw new Error(result.errors.join(', '));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const originalSize = Buffer.byteLength(content, 'utf8');
|
|
90
|
+
const minifiedSize = Buffer.byteLength(result.styles, 'utf8');
|
|
91
|
+
const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
|
|
92
|
+
|
|
93
|
+
Print.minificationResult(filename, originalSize, minifiedSize, savings);
|
|
94
|
+
|
|
95
|
+
return result.styles;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
Print.error(`Error minifying ${filename}: ${error.message}`);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Minifica un archivo HTML
|
|
104
|
+
*/
|
|
105
|
+
async function minifyHTML(content, filename) {
|
|
106
|
+
try {
|
|
107
|
+
const result = await htmlMinifier.minify(content, getMinificationOptions().html);
|
|
108
|
+
|
|
109
|
+
const originalSize = Buffer.byteLength(content, 'utf8');
|
|
110
|
+
const minifiedSize = Buffer.byteLength(result, 'utf8');
|
|
111
|
+
const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
|
|
112
|
+
|
|
113
|
+
Print.minificationResult(filename, originalSize, minifiedSize, savings);
|
|
114
|
+
|
|
115
|
+
return result;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
Print.error(`Error minifying ${filename}: ${error.message}`);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Procesa un archivo según su extensión
|
|
124
|
+
*/
|
|
125
|
+
async function processFile(srcPath, destPath, relativePath) {
|
|
126
|
+
try {
|
|
127
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
128
|
+
const ext = path.extname(srcPath).toLowerCase();
|
|
129
|
+
let processedContent = content;
|
|
130
|
+
|
|
131
|
+
switch (ext) {
|
|
132
|
+
case '.js':
|
|
133
|
+
processedContent = await minifyJavaScript(content, relativePath);
|
|
134
|
+
break;
|
|
135
|
+
case '.css':
|
|
136
|
+
processedContent = await minifyCSS(content, relativePath);
|
|
137
|
+
break;
|
|
138
|
+
case '.html':
|
|
139
|
+
processedContent = await minifyHTML(content, relativePath);
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
// Para otros archivos (JSON, etc.), solo copiar
|
|
143
|
+
await fs.copy(srcPath, destPath);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await fs.writeFile(destPath, processedContent, 'utf8');
|
|
148
|
+
|
|
149
|
+
} catch (error) {
|
|
150
|
+
Print.error(`Error processing ${relativePath}: ${error.message}`);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Copia y procesa recursivamente todos los archivos de src a dist
|
|
157
|
+
*/
|
|
158
|
+
async function processDirectory(srcDir, distDir, baseSrcDir) {
|
|
159
|
+
const items = await fs.readdir(srcDir);
|
|
160
|
+
|
|
161
|
+
for (const item of items) {
|
|
162
|
+
const srcPath = path.join(srcDir, item);
|
|
163
|
+
const destPath = path.join(distDir, item);
|
|
164
|
+
const relativePath = path.relative(baseSrcDir, srcPath);
|
|
165
|
+
|
|
166
|
+
const stat = await fs.stat(srcPath);
|
|
167
|
+
|
|
168
|
+
if (stat.isDirectory()) {
|
|
169
|
+
await fs.ensureDir(destPath);
|
|
170
|
+
await processDirectory(srcPath, destPath, baseSrcDir);
|
|
171
|
+
} else {
|
|
172
|
+
const ext = path.extname(srcPath).toLowerCase();
|
|
173
|
+
|
|
174
|
+
// Procesar archivos que pueden ser minificados
|
|
175
|
+
if (['.js', '.css', '.html'].includes(ext)) {
|
|
176
|
+
await processFile(srcPath, destPath, relativePath);
|
|
177
|
+
} else {
|
|
178
|
+
// Copiar otros archivos sin modificar
|
|
179
|
+
await fs.copy(srcPath, destPath);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Crea un bundle optimizado del archivo principal Slice.js
|
|
187
|
+
*/
|
|
188
|
+
async function createOptimizedBundle() {
|
|
189
|
+
try {
|
|
190
|
+
Print.info('Creating optimized Slice.js bundle...');
|
|
191
|
+
|
|
192
|
+
const slicePath = path.join(__dirname, '../../../../src/Slice/Slice.js');
|
|
193
|
+
const distSlicePath = path.join(__dirname, '../../../../dist/Slice/Slice.js');
|
|
194
|
+
|
|
195
|
+
if (!await fs.pathExists(slicePath)) {
|
|
196
|
+
Print.warning('Slice.js main file not found, skipping bundle optimization');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const content = await fs.readFile(slicePath, 'utf8');
|
|
201
|
+
const minifiedContent = await minifyJavaScript(content, 'Slice/Slice.js');
|
|
202
|
+
|
|
203
|
+
await fs.ensureDir(path.dirname(distSlicePath));
|
|
204
|
+
await fs.writeFile(distSlicePath, minifiedContent, 'utf8');
|
|
205
|
+
|
|
206
|
+
Print.success('Optimized Slice.js bundle created');
|
|
207
|
+
|
|
208
|
+
} catch (error) {
|
|
209
|
+
Print.error(`Error creating optimized bundle: ${error.message}`);
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Copia sliceConfig.json sin modificaciones
|
|
216
|
+
*/
|
|
217
|
+
async function copySliceConfig() {
|
|
218
|
+
try {
|
|
219
|
+
const srcConfigPath = path.join(__dirname, '../../../../src/sliceConfig.json');
|
|
220
|
+
const distConfigPath = path.join(__dirname, '../../../../dist/sliceConfig.json');
|
|
221
|
+
|
|
222
|
+
if (await fs.pathExists(srcConfigPath)) {
|
|
223
|
+
await fs.copy(srcConfigPath, distConfigPath);
|
|
224
|
+
Print.success('sliceConfig.json copied to dist');
|
|
225
|
+
} else {
|
|
226
|
+
Print.warning('sliceConfig.json not found in src, skipping copy');
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
Print.error(`Error copying sliceConfig.json: ${error.message}`);
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Genera estadísticas del build
|
|
236
|
+
*/
|
|
237
|
+
async function generateBuildStats(srcDir, distDir) {
|
|
238
|
+
try {
|
|
239
|
+
Print.info('Generating build statistics...');
|
|
240
|
+
|
|
241
|
+
const calculateDirSize = async (dir) => {
|
|
242
|
+
let totalSize = 0;
|
|
243
|
+
const files = await fs.readdir(dir, { withFileTypes: true });
|
|
244
|
+
|
|
245
|
+
for (const file of files) {
|
|
246
|
+
const filePath = path.join(dir, file.name);
|
|
247
|
+
if (file.isDirectory()) {
|
|
248
|
+
totalSize += await calculateDirSize(filePath);
|
|
249
|
+
} else {
|
|
250
|
+
const stats = await fs.stat(filePath);
|
|
251
|
+
totalSize += stats.size;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return totalSize;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const srcSize = await calculateDirSize(srcDir);
|
|
258
|
+
const distSize = await calculateDirSize(distDir);
|
|
259
|
+
const savings = ((srcSize - distSize) / srcSize * 100).toFixed(1);
|
|
260
|
+
|
|
261
|
+
Print.newLine();
|
|
262
|
+
Print.title('📊 Build Statistics');
|
|
263
|
+
console.log(`📁 Source size: ${(srcSize / 1024).toFixed(1)} KB`);
|
|
264
|
+
console.log(`📦 Production size: ${(distSize / 1024).toFixed(1)} KB`);
|
|
265
|
+
console.log(`💾 Size reduction: ${savings}% saved`);
|
|
266
|
+
|
|
267
|
+
} catch (error) {
|
|
268
|
+
Print.warning(`Could not generate build statistics: ${error.message}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Función principal de build para producción
|
|
274
|
+
*/
|
|
275
|
+
export default async function buildProduction(options = {}) {
|
|
276
|
+
const startTime = Date.now();
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
Print.title('🚀 Building Slice.js project for production...');
|
|
280
|
+
Print.newLine();
|
|
281
|
+
|
|
282
|
+
// Verificar que existe src
|
|
283
|
+
const srcDir = path.join(__dirname, '../../../../src');
|
|
284
|
+
const distDir = path.join(__dirname, '../../../../dist');
|
|
285
|
+
|
|
286
|
+
if (!await fs.pathExists(srcDir)) {
|
|
287
|
+
throw new Error('src directory not found. Run "slice init" first.');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 1. Limpiar directorio dist
|
|
291
|
+
if (await fs.pathExists(distDir)) {
|
|
292
|
+
if (!options.skipClean) {
|
|
293
|
+
Print.info('Cleaning previous build...');
|
|
294
|
+
await fs.remove(distDir);
|
|
295
|
+
Print.success('Previous build cleaned');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
await fs.ensureDir(distDir);
|
|
300
|
+
|
|
301
|
+
// 2. Copiar sliceConfig.json sin modificaciones
|
|
302
|
+
await copySliceConfig();
|
|
303
|
+
|
|
304
|
+
// 3. Procesar todos los archivos de src
|
|
305
|
+
Print.info('Processing and minifying source files...');
|
|
306
|
+
await processDirectory(srcDir, distDir, srcDir);
|
|
307
|
+
Print.success('All source files processed and optimized');
|
|
308
|
+
|
|
309
|
+
// 4. Crear bundle optimizado del archivo principal
|
|
310
|
+
await createOptimizedBundle();
|
|
311
|
+
|
|
312
|
+
// 5. Generar estadísticas
|
|
313
|
+
await generateBuildStats(srcDir, distDir);
|
|
314
|
+
|
|
315
|
+
// 6. Tiempo total
|
|
316
|
+
const buildTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
317
|
+
|
|
318
|
+
Print.newLine();
|
|
319
|
+
Print.success(`✨ Production build completed in ${buildTime}s`);
|
|
320
|
+
Print.info('Your optimized project is ready in the /dist directory');
|
|
321
|
+
Print.newLine();
|
|
322
|
+
Print.info('Next steps:');
|
|
323
|
+
console.log(' • The same /api folder serves both development and production');
|
|
324
|
+
console.log(' • Update your Express server to serve from /dist instead of /src');
|
|
325
|
+
console.log(' • Deploy both /api and /dist directories to your hosting provider');
|
|
326
|
+
console.log(' • Use "slice build --serve" to preview the production build locally');
|
|
327
|
+
|
|
328
|
+
return true;
|
|
329
|
+
|
|
330
|
+
} catch (error) {
|
|
331
|
+
Print.error(`Build failed: ${error.message}`);
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Servidor de desarrollo para testing del build de producción
|
|
338
|
+
* Usa Express como el servidor principal pero sirviendo desde /dist
|
|
339
|
+
*/
|
|
340
|
+
export async function serveProductionBuild(port = 3001) {
|
|
341
|
+
try {
|
|
342
|
+
const distDir = path.join(__dirname, '../../../../dist');
|
|
343
|
+
|
|
344
|
+
if (!await fs.pathExists(distDir)) {
|
|
345
|
+
throw new Error('No production build found. Run "slice build" first.');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
Print.info(`Starting production build server on port ${port}...`);
|
|
349
|
+
|
|
350
|
+
// Implementar servidor estático simple que simula el comportamiento de la API
|
|
351
|
+
const express = await import('express');
|
|
352
|
+
const app = express.default();
|
|
353
|
+
|
|
354
|
+
// Servir archivos estáticos desde dist (equivalente a lo que hace la API con src)
|
|
355
|
+
app.use(express.default.static(distDir));
|
|
356
|
+
|
|
357
|
+
// SPA fallback - servir index.html para rutas no encontradas
|
|
358
|
+
app.get('*', (req, res) => {
|
|
359
|
+
const indexPath = path.join(distDir, 'index.html');
|
|
360
|
+
if (fs.existsSync(indexPath)) {
|
|
361
|
+
res.sendFile(indexPath);
|
|
362
|
+
} else {
|
|
363
|
+
res.status(404).send('Production build not found');
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
app.listen(port, () => {
|
|
368
|
+
Print.success(`Production build server running at http://localhost:${port}`);
|
|
369
|
+
Print.info('Press Ctrl+C to stop the server');
|
|
370
|
+
Print.info('This server simulates production environment using /dist files');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
} catch (error) {
|
|
374
|
+
Print.error(`Error starting production server: ${error.message}`);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Comando build con opciones
|
|
381
|
+
*/
|
|
382
|
+
export async function buildCommand(options = {}) {
|
|
383
|
+
// Verificar dependencias necesarias
|
|
384
|
+
if (!await checkBuildDependencies()) {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (options.serve) {
|
|
389
|
+
// Solo servir build existente
|
|
390
|
+
await serveProductionBuild(options.port);
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (options.analyze) {
|
|
395
|
+
// Analizar build sin construir
|
|
396
|
+
await analyzeBuild();
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Build completo
|
|
401
|
+
const success = await buildProduction(options);
|
|
402
|
+
|
|
403
|
+
if (success && options.preview) {
|
|
404
|
+
Print.newLine();
|
|
405
|
+
Print.info('Starting preview server...');
|
|
406
|
+
setTimeout(() => {
|
|
407
|
+
serveProductionBuild(options.port);
|
|
408
|
+
}, 1000);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return success;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Verifica que las dependencias de build estén instaladas
|
|
416
|
+
*/
|
|
417
|
+
async function checkBuildDependencies() {
|
|
418
|
+
try {
|
|
419
|
+
Print.info('Checking build dependencies...');
|
|
420
|
+
|
|
421
|
+
const packageJsonPath = path.join(__dirname, '../../../../package.json');
|
|
422
|
+
|
|
423
|
+
if (!await fs.pathExists(packageJsonPath)) {
|
|
424
|
+
throw new Error('package.json not found');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
428
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
429
|
+
|
|
430
|
+
const requiredDeps = ['terser', 'clean-css', 'html-minifier-terser'];
|
|
431
|
+
const missing = requiredDeps.filter(dep => !deps[dep]);
|
|
432
|
+
|
|
433
|
+
if (missing.length > 0) {
|
|
434
|
+
Print.error('Missing build dependencies:');
|
|
435
|
+
missing.forEach(dep => console.log(` • ${dep}`));
|
|
436
|
+
Print.newLine();
|
|
437
|
+
Print.info('Install missing dependencies:');
|
|
438
|
+
console.log(`npm install --save-dev ${missing.join(' ')}`);
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
Print.success('All build dependencies are installed');
|
|
443
|
+
return true;
|
|
444
|
+
|
|
445
|
+
} catch (error) {
|
|
446
|
+
Print.error(`Error checking dependencies: ${error.message}`);
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Analiza el tamaño y composición del build
|
|
453
|
+
*/
|
|
454
|
+
async function analyzeBuild() {
|
|
455
|
+
try {
|
|
456
|
+
const distDir = path.join(__dirname, '../../../../dist');
|
|
457
|
+
|
|
458
|
+
if (!await fs.pathExists(distDir)) {
|
|
459
|
+
throw new Error('No production build found. Run "slice build" first.');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
Print.title('📊 Build Analysis');
|
|
463
|
+
Print.newLine();
|
|
464
|
+
|
|
465
|
+
const analyzeDirectory = async (dir, prefix = '') => {
|
|
466
|
+
const items = await fs.readdir(dir);
|
|
467
|
+
let totalSize = 0;
|
|
468
|
+
|
|
469
|
+
for (const item of items) {
|
|
470
|
+
const itemPath = path.join(dir, item);
|
|
471
|
+
const stat = await fs.stat(itemPath);
|
|
472
|
+
|
|
473
|
+
if (stat.isDirectory()) {
|
|
474
|
+
const dirSize = await analyzeDirectory(itemPath, `${prefix}${item}/`);
|
|
475
|
+
totalSize += dirSize;
|
|
476
|
+
} else {
|
|
477
|
+
const size = (stat.size / 1024).toFixed(1);
|
|
478
|
+
console.log(`📄 ${prefix}${item}: ${size} KB`);
|
|
479
|
+
totalSize += stat.size;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return totalSize;
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const totalSize = await analyzeDirectory(distDir);
|
|
487
|
+
Print.newLine();
|
|
488
|
+
Print.info(`Total build size: ${(totalSize / 1024).toFixed(1)} KB`);
|
|
489
|
+
|
|
490
|
+
} catch (error) {
|
|
491
|
+
Print.error(`Error analyzing build: ${error.message}`);
|
|
492
|
+
}
|
|
493
|
+
}
|