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