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