#!/usr/bin/env node /** * Bundle 分析脚本 * 用于分析 Naive UI 组件的打包情况和优化建议 */ import { execSync } from 'child_process' import { readFileSync, writeFileSync } from 'fs' import { resolve } from 'path' const DIST_PATH = resolve(process.cwd(), 'dist') const ANALYSIS_OUTPUT = resolve(process.cwd(), 'bundle-analysis.json') /** * 分析打包结果 */ function analyzeBundleSize() { console.log('🔍 开始分析打包结果...') try { // 构建生产版本 console.log('📦 构建生产版本...') execSync('npm run build', { stdio: 'inherit' }) // 获取文件大小信息 const result = execSync(`find ${DIST_PATH} -name "*.js" -o -name "*.css" | xargs ls -la`, { encoding: 'utf-8' }) const files = result.split('\n') .filter(line => line.trim()) .map(line => { const parts = line.split(/\s+/) const size = parseInt(parts[4]) const name = parts[parts.length - 1].replace(DIST_PATH + '/', '') return { name, size, sizeKB: Math.round(size / 1024 * 100) / 100 } }) .filter(file => file.size > 0) .sort((a, b) => b.size - a.size) // 分析 Naive UI 相关文件 const naiveUIFiles = files.filter(file => file.name.includes('naive-ui') || file.name.includes('naive') ) const totalSize = files.reduce((sum, file) => sum + file.size, 0) const naiveUISize = naiveUIFiles.reduce((sum, file) => sum + file.size, 0) const analysis = { timestamp: new Date().toISOString(), totalFiles: files.length, totalSizeKB: Math.round(totalSize / 1024 * 100) / 100, naiveUIFiles: naiveUIFiles.length, naiveUISizeKB: Math.round(naiveUISize / 1024 * 100) / 100, naiveUIPercentage: Math.round(naiveUISize / totalSize * 100 * 100) / 100, files: files.slice(0, 20), // 只保留前20个最大的文件 naiveUIFiles: naiveUIFiles, recommendations: generateRecommendations(files, naiveUIFiles, totalSize) } // 保存分析结果 writeFileSync(ANALYSIS_OUTPUT, JSON.stringify(analysis, null, 2)) // 输出分析报告 printAnalysisReport(analysis) } catch (error) { console.error('❌ 分析失败:', error.message) process.exit(1) } } /** * 生成优化建议 */ function generateRecommendations(allFiles, naiveUIFiles, totalSize) { const recommendations = [] // 检查是否有过大的单个文件 const largeFiles = allFiles.filter(file => file.size > 500 * 1024) // 大于500KB if (largeFiles.length > 0) { recommendations.push({ type: 'warning', title: '发现大文件', description: `有 ${largeFiles.length} 个文件超过 500KB,建议进一步拆分`, files: largeFiles.map(f => f.name) }) } // 检查 Naive UI 占比 const naiveUIPercentage = naiveUIFiles.reduce((sum, file) => sum + file.size, 0) / totalSize * 100 if (naiveUIPercentage > 30) { recommendations.push({ type: 'info', title: 'Naive UI 占比较高', description: `Naive UI 组件占总包体积的 ${Math.round(naiveUIPercentage)}%,可以考虑更精细的按需引入`, suggestion: '使用 tree-shaking 或更细粒度的组件拆分' }) } // 检查是否有重复的组件 const duplicatePattern = /naive-ui.*\.(js|css)$/ const duplicates = allFiles.filter(file => duplicatePattern.test(file.name)) if (duplicates.length > 5) { recommendations.push({ type: 'warning', title: '可能存在组件重复', description: '发现多个 Naive UI 相关文件,可能存在重复打包', suggestion: '检查 webpack 配置中的 splitChunks 设置' }) } // 检查 CSS 文件大小 const cssFiles = allFiles.filter(file => file.name.endsWith('.css')) const largeCSSFiles = cssFiles.filter(file => file.size > 100 * 1024) // 大于100KB if (largeCSSFiles.length > 0) { recommendations.push({ type: 'info', title: 'CSS 文件较大', description: '发现较大的 CSS 文件,可以考虑按需加载样式', files: largeCSSFiles.map(f => f.name) }) } return recommendations } /** * 打印分析报告 */ function printAnalysisReport(analysis) { console.log('\n📊 Bundle 分析报告') console.log('='.repeat(50)) console.log(`📁 总文件数: ${analysis.totalFiles}`) console.log(`📦 总大小: ${analysis.totalSizeKB} KB`) console.log(`🎨 Naive UI 文件数: ${analysis.naiveUIFiles.length}`) console.log(`🎨 Naive UI 大小: ${analysis.naiveUISizeKB} KB (${analysis.naiveUIPercentage}%)`) console.log('\n📋 最大的文件:') analysis.files.slice(0, 10).forEach((file, index) => { console.log(`${index + 1}. ${file.name}: ${file.sizeKB} KB`) }) if (analysis.naiveUIFiles.length > 0) { console.log('\n🎨 Naive UI 相关文件:') analysis.naiveUIFiles.forEach((file, index) => { console.log(`${index + 1}. ${file.name}: ${file.sizeKB} KB`) }) } if (analysis.recommendations.length > 0) { console.log('\n💡 优化建议:') analysis.recommendations.forEach((rec, index) => { const icon = rec.type === 'warning' ? '⚠️' : 'ℹ️' console.log(`${icon} ${index + 1}. ${rec.title}`) console.log(` ${rec.description}`) if (rec.suggestion) { console.log(` 建议: ${rec.suggestion}`) } if (rec.files) { console.log(` 文件: ${rec.files.join(', ')}`) } console.log('') }) } console.log(`\n💾 详细分析结果已保存到: ${ANALYSIS_OUTPUT}`) } // 运行分析 if (import.meta.url === `file://${process.argv[1]}`) { analyzeBundleSize() } export { analyzeBundleSize }