问题背景
最近在一个中型管理后台项目(约 40 个页面,200+ 组件)中,我遇到了一个棘手的问题:开发环境一切正常,但执行 vite build 部署到生产环境后,页面出现间歇性白屏。
错误信息在控制台中短暂闪现后消失,只留下一个空白的页面。这让问题定位变得非常困难。
排查过程
第一步:确认是否为环境差异
首先排除了最常见的几个原因:
- 环境变量:
.env.production配置正确,API 地址指向生产环境 - 路由模式:使用的是 hash 路由,不存在 Nginx 配置问题
- CDN 缓存:刷新后清除缓存问题依然存在
既然基本方向没有问题,我将目光转向了构建产物本身。
第二步:分析构建产物
通过 vite build --debug 输出详细的构建日志,我发现了一个关键线索:
dist/assets/index-abc123.js 2.8 MB (gzip: 856 KB)
dist/assets/vendor-def456.js 4.2 MB (gzip: 1.3 MB)vendor chunk 高达 4.2MB。这严重影响了首屏加载性能,也可能导致了浏览器解析超时。
第三步:深入排查依赖打包
使用 rollup-plugin-visualizer 生成打包体积分析图后,真相浮出水面:
- Moment.js 全量引入:moment 的所有 locale 文件都被打包进来了(约 300KB gzip)
- Ant Design Vue 全量导入:没有使用按需加载,导致整个组件库被打包
- Lodash 全量引入:import 方式为
import _ from 'lodash'而非import debounce from 'lodash/debounce'
第四步:制定修复方案
针对以上三个问题,逐一修复:
修复 moment.js 体积问题
ts
// vite.config.ts
export default defineConfig({
resolve: {
alias: {
// 使用 dayjs 替代 moment.js,体积减小 90%
}
},
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-ui': ['ant-design-vue'],
'vendor-utils': ['dayjs', 'lodash-es']
}
}
}
}
})用 dayjs 替换 moment.js 后,日期处理相关代码的体积从 ~300KB 降低到 ~7KB。
修复 Ant Design Vue 导入方式
配置 unplugin-vue-components 实现自动按需导入:
ts
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
Components({
resolvers: [AntDesignVueResolver()]
})
]
})修复 Lodash 导入方式
将项目中的 import _ from 'lodash' 全局替换为 import { debounce } from 'lodash-es',配合 Tree Shaking 去除未使用代码。
第五步:优化 chunk 分割策略
除了修复上述依赖问题,我还重新设计了 chunk 分割策略:
ts
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
// 将大依赖拆分为独立 chunk
if (id.includes('ant-design-vue')) return 'antd'
if (id.includes('echarts')) return 'echarts'
if (id.includes('@antv')) return 'antv'
// 小型工具库合并
return 'vendor'
}
}
}
}
}最终效果
| 指标 | 优化前 | 优化后 | 降幅 |
|---|---|---|---|
| 总包体积 (gzip) | 2.1 MB | 680 KB | 67.6% |
| 最大单 chunk (gzip) | 1.3 MB | 280 KB | 78.5% |
| 首屏加载时间 | 4.8s | 1.2s | 75% |
| Lighthouse 评分 | 42 | 91 | +49 |
经验总结
- 开发环境 ≠ 生产环境:Vite 开发模式使用原生 ESM,而生产构建使用 Rollup 打包,两者的模块加载机制完全不一样
- 依赖体积要持续关注:建议在 CI 中集成
bundlesize检查,防止依赖膨胀 - Tree Shaking 不是银弹:需要配合正确的导入方式(使用具名导入而非默认导入)才能生效
- chunk 分割策略需要针对性设计:一刀切的
splitVendorChunk往往不够,需要根据业务场景手动配置
- 本文链接:
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
