analyze.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
  2. const webpack = require('webpack')
  3. const path = require('path')
  4. const fs = require('fs')
  5. // 获取webpack配置
  6. const webpackConfig = require('../webpack.config.js')
  7. // 添加分析插件
  8. const analyzeConfig = {
  9. ...webpackConfig,
  10. plugins: [
  11. ...webpackConfig.plugins,
  12. new BundleAnalyzerPlugin({
  13. analyzerMode: 'static',
  14. reportFilename: 'bundle-report.html',
  15. openAnalyzer: true,
  16. generateStatsFile: true,
  17. statsFilename: 'bundle-stats.json',
  18. statsOptions: null,
  19. logLevel: 'info'
  20. })
  21. ]
  22. }
  23. // 运行分析
  24. console.log('🔍 开始分析打包结果...')
  25. webpack(analyzeConfig, (err, stats) => {
  26. if (err) {
  27. console.error('❌ 分析失败:', err)
  28. return
  29. }
  30. if (stats.hasErrors()) {
  31. console.error('❌ 构建过程中出现错误:')
  32. stats.compilation.errors.forEach(error => {
  33. console.error(error)
  34. })
  35. return
  36. }
  37. console.log('✅ 分析完成!')
  38. console.log('📊 报告文件已生成:')
  39. console.log(' - bundle-report.html (可视化报告)')
  40. console.log(' - bundle-stats.json (详细统计)')
  41. // 生成简单的性能报告
  42. const statsData = stats.toJson()
  43. const report = generatePerformanceReport(statsData)
  44. fs.writeFileSync(
  45. path.join(__dirname, '../performance-report.md'),
  46. report,
  47. 'utf8'
  48. )
  49. console.log(' - performance-report.md (性能报告)')
  50. })
  51. function generatePerformanceReport(stats) {
  52. const assets = stats.assets || []
  53. const chunks = stats.chunks || []
  54. const modules = stats.modules || []
  55. // 按类型分组资源
  56. const assetsByType = {
  57. js: assets.filter(asset => asset.name.endsWith('.js')),
  58. css: assets.filter(asset => asset.name.endsWith('.css')),
  59. images: assets.filter(asset => /\.(png|jpg|jpeg|gif|svg|webp)$/.test(asset.name)),
  60. fonts: assets.filter(asset => /\.(woff|woff2|eot|ttf|otf)$/.test(asset.name)),
  61. other: assets.filter(asset =>
  62. !asset.name.endsWith('.js') &&
  63. !asset.name.endsWith('.css') &&
  64. !/\.(png|jpg|jpeg|gif|svg|webp|woff|woff2|eot|ttf|otf)$/.test(asset.name)
  65. )
  66. }
  67. // 计算总大小
  68. const totalSize = assets.reduce((sum, asset) => sum + asset.size, 0)
  69. const jsSize = assetsByType.js.reduce((sum, asset) => sum + asset.size, 0)
  70. const cssSize = assetsByType.css.reduce((sum, asset) => sum + asset.size, 0)
  71. // 生成报告
  72. const report = `# 性能分析报告
  73. ## 📊 构建统计
  74. - **总文件数**: ${assets.length}
  75. - **总大小**: ${formatBytes(totalSize)}
  76. - **JS文件**: ${assetsByType.js.length} 个,${formatBytes(jsSize)}
  77. - **CSS文件**: ${assetsByType.css.length} 个,${formatBytes(cssSize)}
  78. - **图片文件**: ${assetsByType.images.length} 个
  79. - **字体文件**: ${assetsByType.fonts.length} 个
  80. - **其他文件**: ${assetsByType.other.length} 个
  81. ## 🎯 主要JS文件
  82. ${assetsByType.js
  83. .sort((a, b) => b.size - a.size)
  84. .slice(0, 10)
  85. .map(asset => `- **${asset.name}**: ${formatBytes(asset.size)}`)
  86. .join('\n')}
  87. ## 🎨 CSS文件
  88. ${assetsByType.css
  89. .sort((a, b) => b.size - a.size)
  90. .map(asset => `- **${asset.name}**: ${formatBytes(asset.size)}`)
  91. .join('\n')}
  92. ## 📦 Chunk分析
  93. ${chunks
  94. .sort((a, b) => b.size - a.size)
  95. .slice(0, 10)
  96. .map(chunk => `- **${chunk.names.join(', ')}**: ${formatBytes(chunk.size)}`)
  97. .join('\n')}
  98. ## 🔍 大文件警告
  99. ${assets
  100. .filter(asset => asset.size > 1024 * 1024) // 1MB以上
  101. .sort((a, b) => b.size - a.size)
  102. .map(asset => `- ⚠️ **${asset.name}**: ${formatBytes(asset.size)} (建议优化)`)
  103. .join('\n') || '✅ 没有超过1MB的文件'}
  104. ## 📈 性能建议
  105. ### 🚀 已实施的优化
  106. - ✅ 代码分割 (${chunks.length} 个chunk)
  107. - ✅ 资源压缩
  108. - ✅ 缓存策略
  109. - ✅ 预加载关键资源
  110. ### 🎯 进一步优化建议
  111. ${generateOptimizationSuggestions(assets, chunks)}
  112. ---
  113. *报告生成时间: ${new Date().toLocaleString()}*
  114. `
  115. return report
  116. }
  117. function generateOptimizationSuggestions(assets, chunks) {
  118. const suggestions = []
  119. // 检查大文件
  120. const largeFiles = assets.filter(asset => asset.size > 500 * 1024) // 500KB以上
  121. if (largeFiles.length > 0) {
  122. suggestions.push('- 考虑进一步分割大文件或使用动态导入')
  123. }
  124. // 检查chunk数量
  125. if (chunks.length > 20) {
  126. suggestions.push('- Chunk数量较多,考虑合并相关功能')
  127. }
  128. // 检查图片优化
  129. const images = assets.filter(asset => /\.(png|jpg|jpeg|gif|svg)$/.test(asset.name))
  130. const largeImages = images.filter(asset => asset.size > 100 * 1024) // 100KB以上
  131. if (largeImages.length > 0) {
  132. suggestions.push('- 优化大图片,考虑使用WebP格式')
  133. }
  134. if (suggestions.length === 0) {
  135. suggestions.push('- 当前配置已经相当优化!')
  136. }
  137. return suggestions.join('\n')
  138. }
  139. function formatBytes(bytes) {
  140. if (bytes === 0) return '0 Bytes'
  141. const k = 1024
  142. const sizes = ['Bytes', 'KB', 'MB', 'GB']
  143. const i = Math.floor(Math.log(bytes) / Math.log(k))
  144. return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
  145. }