打包与优化
原创2026/9/9...大约 6 分钟
打包与优化 🚀
Vite 开发体验已经够快,但生产环境的产物质量同样重要。本章我们学习如何分析和优化 Vite 打包结果,让应用又快又小。
一、基本打包 💎
# 生产环境打包
pnpm build
# 打包 + 本地预览
pnpm preview
# 打包并分析体积
pnpm build --mode analyze打包完成后,会在 dist/ 目录生成静态文件:
dist/
├── assets/
│ ├── index-abc123.js
│ ├── index-def456.css
│ └── logo-ghi789.png
├── index.html
└── favicon.ico二、产物分析 💎
1. rollup-plugin-visualizer 💎
最流行的可视化分析插件。
pnpm add -D rollup-plugin-visualizer// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true, // 自动打开报告
filename: 'dist/stats.html',
gzipSize: true,
brotliSize: true,
}),
],
})打包后会自动打开 stats.html,可以清晰地看到每个模块的体积占比。
2. source-map-explorer 💎
通过 sourcemap 分析体积占比。
pnpm add -D source-map-explorer// vite.config.ts
export default defineConfig({
build: {
sourcemap: true, // 需要开启 sourcemap
},
})npx source-map-explorer 'dist/assets/*.js'3. 控制台分析 💎
通过 vite build 的输出,Vite 已经会告诉你:
- 每个 chunk 的大小
- gzip / brotli 压缩后的大小
- 超过
chunkSizeWarningLimit的警告
dist/assets/index-abc123.js 523.45 kB │ gzip: 165.32 kB
dist/assets/index-def456.css 12.34 kB │ gzip: 4.56 kB
✓ built in 12.34s三、代码分割 💎
Vite / Rollup 默认会进行代码分割,但仍需要手动优化以获得最佳效果。
1. 路由级分割(Vue) 💎
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import('@/views/Home.vue'), // 懒加载
},
{
path: '/about',
component: () => import('@/views/About.vue'),
},
{
path: '/user/:id',
component: () => import('@/views/UserDetail.vue'),
},
],
})2. 路由级分割(React) 💎
import { lazy, Suspense } from 'react'
const Home = lazy(() => import('./views/Home'))
const About = lazy(() => import('./views/About'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
)
}3. 手动分割第三方库 💎
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
// 把第三方库拆分到独立 chunk
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
echarts: ['echarts'],
'lodash-es': ['lodash-es'],
},
},
},
},
})4. 函数式 manualChunks 💎
更灵活的方式:
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('echarts') || id.includes('zrender')) return 'echarts'
if (id.includes('vue') || id.includes('@vue') || id.includes('pinia')) return 'vue'
if (id.includes('lodash')) return 'lodash'
// 其他第三方库合并
return 'vendor'
}
},
},
},
}四、资源优化 💎
1. 图片压缩 💎
pnpm add -D vite-plugin-imagemin// vite.config.ts
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig({
plugins: [
viteImagemin({
gifsicle: { optimizationLevel: 7 },
mozjpeg: { quality: 75 },
pngquant: { quality: [0.65, 0.8] },
svgo: {
plugins: [{ removeViewBox: false }],
},
webp: { quality: 75 },
}),
],
})2. 图片转 WebP / AVIF 💎
pnpm add -D sharp// vite.config.ts
export default defineConfig({
build: {
// 开启 Vite 5+ 实验性图片处理
},
})3. SVG 优化 💎
// vite.config.ts
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
export default defineConfig({
plugins: [
createSvgIconsPlugin({
iconDirs: ['src/assets/icons'],
symbolId: 'icon-[name]',
svgoOptions: {
plugins: [
{
name: 'removeAttrs',
params: { attrs: 'fill' },
},
],
},
}),
],
})4. 字体子集化 💎
pnpm add -D fontmin只打包用到的字形,大幅减小字体体积。
5. CDN 加载 💎
把大库(echarts、monaco-editor 等)通过 CDN 加载:
// vite.config.ts
import { cdnImport } from 'vite-plugin-cdn-import'
export default defineConfig({
plugins: [
cdnImport({
modules: [
{
name: 'echarts',
var: 'echarts',
path: 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js',
},
{
name: 'monaco-editor',
var: 'monaco',
path: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45/min/vs/loader.min.js',
},
],
}),
],
})五、Gzip / Brotli 压缩 💎
1. 构建时生成 .gz 文件 💎
pnpm add -D vite-plugin-compression// vite.config.ts
import compression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
compression({
algorithm: 'gzip',
ext: '.gz',
threshold: 10240, // 10KB 以上才压缩
deleteOriginFile: false,
}),
],
})2. 同时生成 .br 文件 💎
pnpm add -D vite-plugin-compression// vite.config.ts
import compression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
compression({ algorithm: 'gzip', ext: '.gz' }),
compression({ algorithm: 'brotliCompress', ext: '.br' }),
],
})3. Nginx 配置启用 💎
server {
listen 80;
server_name example.com;
# 开启 gzip
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css text/javascript application/json application/javascript application/xml;
gzip_vary on;
# 优先返回 .br / .gz
gzip_static on;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}六、Tree Shaking 💎
Vite / Rollup 默认启用 Tree Shaking,但需要满足一些条件:
1. 使用 ES Module 💎
// ❌ 整个 lodash 会被打包
import _ from 'lodash'
// ✅ 只打包用到的函数
import { debounce } from 'lodash-es'2. 设置 sideEffects 💎
// package.json
{
"sideEffects": false
}或精确指定哪些文件有副作用:
{
"sideEffects": ["*.css", "*.scss", "*.vue"]
}3. 避免动态导入的变量 💎
// ❌ 无法静态分析,整个包会被打包
const module = await import(`./modules/${name}`)
// ✅ 静态路径可以 Tree Shaking
const module = await import(`./modules/${name}.js`)七、加载性能优化 💎
1. 路由懒加载 💎
见上文"代码分割"部分。
2. 组件异步加载 💎
<!-- Vue 异步组件 -->
<template>
<Suspense>
<template #default>
<HeavyComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const HeavyComponent = defineAsyncComponent(
() => import('@/components/HeavyComponent.vue')
)
</script>3. 预加载关键资源 💎
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
// 预加载指令
experimentalMinChunkSize: 10000,
},
},
},
})Vite 会自动在 HTML 中加入 <link rel="modulepreload"> 标签。
4. 图片懒加载 💎
<template>
<img
v-for="img in images"
:key="img"
:src="img"
loading="lazy"
alt=""
/>
</template>八、CSS 优化 💎
1. CSS 压缩 💎
// vite.config.ts
export default defineConfig({
build: {
cssMinify: 'esbuild', // 默认,够用
// cssMinify: 'lightningcss', // 更快
cssCodeSplit: true, // CSS 拆分到独立文件
},
})2. 移除未使用的 CSS 💎
pnpm add -D vite-plugin-purgecss// vite.config.ts
import purgecss from 'vite-plugin-purgecss'
export default defineConfig({
plugins: [
purgecss({
content: ['./index.html', './src/**/*.{vue,ts,tsx}'],
}),
],
})九、首屏体积优化实战 💎
下面是一个综合优化的 vite.config.ts:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { visualizer } from 'rollup-plugin-visualizer'
import compression from 'vite-plugin-compression'
import viteImagemin from 'vite-plugin-imagemin'
import path from 'node:path'
export default defineConfig({
plugins: [
vue(),
vueJsx(),
visualizer({ open: true, filename: 'dist/stats.html' }),
compression({ algorithm: 'gzip', ext: '.gz', threshold: 10240 }),
viteImagemin({
mozjpeg: { quality: 75 },
pngquant: { quality: [0.65, 0.8] },
svgo: { plugins: [{ removeViewBox: false }] },
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
build: {
target: 'es2020',
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'esbuild',
cssCodeSplit: true,
cssMinify: 'esbuild',
chunkSizeWarningLimit: 1500,
reportCompressedSize: false,
rollupOptions: {
output: {
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]',
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('echarts') || id.includes('zrender')) return 'echarts-vendor'
if (id.includes('vue') || id.includes('pinia')) return 'vue-vendor'
if (id.includes('lodash')) return 'lodash-vendor'
return 'vendor'
}
},
},
},
},
})十、部署 💎
1. 部署到静态服务器 💎
# 打包
pnpm build
# 产物目录:dist/将 dist/ 整个目录上传到服务器即可。
2. SPA 路由配置 💎
SPA 应用刷新页面会 404,需要配置服务器把所有未匹配的请求 fallback 到 index.html:
Nginx
location / {
try_files $uri $uri/ /index.html;
}Apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>3. 部署到 CDN 💎
// vite.config.ts
export default defineConfig({
base: '/myapp/', // 子路径
build: {
assetsDir: 'assets',
},
})<!-- 自动生成的 index.html -->
<script src="/myapp/assets/index-abc123.js"></script>4. 部署到 OSS / S3 💎
// vite.config.ts
export default defineConfig({
base: 'https://cdn.yourcompany.com/myapp/',
})每次发布前上传 dist/ 目录到 OSS / S3 即可。
常见问题 💎
chunk 太大警告 👻
超过 chunkSizeWarningLimit(默认 500KB)会有警告。解决方案:
- 手动分割大依赖(
manualChunks) - 把大依赖改为 CDN 加载
- 升级
chunkSizeWarningLimit
路由懒加载不生效 👻
确认:
- 使用了动态
import() - 路径正确
- 浏览器 DevTools Network 中能看到独立 chunk
打包后图片不显示 👻
检查:
base配置是否正确- 图片路径是否带 hash(说明已正确处理)
- 服务器 MIME 类型是否正确
小结
本章我们学习了 Vite 生产环境打包与优化的完整内容:
- 产物分析:visualizer、source-map-explorer
- 代码分割:路由级、第三方库
- 资源优化:图片压缩、SVG、CDN
- 压缩:Gzip / Brotli
- Tree Shaking:ES Module、sideEffects
- 加载性能:懒加载、预加载
- 部署:Nginx、CDN、OSS
下一章我们将学习 HMR 热更新与常见问题。
至此,本章节的学习就到此结束了,如有疑惑,可对接技术客服进行相关咨询。