perf: optimize build chunks and add bundle size visualization

This commit is contained in:
Megghy
2025-10-04 09:54:27 +08:00
parent 389515bc5b
commit 888424195b
6 changed files with 176 additions and 16 deletions

View File

@@ -9,6 +9,7 @@ import Markdown from 'unplugin-vue-markdown/vite'
import { defineConfig } from 'vite'
import svgLoader from 'vite-svg-loader'
import { VineVitePlugin } from 'vue-vine/vite'
import { visualizer } from 'rollup-plugin-visualizer';
// 自定义SVGO插件删除所有名称以sodipodi:和inkscape:开头的元素
const removeSodipodiInkscape = {
@@ -87,6 +88,14 @@ export default defineConfig({
include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.vine$/],
}),
VineVitePlugin(),
visualizer({
open: false, // 不自动打开浏览器,避免影响 CI/CD
gzipSize: true, // 显示 Gzip 压缩后的大小
brotliSize: true, // 显示 Brotli 压缩后的大小
filename: 'dist/stats.html', // 分析报告的输出路径
template: 'treemap', // 使用树图模式展示
sourcemap: true, // 使用 sourcemap 进行更精确的分析
}),
],
server: { port: 51000 },
resolve: { alias: { '@': path.resolve(__dirname, 'src') } },
@@ -96,29 +105,161 @@ export default defineConfig({
'__BUILD_TIME__': JSON.stringify(new Date().toISOString()),
},
optimizeDeps: {
include: ['@vicons/fluent', '@vicons/ionicons5', 'vue', 'vue-router'],
include: [
'vue',
'vue-router',
'pinia',
'@vueuse/core',
'naive-ui',
'date-fns',
'@vicons/fluent',
'@vicons/ionicons5',
],
exclude: ['@tauri-apps/api', '@tauri-apps/plugin-autostart', '@tauri-apps/plugin-http'],
},
build: {
sourcemap: true,
target: 'esnext',
minify: 'oxc',
chunkSizeWarningLimit: 1000,
chunkSizeWarningLimit: 1600, // 调整警告阈值gamepad-assets 会比较大
cssCodeSplit: true,
cssMinify: 'lightningcss',
// 报告压缩后的文件大小
reportCompressedSize: true,
// 优化模块预加载策略
modulePreload: {
polyfill: true,
},
// 静态资源内联阈值(小于此大小的资源会被内联为 base64
assetsInlineLimit: 4096, // 4KB默认值可根据需要调整
rollupOptions: {
output: { // @ts-ignore
output: {
advancedChunks: {
groups: [
{
name: 'vue-vendor',
test: /[\\/]node_modules[\\/](vue|vue-router|pinia)[\\/]/,
priority: -10,
},
{
name: 'ui-vendor',
test: /[\\/]node_modules[\\/](naive-ui|@vueuse[\\/]core)[\\/]/,
priority: -10,
name: (id: string) => {
// 核心框架
if (id.includes('node_modules/vue/') || id.includes('node_modules/@vue/')) {
return 'vue-core'
}
if (id.includes('node_modules/vue-router/')) {
return 'vue-router'
}
if (id.includes('node_modules/pinia/')) {
return 'pinia'
}
// UI 框架及相关
if (id.includes('node_modules/naive-ui/') || id.includes('node_modules/vueuc/')) {
return 'naive-ui'
}
// VueUse 系列
if (id.includes('node_modules/@vueuse/')) {
return 'vueuse'
}
// 图标库
if (id.includes('node_modules/@vicons/')) {
return 'icons'
}
// Gamepad 配置和资源(体积大,单独分离)
if (id.includes('/gamepadConfigs') || id.includes('assets/controller/')) {
return 'gamepad-assets'
}
// 字典文件(拼音、假名等,按需加载)
if (id.includes('/dictPinyin')) {
return 'dict-pinyin'
}
if (id.includes('/dictKana')) {
return 'dict-kana'
}
// Monaco Editor (代码编辑器,体积大)
if (id.includes('node_modules/monaco-editor/') || id.includes('node_modules/@guolao/vue-monaco-editor/')) {
return 'monaco-editor'
}
// ECharts (图表库,体积大)
if (id.includes('node_modules/echarts/') || id.includes('node_modules/vue-echarts/')) {
return 'echarts'
}
// 富文本编辑器
if (id.includes('node_modules/@wangeditor/')) {
return 'wangeditor'
}
// SignalR 相关
if (id.includes('node_modules/@microsoft/signalr')) {
return 'signalr'
}
// Tauri 相关(客户端专用)
if (id.includes('node_modules/@tauri-apps/')) {
return 'tauri'
}
// B站直播弹幕客户端
if (id.includes('node_modules/bilibili-live-danmaku/')) {
return 'bili-danmaku'
}
// 工具库
if (id.includes('node_modules/lodash/')) {
return 'lodash'
}
if (id.includes('node_modules/date-fns/')) {
return 'date-fns'
}
// Excel 相关 (体积大)
if (id.includes('node_modules/xlsx/')) {
return 'xlsx'
}
// 压缩和解压
if (id.includes('node_modules/jszip/') || id.includes('node_modules/@oneidentity/zstd-js/')) {
return 'compression'
}
// 其他大型依赖
if (id.includes('node_modules/html2canvas/')) {
return 'html2canvas'
}
if (id.includes('node_modules/cropperjs/') || id.includes('node_modules/vue-cropperjs/')) {
return 'cropper'
}
// 通用 node_modules 分离
if (id.includes('node_modules/')) {
return 'vendor'
}
return null
},
},
],
},
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: (assetInfo: any) => {
// 根据资源类型分类存放
const info = assetInfo.name?.split('.') ?? []
const ext = info[info.length - 1]
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(ext)) {
return 'assets/images/[name]-[hash][extname]'
}
if (/woff2?|ttf|otf|eot/i.test(ext)) {
return 'assets/fonts/[name]-[hash][extname]'
}
if (/css/i.test(ext)) {
return 'assets/css/[name]-[hash][extname]'
}
return 'assets/[ext]/[name]-[hash][extname]'
},
},
},
},