177 lines
5.6 KiB
JavaScript
177 lines
5.6 KiB
JavaScript
#!/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 }
|