以下内容 仅供参考酌情使用,正好最近优化项目 顺便记录下 后续好翻阅
说起 Vite 和性能优化,网上大多都是讲概念,比如:
- 什么是分包
- 什么是懒加载
- 什么是 Tree Shaking
但很多文章 只讲理论,不讲工程实践。
最近正好优化了一个项目的打包体积和首屏加载速度,顺手把 Vite 实战中最常见、最有效的优化方式总结了一下。
本文不讲复杂原理,只讲 真实项目里最有用的优化方法。
如果你的项目存在这些问题:
- 打包后
dist特别大 - 首屏加载慢
- 每次发布用户都要重新下载一堆 JS
- 图片体积巨大
那么这篇文章基本可以解决 80% 的性能问题。
一、默认情况下,Vite 的打包会发生什么?
很多人误以为:
Vite 会把所有代码打成一个 JS。
其实并不是。
Vite 的生产构建基于 Rollup ,默认会做基础的 chunk splitting(代码拆分) 。
例如:
css
main.js
vendor.js
但默认拆分 不会按照业务语义优化,可能会出现:
首页代码 + lodash
报表代码 + lodash
编辑器代码 + lodash
也就是说:
同一个依赖可能会被多个 chunk 引用,或者混入业务代码。
这会导致几个问题:
1️⃣ 首屏加载体积过大
2️⃣ 浏览器缓存利用率低
3️⃣ 更新业务代码时 vendor 也失效
所以我们需要做 更精细的分包控制。
二、第一层优化:路由懒加载(Code Splitting)
最基础也是最重要的一步:
按页面加载代码。
如果不做懒加载:
用户打开首页
↓
下载整个项目所有页面代码
如果做了懒加载:
打开首页
↓
只加载首页代码
进入报表页
↓
再加载报表代码
这样 首屏体积会显著下降。
React 示例
javascript
import { lazy, Suspense } from 'react'
const Home = lazy(() => import('./pages/Home'))
const Report = lazy(() => import('./pages/Report'))
const Editor = lazy(() => import('./pages/Editor'))
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/report" element={<Report />} />
<Route path="/editor" element={<Editor />} />
</Routes>
</Suspense>
)
}
核心思想只有一句话:
用到哪个页面,再加载哪个页面的代码。
三、第二层优化:manualChunks 控制第三方分包
懒加载解决的是 业务代码拆分。
但第三方依赖仍然可能混在业务 chunk 里。
例如:
arduino
page-home.js
└ react
└ lodash
page-report.js
└ echarts
└ lodash
如果业务代码更新:
arduino
page-home hash 改变
↓
lodash 也被重新下载
浏览器缓存就失效了。
manualChunks 的作用
manualChunks 可以 手动控制依赖如何分包。
把长期不变的依赖拆出来:
react-vendor.js
utils.js
echarts.js
editor.js
这样:
业务更新
↓
只更新业务 chunk
↓
vendor 继续使用缓存
示例配置
css
rollupOptions: {
output: {
manualChunks: {
'react-vendor': [
'react',
'react-dom',
'react-router-dom',
'zustand'
],
'arco-design': ['@arco-design/web-react'],
echarts: ['@ceai-front/echarts'],
wangeditor: [
'@wangeditor-next/editor',
'@wangeditor-next/editor-for-react'
],
utils: [
'lodash',
'lodash-es',
'dayjs',
'axios',
'classnames'
]
}
}
}
更稳定的写法(推荐)
在大型项目中,很多团队更推荐使用函数写法:
python
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('react')) {
return 'react-vendor'
}
if (id.includes('arco')) {
return 'arco'
}
if (id.includes('echarts')) {
return 'echarts'
}
}
}
原因是:
Rollup 解析模块时使用的是 文件路径:
bash
node_modules/react/index.js
函数写法在复杂依赖场景中 更稳定。
四、第三层优化:图片体积优化
在很多项目里:
图片往往才是体积最大的资源。
常见情况:
css
banner.jpg 2MB
icon.png 800KB
background 3MB
一旦页面加载多张图片,性能会明显下降。
使用 vite-plugin-imagemin
安装:
csharp
pnpm add vite-plugin-imagemin -D
配置:
css
import viteImagemin from 'vite-plugin-imagemin'
plugins: [
viteImagemin({
gifsicle: { optimizationLevel: 3 },
optipng: { optimizationLevel: 7 },
mozjpeg: { quality: 80 },
pngquant: { quality: [0.7, 0.8] },
svgo: {
plugins: [{ name: 'removeViewBox' }]
}
})
]
通常可以减少:
erlang
PNG 20% ~ 40%
JPG 10% ~ 30%
SVG 50%+
而肉眼几乎看不出差异。
五、一个容易忽略的小优化:assetsInlineLimit
yaml
assetsInlineLimit: 4096
意思是:
小于 4KB 的资源会被转换为 Base64 并直接内联到 JS 中。
这样可以减少 HTTP 请求数量。
但不宜设置过大,否则 JS 体积会膨胀。
六、完整的 Vite 优化配置示例
php
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig({
plugins: [
react(),
viteImagemin({
gifsicle: { optimizationLevel: 3 },
optipng: { optimizationLevel: 7 },
mozjpeg: { quality: 80 },
pngquant: { quality: [0.7, 0.8] },
svgo: { plugins: [{ name: 'removeViewBox' }] }
})
],
build: {
target: 'esnext',
minify: 'esbuild',
cssCodeSplit: true,
sourcemap: false,
chunkSizeWarningLimit: 2000,
assetsInlineLimit: 4096,
rollupOptions: {
output: {
manualChunks: {
'react-vendor': [
'react',
'react-dom',
'react-router-dom',
'zustand'
],
'arco-design': ['@arco-design/web-react'],
echarts: ['@ceai-front/echarts'],
pdfjs: ['pdfjs-dist'],
utils: [
'lodash',
'lodash-es',
'dayjs',
'axios',
'classnames'
]
},
assetFileNames: 'assets/[name].[hash][extname]',
chunkFileNames: 'js/[name].[hash].js',
entryFileNames: 'js/[name].[hash].js'
}
}
}
})
七、Vite 项目最实用的性能优化三件套
如果只记住三件事就够了:
1️⃣ 路由懒加载
控制 什么时候加载代码
首屏体积大幅下降
2️⃣ manualChunks 分包
把 长期不变的依赖单独打包
浏览器缓存最大化
3️⃣ 图片压缩
减少 最大体积资源
页面加载速度明显提升
三者关系(很好记)
懒加载 = 控制加载时机
分包 = 控制缓存策略
图片压缩 = 减少资源体积
一套下来,项目通常会有明显变化:
- 首屏体积下降
- JS chunk 更清晰
- 浏览器缓存利用率更高
- 页面加载更快