176
scripts/analyze-bundle.js
Normal file
176
scripts/analyze-bundle.js
Normal file
@@ -0,0 +1,176 @@
|
||||
#!/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 }
|
||||
Reference in New Issue
Block a user