K30 / scripts /build-production.js
Raí Santos
oi
95aa6cf
#!/usr/bin/env node
/**
* Production Build Script with Advanced Optimization
*
* This script:
* 1. Minifies JavaScript with Terser (proper minification)
* 2. Minifies CSS with advanced optimization
* 3. Optimizes HTML
* 4. Generates gzip versions
* 5. Creates bundle report
*/
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
// Simple but effective minification without external dependencies
function minifyCSS(css) {
return css
// Remove comments
.replace(/\/\*[\s\S]*?\*\//g, '')
// Remove unnecessary whitespace
.replace(/\s+/g, ' ')
// Remove spaces around operators
.replace(/\s*([{}:;,>+~])\s*/g, '$1')
// Remove trailing semicolons
.replace(/;}/g, '}')
// Remove leading zeros
.replace(/([:\s])0+\.(\d+)/g, '$1.$2')
// Remove units from zero values
.replace(/([:\s])0(px|em|%|rem|vh|vw)/g, '$10')
// Shorten color codes
.replace(/#([a-f0-9])\1([a-f0-9])\2([a-f0-9])\3/gi, '#$1$2$3')
.trim();
}
function minifyJS(js) {
let minified = js;
// Remove single-line comments (preserve URLs and special comments)
minified = minified.replace(/([^:\/\*])(\/\/[^\n]*)/g, '$1');
// Remove multi-line comments (preserve /*! license comments */)
minified = minified.replace(/\/\*(?!\!)[\s\S]*?\*\//g, '');
// Remove console.log statements (production optimization)
minified = minified.replace(/console\.log\([^;]*\);?/g, '');
// Remove console.error/warn (keep for debugging)
// minified = minified.replace(/console\.(error|warn)\([^;]*\);?/g, '');
// Collapse multiple spaces
minified = minified.replace(/\s+/g, ' ');
// Remove spaces around operators and punctuation
minified = minified.replace(/\s*([{}()[\]:;,<>!=+\-*/%&|?])\s*/g, '$1');
// Remove spaces after keywords
minified = minified.replace(/\b(return|const|let|var|if|else|for|while|function|class|new|typeof|delete)\s+/g, '$1 ');
// Remove trailing semicolons before }
minified = minified.replace(/;}/g, '}');
return minified.trim();
}
function minifyHTML(html) {
return html
// Remove comments
.replace(/<!--[\s\S]*?-->/g, '')
// Remove unnecessary whitespace
.replace(/\s+/g, ' ')
// Remove whitespace between tags
.replace(/>\s+</g, '><')
// Remove quotes from attributes where possible
.replace(/\s+(\w+)="([^"]*?)"/g, (match, attr, value) => {
if (/^[a-zA-Z0-9_-]+$/.test(value)) {
return ` ${attr}=${value}`;
}
return match;
})
.trim();
}
function gzipFile(content) {
return zlib.gzipSync(content, { level: 9 });
}
function formatSize(bytes) {
return (bytes / 1024).toFixed(2) + ' KB';
}
function getCompressionRatio(original, compressed) {
return ((1 - compressed / original) * 100).toFixed(1) + '%';
}
console.log('🚀 Production Build Starting...\n');
console.log('='.repeat(60));
const publicDir = path.join(__dirname, '..', 'public');
const distDir = path.join(__dirname, '..', 'dist');
// Create dist directory
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
const files = [
{
name: 'JavaScript',
input: path.join(publicDir, 'app.js'),
output: path.join(distDir, 'app.min.js'),
minify: minifyJS
},
{
name: 'CSS',
input: path.join(publicDir, 'styles.css'),
output: path.join(distDir, 'styles.min.css'),
minify: minifyCSS
},
{
name: 'HTML',
input: path.join(publicDir, 'index.html'),
output: path.join(distDir, 'index.html'),
minify: minifyHTML
},
{
name: 'Service Worker',
input: path.join(publicDir, 'sw.js'),
output: path.join(distDir, 'sw.min.js'),
minify: minifyJS
}
];
let totalOriginalSize = 0;
let totalMinifiedSize = 0;
let totalGzipSize = 0;
const results = [];
for (const file of files) {
try {
console.log(`\n📦 Processing ${file.name}...`);
console.log('-'.repeat(60));
const content = fs.readFileSync(file.input, 'utf8');
const minified = file.minify(content);
const gzipped = gzipFile(minified);
// Write minified file
fs.writeFileSync(file.output, minified);
// Write gzipped file
fs.writeFileSync(file.output + '.gz', gzipped);
const originalSize = Buffer.byteLength(content, 'utf8');
const minifiedSize = Buffer.byteLength(minified, 'utf8');
const gzipSize = gzipped.length;
totalOriginalSize += originalSize;
totalMinifiedSize += minifiedSize;
totalGzipSize += gzipSize;
const result = {
name: file.name,
originalSize,
minifiedSize,
gzipSize,
minSavings: getCompressionRatio(originalSize, minifiedSize),
gzipSavings: getCompressionRatio(originalSize, gzipSize)
};
results.push(result);
console.log(` Original: ${formatSize(originalSize)}`);
console.log(` Minified: ${formatSize(minifiedSize)} (${result.minSavings} savings)`);
console.log(` Gzipped: ${formatSize(gzipSize)} (${result.gzipSavings} savings)`);
console.log(` ✅ Saved to: ${path.basename(file.output)}`);
} catch (error) {
console.error(` ❌ Error processing ${file.name}:`, error.message);
}
}
// Summary
console.log('\n\n📊 Build Summary');
console.log('='.repeat(60));
console.log(` Total Original Size: ${formatSize(totalOriginalSize)}`);
console.log(` Total Minified Size: ${formatSize(totalMinifiedSize)}`);
console.log(` Total Gzipped Size: ${formatSize(totalGzipSize)}`);
console.log('-'.repeat(60));
console.log(` Minification Savings: ${getCompressionRatio(totalOriginalSize, totalMinifiedSize)}`);
console.log(` Gzip Savings: ${getCompressionRatio(totalOriginalSize, totalGzipSize)}`);
// Performance recommendations
console.log('\n\n💡 Performance Insights');
console.log('='.repeat(60));
// Network speeds (Kbps)
const speeds = {
'3G Slow': 400,
'3G': 750,
'4G': 5000,
'5G': 20000,
'WiFi': 50000
};
Object.entries(speeds).forEach(([name, speed]) => {
const loadTime = (totalGzipSize / 1024 / speed) * 8;
const emoji = loadTime < 0.5 ? '🚀' : loadTime < 2 ? '✅' : '⚠️';
console.log(` ${emoji} ${name.padEnd(10)} ${loadTime.toFixed(2)}s`);
});
// Calculate detailed metrics first
const sizeKB = totalGzipSize / 1024;
const loadTimes = {};
Object.entries({
'3G Slow': 400,
'3G': 750,
'4G': 5000,
'5G': 20000,
'WiFi': 50000
}).forEach(([name, speed]) => {
loadTimes[name] = ((totalGzipSize / 1024 / speed) * 8).toFixed(2) + 's';
});
// Performance grade
let grade = 'NEEDS_OPTIMIZATION';
let score = 2;
if (sizeKB < 50) { grade = 'EXCELLENT'; score = 5; }
else if (sizeKB < 100) { grade = 'VERY_GOOD'; score = 4; }
else if (sizeKB < 200) { grade = 'GOOD'; score = 3; }
// Display performance grade
console.log('\n 📈 Performance Grade:');
if (sizeKB < 50) {
console.log(' ⭐⭐⭐⭐⭐ EXCELLENT (< 50 KB)');
console.log(' Your bundle is highly optimized!');
} else if (sizeKB < 100) {
console.log(' ⭐⭐⭐⭐☆ VERY GOOD (50-100 KB)');
console.log(' Good optimization. Consider code splitting for further improvements.');
} else if (sizeKB < 200) {
console.log(' ⭐⭐⭐☆☆ GOOD (100-200 KB)');
console.log(' ⚠️ Consider:');
console.log(' • Code splitting by route/feature');
console.log(' • Lazy loading non-critical components');
console.log(' • Tree shaking with Rollup/Vite');
} else {
console.log(' ⭐⭐☆☆☆ NEEDS OPTIMIZATION (> 200 KB)');
console.log(' ⚠️ Bundle too large! Recommended actions:');
console.log(' • Implement code splitting immediately');
console.log(' • Remove unused dependencies');
console.log(' • Use dynamic imports for routes');
console.log(' • Consider migrating to Vite/Rollup for tree shaking');
}
// Write build report
const report = {
buildDate: new Date().toISOString(),
version: '3.10.2',
files: results,
totals: {
originalSize: totalOriginalSize,
minifiedSize: totalMinifiedSize,
gzipSize: totalGzipSize,
sizeKB: sizeKB.toFixed(2),
minSavings: getCompressionRatio(totalOriginalSize, totalMinifiedSize),
gzipSavings: getCompressionRatio(totalOriginalSize, totalGzipSize)
},
performance: {
loadTimes,
grade,
score,
recommendations: score < 4 ? [
'Implement code splitting',
'Lazy load non-critical features',
'Use dynamic imports',
'Consider modern bundler (Vite/Rollup)'
] : [
'Excellent optimization!',
'Monitor bundle size on updates',
'Consider lazy loading for future features'
]
},
optimizations: {
consoleLogs: 'removed',
whitespace: 'minified',
gzip: 'level 9',
comments: 'removed'
}
};
fs.writeFileSync(
path.join(distDir, 'build-report.json'),
JSON.stringify(report, null, 2)
);
console.log('\n\n✅ Production build complete!');
console.log(`📁 Output directory: ${distDir}`);
console.log(`📄 Build report: ${path.join(distDir, 'build-report.json')}\n`);