/** * Bundle 分析脚本 * * 自动化 bundle 分析流程: * 1. 构建生产版本 * 2. 生成分析报告 * 3. 输出关键指标 * * 使用: node scripts/analyze-bundle.js */ import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; // ANSI 颜色代码 const colors = { reset: '\x1b[0m', bright: '\x1b[1m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', red: '\x1b[31m', }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function formatBytes(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; } async function main() { log('\n🔍 Bundle 分析工具\n', 'bright'); // 步骤 1: 清理旧的构建产物 log('📦 清理旧的构建产物...', 'blue'); try { if (fs.existsSync('dist')) { fs.rmSync('dist', { recursive: true, force: true }); } log('✅ 清理完成\n', 'green'); } catch (error) { log(`❌ 清理失败: ${error.message}\n`, 'red'); } // 步骤 2: 构建生产版本 log('🏗️ 构建生产版本...', 'blue'); try { execSync('npm run build', { stdio: 'inherit', env: { ...process.env, ANALYZE: 'true' } }); log('✅ 构建完成\n', 'green'); } catch (error) { log('❌ 构建失败,请检查错误信息\n', 'red'); process.exit(1); } // 步骤 3: 分析构建产物 log('📊 分析构建产物...\n', 'blue'); const distPath = path.join(process.cwd(), 'dist', 'assets'); if (!fs.existsSync(distPath)) { log('❌ dist/assets 目录不存在\n', 'red'); return; } const files = fs.readdirSync(distPath); // 分类统计 const stats = { js: [], css: [], other: [], }; files.forEach((file) => { const filePath = path.join(distPath, file); const stat = fs.statSync(filePath); const size = stat.size; const fileInfo = { name: file, size, sizeFormatted: formatBytes(size), }; if (file.endsWith('.js')) { stats.js.push(fileInfo); } else if (file.endsWith('.css')) { stats.css.push(fileInfo); } else { stats.other.push(fileInfo); } }); // 排序(按大小降序) stats.js.sort((a, b) => b.size - a.size); stats.css.sort((a, b) => b.size - a.size); // 输出 JS 文件统计 log('JavaScript 文件:', 'bright'); stats.js.forEach((file, index) => { const color = file.size > 600 * 1024 ? 'red' : file.size > 200 * 1024 ? 'yellow' : 'green'; const icon = file.size > 600 * 1024 ? '⚠️ ' : ' '; log(`${icon}${index + 1}. ${file.name.padEnd(50)} ${file.sizeFormatted}`, color); }); const totalJS = stats.js.reduce((sum, file) => sum + file.size, 0); log(`\n总计: ${formatBytes(totalJS)}\n`, 'bright'); // 输出 CSS 文件统计 log('CSS 文件:', 'bright'); stats.css.forEach((file, index) => { log(` ${index + 1}. ${file.name.padEnd(50)} ${file.sizeFormatted}`, 'green'); }); const totalCSS = stats.css.reduce((sum, file) => sum + file.size, 0); log(`\n总计: ${formatBytes(totalCSS)}\n`, 'bright'); // 总体统计 const total = totalJS + totalCSS; log('📈 总体统计:', 'bright'); log(` JavaScript: ${formatBytes(totalJS)} (${((totalJS / total) * 100).toFixed(1)}%)`, 'blue'); log(` CSS: ${formatBytes(totalCSS)} (${((totalCSS / total) * 100).toFixed(1)}%)`, 'blue'); log(` 总计: ${formatBytes(total)}\n`, 'bright'); // 警告信息 const largeFiles = stats.js.filter(f => f.size > 600 * 1024); if (largeFiles.length > 0) { log('⚠️ 警告: 以下文件超过 600 KB,建议优化:', 'yellow'); largeFiles.forEach((file) => { log(` - ${file.name}: ${file.sizeFormatted}`, 'yellow'); }); log(''); } // 优化建议 log('💡 优化建议:', 'bright'); if (largeFiles.length > 0) { log(' 1. 检查是否可以进一步拆分大型 chunk', 'blue'); log(' 2. 确认是否启用了 tree-shaking', 'blue'); log(' 3. 考虑使用动态导入延迟加载', 'blue'); } else { log(' ✅ Bundle 大小在合理范围内', 'green'); } // 检查是否生成了分析报告 const statsFile = path.join(process.cwd(), 'dist', 'stats.html'); if (fs.existsSync(statsFile)) { log('\n📊 详细分析报告已生成: dist/stats.html', 'green'); log(' 在浏览器中打开此文件查看可视化分析\n', 'blue'); } else { log('\n⚠️ 未生成详细分析报告', 'yellow'); log(' 请确保已安装 rollup-plugin-visualizer:', 'yellow'); log(' npm install --save-dev rollup-plugin-visualizer\n', 'yellow'); } } // 运行脚本 main().catch((error) => { console.error('Error:', error); process.exit(1); });