slicejs-cli 2.8.3 → 2.8.4

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.
@@ -1,544 +1,544 @@
1
- // commands/buildProduction/buildProduction.js - VERSIÓN LIMPIA
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 { minify } from 'html-minifier-terser';
8
- import CleanCSS from 'clean-css';
9
- import Print from '../Print.js';
10
- import { getSrcPath, getDistPath, getConfigPath } from '../utils/PathHelper.js';
11
-
12
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
-
14
- /**
15
- * Carga la configuración desde sliceConfig.json
16
- */
17
- const loadConfig = () => {
18
- try {
19
- const configPath = getConfigPath(import.meta.url);
20
- const rawData = fs.readFileSync(configPath, 'utf-8');
21
- return JSON.parse(rawData);
22
- } catch (error) {
23
- Print.error(`Loading configuration: ${error.message}`);
24
- return null;
25
- }
26
- };
27
-
28
- /**
29
- * Verifica dependencias necesarias para el build
30
- */
31
- async function checkBuildDependencies() {
32
- const srcDir = getSrcPath(import.meta.url);
33
-
34
- if (!await fs.pathExists(srcDir)) {
35
- Print.error('Source directory (/src) not found');
36
- Print.info('Run "slice init" to initialize your project');
37
- return false;
38
- }
39
-
40
- try {
41
- await import('terser');
42
- await import('clean-css');
43
- await import('html-minifier-terser');
44
- Print.success('Build dependencies available');
45
- return true;
46
- } catch (error) {
47
- Print.warning('Some build dependencies missing - using fallback copy mode');
48
- return true;
49
- }
50
- }
51
-
52
- /**
53
- * Verifica que existan los archivos críticos para Slice.js
54
- */
55
- async function verifySliceFiles(srcDir) {
56
- Print.info('Verifying Slice.js critical files...');
57
-
58
- const criticalFiles = [
59
- 'sliceConfig.json',
60
- 'Components/components.js',
61
- 'App/index.js'
62
- ];
63
-
64
- for (const file of criticalFiles) {
65
- const filePath = path.join(srcDir, file);
66
- if (!await fs.pathExists(filePath)) {
67
- throw new Error(`Critical Slice.js file missing: ${file}`);
68
- }
69
- }
70
-
71
- Print.success('All critical Slice.js files verified');
72
- }
73
-
74
- /**
75
- * Verifica la integridad del build para Slice.js
76
- */
77
- async function verifyBuildIntegrity(distDir) {
78
- Print.info('Verifying build integrity for Slice.js...');
79
-
80
- const criticalBuiltFiles = [
81
- 'sliceConfig.json',
82
- 'Components/components.js',
83
- 'App/index.js'
84
- ];
85
-
86
- for (const file of criticalBuiltFiles) {
87
- const filePath = path.join(distDir, file);
88
- if (!await fs.pathExists(filePath)) {
89
- throw new Error(`Critical built file missing: ${file}`);
90
- }
91
-
92
- if (file === 'Components/components.js') {
93
- const content = await fs.readFile(filePath, 'utf8');
94
- if (!content.includes('const components') || !content.includes('export default')) {
95
- throw new Error('components.js structure corrupted during build');
96
- }
97
- }
98
- }
99
-
100
- Print.success('Build integrity verified - all Slice.js components preserved');
101
- }
102
-
103
- /**
104
- * Copia sliceConfig.json al directorio dist
105
- */
106
- async function copySliceConfig() {
107
- const srcConfig = getConfigPath(import.meta.url);
108
- const distConfig = getDistPath(import.meta.url, 'sliceConfig.json');
109
-
110
- if (await fs.pathExists(srcConfig)) {
111
- await fs.copy(srcConfig, distConfig);
112
- Print.info('sliceConfig.json copied to dist');
113
- }
114
- }
115
-
116
- /**
117
- * Procesa un directorio completo
118
- */
119
- async function processDirectory(srcPath, distPath, baseSrcPath) {
120
- const items = await fs.readdir(srcPath);
121
-
122
- for (const item of items) {
123
- const srcItemPath = path.join(srcPath, item);
124
- const distItemPath = path.join(distPath, item);
125
- const stat = await fs.stat(srcItemPath);
126
-
127
- if (stat.isDirectory()) {
128
- await fs.ensureDir(distItemPath);
129
- await processDirectory(srcItemPath, distItemPath, baseSrcPath);
130
- } else {
131
- await processFile(srcItemPath, distItemPath);
132
- }
133
- }
134
- }
135
-
136
- /**
137
- * Procesa un archivo individual
138
- */
139
- async function processFile(srcFilePath, distFilePath) {
140
- const ext = path.extname(srcFilePath).toLowerCase();
141
- const fileName = path.basename(srcFilePath);
142
-
143
- try {
144
- if (fileName === 'components.js') {
145
- await processComponentsFile(srcFilePath, distFilePath);
146
- } else if (ext === '.js') {
147
- await minifyJavaScript(srcFilePath, distFilePath);
148
- } else if (ext === '.css') {
149
- await minifyCSS(srcFilePath, distFilePath);
150
- } else if (ext === '.html') {
151
- await minifyHTML(srcFilePath, distFilePath);
152
- } else if (fileName === 'sliceConfig.json') {
153
- await fs.copy(srcFilePath, distFilePath);
154
- Print.info(`📄 Preserved: ${fileName} (configuration file)`);
155
- } else {
156
- await fs.copy(srcFilePath, distFilePath);
157
- const stat = await fs.stat(srcFilePath);
158
- const sizeKB = (stat.size / 1024).toFixed(1);
159
- Print.info(`📄 Copied: ${fileName} (${sizeKB} KB)`);
160
- }
161
- } catch (error) {
162
- Print.error(`Processing ${fileName}: ${error.message}`);
163
- await fs.copy(srcFilePath, distFilePath);
164
- }
165
- }
166
-
167
- /**
168
- * Procesa el archivo components.js de forma especial
169
- */
170
- async function processComponentsFile(srcPath, distPath) {
171
- const content = await fs.readFile(srcPath, 'utf8');
172
- const originalSize = Buffer.byteLength(content, 'utf8');
173
-
174
- const result = await terserMinify(content, {
175
- compress: false,
176
- mangle: false,
177
- format: {
178
- comments: false,
179
- beautify: false,
180
- indent_level: 0
181
- }
182
- });
183
-
184
- if (result.error) {
185
- throw new Error(`Terser error in components.js: ${result.error}`);
186
- }
187
-
188
- await fs.writeFile(distPath, result.code, 'utf8');
189
-
190
- const minifiedSize = Buffer.byteLength(result.code, 'utf8');
191
- const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
192
-
193
- Print.minificationResult(`${path.basename(srcPath)} (preserved structure)`, originalSize, minifiedSize, savings);
194
- }
195
-
196
- /**
197
- * Minifica archivos JavaScript preservando la arquitectura de Slice.js
198
- */
199
- async function minifyJavaScript(srcPath, distPath) {
200
- const content = await fs.readFile(srcPath, 'utf8');
201
- const originalSize = Buffer.byteLength(content, 'utf8');
202
-
203
- const result = await terserMinify(content, {
204
- compress: {
205
- drop_console: false,
206
- drop_debugger: true,
207
- pure_funcs: [],
208
- passes: 1,
209
- unused: false,
210
- side_effects: false,
211
- reduce_vars: false,
212
- collapse_vars: false
213
- },
214
- mangle: {
215
- reserved: [
216
- // Core Slice
217
- 'slice', 'Slice', 'SliceJS', 'window', 'document',
218
- // Clases principales
219
- 'Controller', 'StylesManager', 'Router', 'Logger', 'Debugger',
220
- // Métodos de Slice
221
- 'getClass', 'isProduction', 'getComponent', 'build', 'setTheme', 'attachTemplate',
222
- // Controller
223
- 'componentCategories', 'templates', 'classes', 'requestedStyles', 'activeComponents',
224
- 'registerComponent', 'registerComponentsRecursively', 'loadTemplateToComponent',
225
- 'fetchText', 'setComponentProps', 'verifyComponentIds', 'destroyComponent',
226
- // StylesManager
227
- 'componentStyles', 'themeManager', 'init', 'appendComponentStyles', 'registerComponentStyles',
228
- // Router
229
- 'routes', 'pathToRouteMap', 'activeRoute', 'navigate', 'matchRoute', 'handleRoute',
230
- 'onRouteChange', 'loadInitialRoute', 'renderRoutesComponentsInPage',
231
- // Propiedades de componentes
232
- 'sliceId', 'sliceType', 'sliceConfig', 'debuggerProps', 'parentComponent',
233
- 'value', 'customColor', 'icon', 'layout', 'view', 'items', 'columns', 'rows',
234
- 'onClickCallback', 'props',
235
- // Custom Elements
236
- 'customElements', 'define', 'HTMLElement',
237
- // DOM APIs críticas
238
- 'addEventListener', 'removeEventListener', 'querySelector', 'querySelectorAll',
239
- 'appendChild', 'removeChild', 'innerHTML', 'textContent', 'style', 'classList',
240
- // Lifecycle
241
- 'beforeMount', 'afterMount', 'beforeDestroy', 'afterDestroy',
242
- 'mount', 'unmount', 'destroy', 'update', 'start', 'stop',
243
- // Browser APIs
244
- 'fetch', 'setTimeout', 'clearTimeout', 'localStorage', 'history', 'pushState',
245
- // Exports/Imports
246
- 'default', 'export', 'import', 'from', 'await', 'async',
247
- // Nombres de componentes
248
- 'Button', 'Grid', 'Layout', 'HomePage', 'NotFound', 'Loading', 'TreeView', 'Link',
249
- 'FetchManager', 'Translator'
250
- ],
251
- properties: {
252
- regex: /^(slice|_|\$|on[A-Z]|get|set|has|is)/
253
- }
254
- },
255
- format: {
256
- comments: false,
257
- beautify: false
258
- },
259
- keep_fnames: true,
260
- keep_classnames: true
261
- });
262
-
263
- if (result.error) {
264
- throw new Error(`Terser error: ${result.error}`);
265
- }
266
-
267
- await fs.writeFile(distPath, result.code, 'utf8');
268
-
269
- const minifiedSize = Buffer.byteLength(result.code, 'utf8');
270
- const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
271
-
272
- Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
273
- }
274
-
275
- /**
276
- * Minifica archivos CSS
277
- */
278
- async function minifyCSS(srcPath, distPath) {
279
- const content = await fs.readFile(srcPath, 'utf8');
280
- const originalSize = Buffer.byteLength(content, 'utf8');
281
-
282
- const cleanCSS = new CleanCSS({
283
- level: 2,
284
- returnPromise: false
285
- });
286
-
287
- const result = cleanCSS.minify(content);
288
-
289
- if (result.errors.length > 0) {
290
- throw new Error(`CleanCSS errors: ${result.errors.join(', ')}`);
291
- }
292
-
293
- await fs.writeFile(distPath, result.styles, 'utf8');
294
-
295
- const minifiedSize = Buffer.byteLength(result.styles, 'utf8');
296
- const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
297
-
298
- Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
299
- }
300
-
301
- /**
302
- * Minifica archivos HTML
303
- */
304
- async function minifyHTML(srcPath, distPath) {
305
- const content = await fs.readFile(srcPath, 'utf8');
306
- const originalSize = Buffer.byteLength(content, 'utf8');
307
-
308
- const minified = await minify(content, {
309
- collapseWhitespace: true,
310
- removeComments: true,
311
- removeRedundantAttributes: true,
312
- removeScriptTypeAttributes: true,
313
- removeStyleLinkTypeAttributes: true,
314
- useShortDoctype: true,
315
- minifyCSS: true,
316
- minifyJS: {
317
- mangle: {
318
- reserved: ['slice', 'Slice', 'SliceJS', 'sliceId', 'sliceConfig']
319
- }
320
- },
321
- ignoreCustomFragments: [
322
- /slice-[\w-]+="[^"]*"/g
323
- ]
324
- });
325
-
326
- await fs.writeFile(distPath, minified, 'utf8');
327
-
328
- const minifiedSize = Buffer.byteLength(minified, 'utf8');
329
- const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
330
-
331
- Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
332
- }
333
-
334
- /**
335
- * Crea un bundle optimizado del archivo principal
336
- */
337
- async function createOptimizedBundle() {
338
- Print.buildProgress('Creating optimized bundle...');
339
-
340
- const mainJSPath = getDistPath(import.meta.url, 'App', 'index.js');
341
-
342
- if (await fs.pathExists(mainJSPath)) {
343
- Print.success('Main bundle optimized');
344
- } else {
345
- Print.warning('No main JavaScript file found for bundling');
346
- }
347
- }
348
-
349
- /**
350
- * Genera estadísticas del build
351
- */
352
- async function generateBuildStats(srcDir, distDir) {
353
- Print.buildProgress('Generating build statistics...');
354
-
355
- const getDirectorySize = async (dirPath) => {
356
- let totalSize = 0;
357
- const items = await fs.readdir(dirPath);
358
-
359
- for (const item of items) {
360
- const itemPath = path.join(dirPath, item);
361
- const stat = await fs.stat(itemPath);
362
-
363
- if (stat.isDirectory()) {
364
- totalSize += await getDirectorySize(itemPath);
365
- } else {
366
- totalSize += stat.size;
367
- }
368
- }
369
-
370
- return totalSize;
371
- };
372
-
373
- try {
374
- const srcSize = await getDirectorySize(srcDir);
375
- const distSize = await getDirectorySize(distDir);
376
- const savings = Math.round(((srcSize - distSize) / srcSize) * 100);
377
-
378
- Print.newLine();
379
- Print.info(`📊 Build Statistics:`);
380
- console.log(` Source: ${(srcSize / 1024).toFixed(1)} KB`);
381
- console.log(` Built: ${(distSize / 1024).toFixed(1)} KB`);
382
- console.log(` Saved: ${savings}% smaller`);
383
-
384
- } catch (error) {
385
- Print.warning('Could not generate build statistics');
386
- }
387
- }
388
-
389
- /**
390
- * Analiza el build sin construir
391
- */
392
- async function analyzeBuild() {
393
- const distDir = getDistPath(import.meta.url);
394
-
395
- if (!await fs.pathExists(distDir)) {
396
- Print.error('No build found to analyze. Run "slice build" first.');
397
- return;
398
- }
399
-
400
- Print.info('Analyzing production build...');
401
- await generateBuildStats(
402
- getSrcPath(import.meta.url),
403
- distDir
404
- );
405
- }
406
-
407
- /**
408
- * FUNCIÓN PRINCIPAL DE BUILD
409
- */
410
- export default async function buildProduction(options = {}) {
411
- const startTime = Date.now();
412
-
413
- try {
414
- Print.title('🔨 Building Slice.js project for production...');
415
- Print.newLine();
416
-
417
- const srcDir = getSrcPath(import.meta.url);
418
- const distDir = getDistPath(import.meta.url);
419
-
420
- if (!await fs.pathExists(srcDir)) {
421
- throw new Error('Source directory not found. Run "slice init" first.');
422
- }
423
-
424
- await verifySliceFiles(srcDir);
425
-
426
- // Limpiar directorio dist
427
- if (await fs.pathExists(distDir)) {
428
- if (!options.skipClean) {
429
- Print.info('Cleaning previous build...');
430
- await fs.remove(distDir);
431
- Print.success('Previous build cleaned');
432
- }
433
- }
434
-
435
- await fs.ensureDir(distDir);
436
- await copySliceConfig();
437
-
438
- // Procesar archivos
439
- Print.info('Processing and optimizing source files for Slice.js...');
440
- await processDirectory(srcDir, distDir, srcDir);
441
- Print.success('All source files processed and optimized');
442
-
443
- await verifyBuildIntegrity(distDir);
444
- await createOptimizedBundle();
445
- await generateBuildStats(srcDir, distDir);
446
-
447
- const buildTime = ((Date.now() - startTime) / 1000).toFixed(1);
448
-
449
- Print.newLine();
450
- Print.success(`✨ Slice.js production build completed in ${buildTime}s`);
451
- Print.info('Your optimized project is ready in the /dist directory');
452
- Print.newLine();
453
- Print.info('Next steps:');
454
- console.log(' • Use "npm run slice:start" to test the production build');
455
- console.log(' • All Slice.js components and architecture preserved');
456
-
457
- return true;
458
-
459
- } catch (error) {
460
- Print.error(`Build failed: ${error.message}`);
461
- return false;
462
- }
463
- }
464
-
465
- /**
466
- * Servidor de preview para testing del build de producción
467
- */
468
- export async function serveProductionBuild(port) {
469
- try {
470
- const config = loadConfig();
471
- const defaultPort = config?.server?.port || 3001;
472
- const finalPort = port || defaultPort;
473
-
474
- const distDir = getDistPath(import.meta.url);
475
-
476
- if (!await fs.pathExists(distDir)) {
477
- throw new Error('No production build found. Run "slice build" first.');
478
- }
479
-
480
- Print.info(`Starting production preview server on port ${finalPort}...`);
481
-
482
- const express = await import('express');
483
- const app = express.default();
484
-
485
- app.use(express.default.static(distDir));
486
-
487
- app.get('*', (req, res) => {
488
- const indexPath = path.join(distDir, 'App/index.html');
489
- const fallbackPath = path.join(distDir, 'index.html');
490
-
491
- if (fs.existsSync(indexPath)) {
492
- res.sendFile(indexPath);
493
- } else if (fs.existsSync(fallbackPath)) {
494
- res.sendFile(fallbackPath);
495
- } else {
496
- res.status(404).send('Production build index.html not found');
497
- }
498
- });
499
-
500
- app.listen(finalPort, () => {
501
- Print.success(`Production preview server running at http://localhost:${finalPort}`);
502
- Print.info('Press Ctrl+C to stop the server');
503
- Print.info('This server previews your production build from /dist');
504
- Print.warning('This is a preview server - use "npm run slice:start" for the full production server');
505
- });
506
-
507
- } catch (error) {
508
- Print.error(`Error starting production preview server: ${error.message}`);
509
- throw error;
510
- }
511
- }
512
-
513
- /**
514
- * Comando build con opciones
515
- */
516
- export async function buildCommand(options = {}) {
517
- const config = loadConfig();
518
- const defaultPort = config?.server?.port || 3001;
519
-
520
- if (!await checkBuildDependencies()) {
521
- return false;
522
- }
523
-
524
- if (options.serve) {
525
- await serveProductionBuild(options.port || defaultPort);
526
- return true;
527
- }
528
-
529
- if (options.analyze) {
530
- await analyzeBuild();
531
- return true;
532
- }
533
-
534
- const success = await buildProduction(options);
535
-
536
- if (success && options.preview) {
537
- Print.newLine();
538
- Print.info('✨ Build completed successfully!');
539
- Print.info(`Starting preview server on port ${options.port || defaultPort}...`);
540
- await serveProductionBuild(options.port || defaultPort);
541
- }
542
-
543
- return success;
544
- }
1
+ // commands/buildProduction/buildProduction.js - VERSIÓN LIMPIA
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 { minify } from 'html-minifier-terser';
8
+ import CleanCSS from 'clean-css';
9
+ import Print from '../Print.js';
10
+ import { getSrcPath, getDistPath, getConfigPath } from '../utils/PathHelper.js';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+
14
+ /**
15
+ * Carga la configuración desde sliceConfig.json
16
+ */
17
+ const loadConfig = () => {
18
+ try {
19
+ const configPath = getConfigPath(import.meta.url);
20
+ const rawData = fs.readFileSync(configPath, 'utf-8');
21
+ return JSON.parse(rawData);
22
+ } catch (error) {
23
+ Print.error(`Loading configuration: ${error.message}`);
24
+ return null;
25
+ }
26
+ };
27
+
28
+ /**
29
+ * Verifica dependencias necesarias para el build
30
+ */
31
+ async function checkBuildDependencies() {
32
+ const srcDir = getSrcPath(import.meta.url);
33
+
34
+ if (!await fs.pathExists(srcDir)) {
35
+ Print.error('Source directory (/src) not found');
36
+ Print.info('Run "slice init" to initialize your project');
37
+ return false;
38
+ }
39
+
40
+ try {
41
+ await import('terser');
42
+ await import('clean-css');
43
+ await import('html-minifier-terser');
44
+ Print.success('Build dependencies available');
45
+ return true;
46
+ } catch (error) {
47
+ Print.warning('Some build dependencies missing - using fallback copy mode');
48
+ return true;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Verifica que existan los archivos críticos para Slice.js
54
+ */
55
+ async function verifySliceFiles(srcDir) {
56
+ Print.info('Verifying Slice.js critical files...');
57
+
58
+ const criticalFiles = [
59
+ 'sliceConfig.json',
60
+ 'Components/components.js',
61
+ 'App/index.js'
62
+ ];
63
+
64
+ for (const file of criticalFiles) {
65
+ const filePath = path.join(srcDir, file);
66
+ if (!await fs.pathExists(filePath)) {
67
+ throw new Error(`Critical Slice.js file missing: ${file}`);
68
+ }
69
+ }
70
+
71
+ Print.success('All critical Slice.js files verified');
72
+ }
73
+
74
+ /**
75
+ * Verifica la integridad del build para Slice.js
76
+ */
77
+ async function verifyBuildIntegrity(distDir) {
78
+ Print.info('Verifying build integrity for Slice.js...');
79
+
80
+ const criticalBuiltFiles = [
81
+ 'sliceConfig.json',
82
+ 'Components/components.js',
83
+ 'App/index.js'
84
+ ];
85
+
86
+ for (const file of criticalBuiltFiles) {
87
+ const filePath = path.join(distDir, file);
88
+ if (!await fs.pathExists(filePath)) {
89
+ throw new Error(`Critical built file missing: ${file}`);
90
+ }
91
+
92
+ if (file === 'Components/components.js') {
93
+ const content = await fs.readFile(filePath, 'utf8');
94
+ if (!content.includes('const components') || !content.includes('export default')) {
95
+ throw new Error('components.js structure corrupted during build');
96
+ }
97
+ }
98
+ }
99
+
100
+ Print.success('Build integrity verified - all Slice.js components preserved');
101
+ }
102
+
103
+ /**
104
+ * Copia sliceConfig.json al directorio dist
105
+ */
106
+ async function copySliceConfig() {
107
+ const srcConfig = getConfigPath(import.meta.url);
108
+ const distConfig = getDistPath(import.meta.url, 'sliceConfig.json');
109
+
110
+ if (await fs.pathExists(srcConfig)) {
111
+ await fs.copy(srcConfig, distConfig);
112
+ Print.info('sliceConfig.json copied to dist');
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Procesa un directorio completo
118
+ */
119
+ async function processDirectory(srcPath, distPath, baseSrcPath) {
120
+ const items = await fs.readdir(srcPath);
121
+
122
+ for (const item of items) {
123
+ const srcItemPath = path.join(srcPath, item);
124
+ const distItemPath = path.join(distPath, item);
125
+ const stat = await fs.stat(srcItemPath);
126
+
127
+ if (stat.isDirectory()) {
128
+ await fs.ensureDir(distItemPath);
129
+ await processDirectory(srcItemPath, distItemPath, baseSrcPath);
130
+ } else {
131
+ await processFile(srcItemPath, distItemPath);
132
+ }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Procesa un archivo individual
138
+ */
139
+ async function processFile(srcFilePath, distFilePath) {
140
+ const ext = path.extname(srcFilePath).toLowerCase();
141
+ const fileName = path.basename(srcFilePath);
142
+
143
+ try {
144
+ if (fileName === 'components.js') {
145
+ await processComponentsFile(srcFilePath, distFilePath);
146
+ } else if (ext === '.js') {
147
+ await minifyJavaScript(srcFilePath, distFilePath);
148
+ } else if (ext === '.css') {
149
+ await minifyCSS(srcFilePath, distFilePath);
150
+ } else if (ext === '.html') {
151
+ await minifyHTML(srcFilePath, distFilePath);
152
+ } else if (fileName === 'sliceConfig.json') {
153
+ await fs.copy(srcFilePath, distFilePath);
154
+ Print.info(`📄 Preserved: ${fileName} (configuration file)`);
155
+ } else {
156
+ await fs.copy(srcFilePath, distFilePath);
157
+ const stat = await fs.stat(srcFilePath);
158
+ const sizeKB = (stat.size / 1024).toFixed(1);
159
+ Print.info(`📄 Copied: ${fileName} (${sizeKB} KB)`);
160
+ }
161
+ } catch (error) {
162
+ Print.error(`Processing ${fileName}: ${error.message}`);
163
+ await fs.copy(srcFilePath, distFilePath);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Procesa el archivo components.js de forma especial
169
+ */
170
+ async function processComponentsFile(srcPath, distPath) {
171
+ const content = await fs.readFile(srcPath, 'utf8');
172
+ const originalSize = Buffer.byteLength(content, 'utf8');
173
+
174
+ const result = await terserMinify(content, {
175
+ compress: false,
176
+ mangle: false,
177
+ format: {
178
+ comments: false,
179
+ beautify: false,
180
+ indent_level: 0
181
+ }
182
+ });
183
+
184
+ if (result.error) {
185
+ throw new Error(`Terser error in components.js: ${result.error}`);
186
+ }
187
+
188
+ await fs.writeFile(distPath, result.code, 'utf8');
189
+
190
+ const minifiedSize = Buffer.byteLength(result.code, 'utf8');
191
+ const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
192
+
193
+ Print.minificationResult(`${path.basename(srcPath)} (preserved structure)`, originalSize, minifiedSize, savings);
194
+ }
195
+
196
+ /**
197
+ * Minifica archivos JavaScript preservando la arquitectura de Slice.js
198
+ */
199
+ async function minifyJavaScript(srcPath, distPath) {
200
+ const content = await fs.readFile(srcPath, 'utf8');
201
+ const originalSize = Buffer.byteLength(content, 'utf8');
202
+
203
+ const result = await terserMinify(content, {
204
+ compress: {
205
+ drop_console: false,
206
+ drop_debugger: true,
207
+ pure_funcs: [],
208
+ passes: 1,
209
+ unused: false,
210
+ side_effects: false,
211
+ reduce_vars: false,
212
+ collapse_vars: false
213
+ },
214
+ mangle: {
215
+ reserved: [
216
+ // Core Slice
217
+ 'slice', 'Slice', 'SliceJS', 'window', 'document',
218
+ // Clases principales
219
+ 'Controller', 'StylesManager', 'Router', 'Logger', 'Debugger',
220
+ // Métodos de Slice
221
+ 'getClass', 'isProduction', 'getComponent', 'build', 'setTheme', 'attachTemplate',
222
+ // Controller
223
+ 'componentCategories', 'templates', 'classes', 'requestedStyles', 'activeComponents',
224
+ 'registerComponent', 'registerComponentsRecursively', 'loadTemplateToComponent',
225
+ 'fetchText', 'setComponentProps', 'verifyComponentIds', 'destroyComponent',
226
+ // StylesManager
227
+ 'componentStyles', 'themeManager', 'init', 'appendComponentStyles', 'registerComponentStyles',
228
+ // Router
229
+ 'routes', 'pathToRouteMap', 'activeRoute', 'navigate', 'matchRoute', 'handleRoute',
230
+ 'onRouteChange', 'loadInitialRoute', 'renderRoutesComponentsInPage',
231
+ // Propiedades de componentes
232
+ 'sliceId', 'sliceType', 'sliceConfig', 'debuggerProps', 'parentComponent',
233
+ 'value', 'customColor', 'icon', 'layout', 'view', 'items', 'columns', 'rows',
234
+ 'onClickCallback', 'props',
235
+ // Custom Elements
236
+ 'customElements', 'define', 'HTMLElement',
237
+ // DOM APIs críticas
238
+ 'addEventListener', 'removeEventListener', 'querySelector', 'querySelectorAll',
239
+ 'appendChild', 'removeChild', 'innerHTML', 'textContent', 'style', 'classList',
240
+ // Lifecycle
241
+ 'beforeMount', 'afterMount', 'beforeDestroy', 'afterDestroy',
242
+ 'mount', 'unmount', 'destroy', 'update', 'start', 'stop',
243
+ // Browser APIs
244
+ 'fetch', 'setTimeout', 'clearTimeout', 'localStorage', 'history', 'pushState',
245
+ // Exports/Imports
246
+ 'default', 'export', 'import', 'from', 'await', 'async',
247
+ // Nombres de componentes
248
+ 'Button', 'Grid', 'Layout', 'HomePage', 'NotFound', 'Loading', 'TreeView', 'Link',
249
+ 'FetchManager'
250
+ ],
251
+ properties: {
252
+ regex: /^(slice|_|\$|on[A-Z]|get|set|has|is)/
253
+ }
254
+ },
255
+ format: {
256
+ comments: false,
257
+ beautify: false
258
+ },
259
+ keep_fnames: true,
260
+ keep_classnames: true
261
+ });
262
+
263
+ if (result.error) {
264
+ throw new Error(`Terser error: ${result.error}`);
265
+ }
266
+
267
+ await fs.writeFile(distPath, result.code, 'utf8');
268
+
269
+ const minifiedSize = Buffer.byteLength(result.code, 'utf8');
270
+ const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
271
+
272
+ Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
273
+ }
274
+
275
+ /**
276
+ * Minifica archivos CSS
277
+ */
278
+ async function minifyCSS(srcPath, distPath) {
279
+ const content = await fs.readFile(srcPath, 'utf8');
280
+ const originalSize = Buffer.byteLength(content, 'utf8');
281
+
282
+ const cleanCSS = new CleanCSS({
283
+ level: 2,
284
+ returnPromise: false
285
+ });
286
+
287
+ const result = cleanCSS.minify(content);
288
+
289
+ if (result.errors.length > 0) {
290
+ throw new Error(`CleanCSS errors: ${result.errors.join(', ')}`);
291
+ }
292
+
293
+ await fs.writeFile(distPath, result.styles, 'utf8');
294
+
295
+ const minifiedSize = Buffer.byteLength(result.styles, 'utf8');
296
+ const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
297
+
298
+ Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
299
+ }
300
+
301
+ /**
302
+ * Minifica archivos HTML
303
+ */
304
+ async function minifyHTML(srcPath, distPath) {
305
+ const content = await fs.readFile(srcPath, 'utf8');
306
+ const originalSize = Buffer.byteLength(content, 'utf8');
307
+
308
+ const minified = await minify(content, {
309
+ collapseWhitespace: true,
310
+ removeComments: true,
311
+ removeRedundantAttributes: true,
312
+ removeScriptTypeAttributes: true,
313
+ removeStyleLinkTypeAttributes: true,
314
+ useShortDoctype: true,
315
+ minifyCSS: true,
316
+ minifyJS: {
317
+ mangle: {
318
+ reserved: ['slice', 'Slice', 'SliceJS', 'sliceId', 'sliceConfig']
319
+ }
320
+ },
321
+ ignoreCustomFragments: [
322
+ /slice-[\w-]+="[^"]*"/g
323
+ ]
324
+ });
325
+
326
+ await fs.writeFile(distPath, minified, 'utf8');
327
+
328
+ const minifiedSize = Buffer.byteLength(minified, 'utf8');
329
+ const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
330
+
331
+ Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
332
+ }
333
+
334
+ /**
335
+ * Crea un bundle optimizado del archivo principal
336
+ */
337
+ async function createOptimizedBundle() {
338
+ Print.buildProgress('Creating optimized bundle...');
339
+
340
+ const mainJSPath = getDistPath(import.meta.url, 'App', 'index.js');
341
+
342
+ if (await fs.pathExists(mainJSPath)) {
343
+ Print.success('Main bundle optimized');
344
+ } else {
345
+ Print.warning('No main JavaScript file found for bundling');
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Genera estadísticas del build
351
+ */
352
+ async function generateBuildStats(srcDir, distDir) {
353
+ Print.buildProgress('Generating build statistics...');
354
+
355
+ const getDirectorySize = async (dirPath) => {
356
+ let totalSize = 0;
357
+ const items = await fs.readdir(dirPath);
358
+
359
+ for (const item of items) {
360
+ const itemPath = path.join(dirPath, item);
361
+ const stat = await fs.stat(itemPath);
362
+
363
+ if (stat.isDirectory()) {
364
+ totalSize += await getDirectorySize(itemPath);
365
+ } else {
366
+ totalSize += stat.size;
367
+ }
368
+ }
369
+
370
+ return totalSize;
371
+ };
372
+
373
+ try {
374
+ const srcSize = await getDirectorySize(srcDir);
375
+ const distSize = await getDirectorySize(distDir);
376
+ const savings = Math.round(((srcSize - distSize) / srcSize) * 100);
377
+
378
+ Print.newLine();
379
+ Print.info(`📊 Build Statistics:`);
380
+ console.log(` Source: ${(srcSize / 1024).toFixed(1)} KB`);
381
+ console.log(` Built: ${(distSize / 1024).toFixed(1)} KB`);
382
+ console.log(` Saved: ${savings}% smaller`);
383
+
384
+ } catch (error) {
385
+ Print.warning('Could not generate build statistics');
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Analiza el build sin construir
391
+ */
392
+ async function analyzeBuild() {
393
+ const distDir = getDistPath(import.meta.url);
394
+
395
+ if (!await fs.pathExists(distDir)) {
396
+ Print.error('No build found to analyze. Run "slice build" first.');
397
+ return;
398
+ }
399
+
400
+ Print.info('Analyzing production build...');
401
+ await generateBuildStats(
402
+ getSrcPath(import.meta.url),
403
+ distDir
404
+ );
405
+ }
406
+
407
+ /**
408
+ * FUNCIÓN PRINCIPAL DE BUILD
409
+ */
410
+ export default async function buildProduction(options = {}) {
411
+ const startTime = Date.now();
412
+
413
+ try {
414
+ Print.title('🔨 Building Slice.js project for production...');
415
+ Print.newLine();
416
+
417
+ const srcDir = getSrcPath(import.meta.url);
418
+ const distDir = getDistPath(import.meta.url);
419
+
420
+ if (!await fs.pathExists(srcDir)) {
421
+ throw new Error('Source directory not found. Run "slice init" first.');
422
+ }
423
+
424
+ await verifySliceFiles(srcDir);
425
+
426
+ // Limpiar directorio dist
427
+ if (await fs.pathExists(distDir)) {
428
+ if (!options.skipClean) {
429
+ Print.info('Cleaning previous build...');
430
+ await fs.remove(distDir);
431
+ Print.success('Previous build cleaned');
432
+ }
433
+ }
434
+
435
+ await fs.ensureDir(distDir);
436
+ await copySliceConfig();
437
+
438
+ // Procesar archivos
439
+ Print.info('Processing and optimizing source files for Slice.js...');
440
+ await processDirectory(srcDir, distDir, srcDir);
441
+ Print.success('All source files processed and optimized');
442
+
443
+ await verifyBuildIntegrity(distDir);
444
+ await createOptimizedBundle();
445
+ await generateBuildStats(srcDir, distDir);
446
+
447
+ const buildTime = ((Date.now() - startTime) / 1000).toFixed(1);
448
+
449
+ Print.newLine();
450
+ Print.success(`✨ Slice.js production build completed in ${buildTime}s`);
451
+ Print.info('Your optimized project is ready in the /dist directory');
452
+ Print.newLine();
453
+ Print.info('Next steps:');
454
+ console.log(' • Use "npm run slice:start" to test the production build');
455
+ console.log(' • All Slice.js components and architecture preserved');
456
+
457
+ return true;
458
+
459
+ } catch (error) {
460
+ Print.error(`Build failed: ${error.message}`);
461
+ return false;
462
+ }
463
+ }
464
+
465
+ /**
466
+ * Servidor de preview para testing del build de producción
467
+ */
468
+ export async function serveProductionBuild(port) {
469
+ try {
470
+ const config = loadConfig();
471
+ const defaultPort = config?.server?.port || 3001;
472
+ const finalPort = port || defaultPort;
473
+
474
+ const distDir = getDistPath(import.meta.url);
475
+
476
+ if (!await fs.pathExists(distDir)) {
477
+ throw new Error('No production build found. Run "slice build" first.');
478
+ }
479
+
480
+ Print.info(`Starting production preview server on port ${finalPort}...`);
481
+
482
+ const express = await import('express');
483
+ const app = express.default();
484
+
485
+ app.use(express.default.static(distDir));
486
+
487
+ app.get('*', (req, res) => {
488
+ const indexPath = path.join(distDir, 'App/index.html');
489
+ const fallbackPath = path.join(distDir, 'index.html');
490
+
491
+ if (fs.existsSync(indexPath)) {
492
+ res.sendFile(indexPath);
493
+ } else if (fs.existsSync(fallbackPath)) {
494
+ res.sendFile(fallbackPath);
495
+ } else {
496
+ res.status(404).send('Production build index.html not found');
497
+ }
498
+ });
499
+
500
+ app.listen(finalPort, () => {
501
+ Print.success(`Production preview server running at http://localhost:${finalPort}`);
502
+ Print.info('Press Ctrl+C to stop the server');
503
+ Print.info('This server previews your production build from /dist');
504
+ Print.warning('This is a preview server - use "npm run slice:start" for the full production server');
505
+ });
506
+
507
+ } catch (error) {
508
+ Print.error(`Error starting production preview server: ${error.message}`);
509
+ throw error;
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Comando build con opciones
515
+ */
516
+ export async function buildCommand(options = {}) {
517
+ const config = loadConfig();
518
+ const defaultPort = config?.server?.port || 3001;
519
+
520
+ if (!await checkBuildDependencies()) {
521
+ return false;
522
+ }
523
+
524
+ if (options.serve) {
525
+ await serveProductionBuild(options.port || defaultPort);
526
+ return true;
527
+ }
528
+
529
+ if (options.analyze) {
530
+ await analyzeBuild();
531
+ return true;
532
+ }
533
+
534
+ const success = await buildProduction(options);
535
+
536
+ if (success && options.preview) {
537
+ Print.newLine();
538
+ Print.info('✨ Build completed successfully!');
539
+ Print.info(`Starting preview server on port ${options.port || defaultPort}...`);
540
+ await serveProductionBuild(options.port || defaultPort);
541
+ }
542
+
543
+ return success;
544
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-cli",
3
- "version": "2.8.3",
3
+ "version": "2.8.4",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "bin": {