把首屏 JS 从 1.2MB 压到 180KB:构建产物体积优化的 6 个实操点
先量化,再动手
体积优化最容易犯的错是凭感觉改配置。先用工具把产物拆开看清楚,再决定改什么。
# 安装分析插件
npm i -D rollup-plugin-visualizer
# 在 vite.config.ts 中启用
import { visualizer } from 'rollup-plugin-visualizer'
export default {
plugins: [visualizer({ gzipSize: true, brotliSize: true, filename: 'stats.html' })]
}
# 构建后打开 stats.html,按 brotli 体积排序同时统计每次构建的增量,避免优化成果被后续提交吃掉:
npx vite build --mode production
# 记录 dist 下每个 chunk 的 brotli 大小,写进 CI 的对比基线一个真实项目的首屏 JS 是 1.2MB(brotli),分析后发现 3 个占比最大的依赖:moment(含全部 locale,232KB)、lodash 全量引入(96KB)、一个内部 UI 库把整个组件库打进了入口 chunk(410KB)。
依赖替换与按需引入
体积问题大部分出在依赖,不在业务代码。
- moment 换成 dayjs,并把 locale 显式引入:
import 'dayjs/locale/zh-cn',体积从 232KB 降到 6KB。 - lodash 改为具名路径引入
import debounce from 'lodash/debounce',或直接用lodash-es配合 tree-shaking。 - UI 库按需引入,antd 5 默认支持 tree-shaking,但仍要确认没有
import { Button } from 'antd'之外的整包引入。
用一条命令检查是否混入了重复版本:
npm ls lodash react react-dom
# 出现多个版本说明有依赖没对齐,用 overrides 统一
# package.json
"overrides": { "lodash": "4.17.21" }
代码分割与 chunk 策略
默认的自动分割在大项目里经常切得太碎或太粗。手动配置 vendor chunk,让公共依赖的缓存命中率更高:
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('react') || id.includes('scheduler')) return 'react-vendor'
if (id.includes('echarts')) return 'charts'
return 'vendor'
}
}
}
}
}路由级别用动态 import 懒加载,把非首屏页面全部拆出去。配合 /* @vite-ignore */ 之外,注意动态 import 的路径不要拼接变量,否则无法静态分析,会退化成一个包含所有可能模块的 chunk。
踩坑一:打包后 IE 兼容插件把体积翻倍
项目引入了 @vitejs/plugin-legacy 支持旧浏览器,构建产物里多出一份完整 polyfill 包(约 300KB)和一份 legacy chunk。实际上只覆盖 99.5% 以上的现代浏览器即可,旧的 legacy 包白白增加部署与缓存成本。
解决办法:
legacy({
targets: ['defaults', 'not IE 11'],
renderLegacyChunks: false, // 不再生成完整 legacy chunk
modernPolyfills: true
})同时用 build.target: 'es2015' 让现代产物只需少量 polyfill。调整后首屏下降约 260KB。
踩坑二:tree-shaking 没生效,因为副作用标记
把内部工具库改成 ESM 后体积几乎没降。原因是该包的 package.json 没有声明 "sideEffects": false,打包器保守地保留了所有模块。在工具库中补上:
{
"main": "dist/index.cjs",
"module": "dist/index.esm.js",
"sideEffects": false
}另一个隐蔽原因是 import * as utils from './utils' 后只用了其中一个方法,命名空间导入会阻止摇树。改成具名导入即可。console.log 也要清理,用 esbuild 的 drop:
esbuild: { drop: ['console', 'debugger'] }
压缩与产物细节
开启 brotli 预压缩,服务端直接下发压缩文件,避免运行时压缩开销:
npm i -D vite-plugin-compression
compression({ algorithm: 'brotliCompress', threshold: 10240, ext: '.br' })CSS 也要关注:cssCodeSplit: true 按路由拆分样式,配合 PurgeCSS 或 UnoCSS 清除未使用类名。图片走 vite-plugin-imagemin,优先转 WebP。
小结
先量化再优化,重点在依赖替换、按需引入、手动 chunk 和副作用标记。三个改动叠加后,该项目首屏 JS 从 1.2MB 降到 180KB(brotli),首屏加载时间从 3.4s 降到 1.1s。把体积检查写入 CI 阈值(例如入口 chunk 超过 250KB 即失败),是防止体积回流最有效的手段。
- 点赞
- 收藏
- 关注作者
评论(0)