2025-09-23 【架构】vite性能优化最佳实践

Vite 的优化主要分为 开发体验(Dev Speed)构建性能(Build Speed)产物质量(Bundle Quality) 三个维度。

对于一个标准的 Vite 项目,可以从以下几个核心层面进行深度优化:


一、 构建产物优化(Bundle Quality)

这是优化最有感的部分,直接影响用户访问你的网站时的首字节时间(TTFB)和页面加载速度。

1. 分包策略 (Manual Chunks)

默认情况下,Vite 会将所有 JS 打包在一起。利用 rollupOptions 将大型第三方库拆分出来,可以有效利用浏览器缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
"vue-vendor": ["vue", "vue-router", "pinia"],
"ui-vendor": ["element-plus", "antd"], // 较大的 UI 库单独打包
},
},
},
},
});

2. 启用预压缩 (Gzip & Brotli)

前端需要在构建时,同时生成 .gz.br 文件。使用 vite-plugin-compression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import compress from "vite-plugin-compression";

plugins: [
// 1. 生成 .gz 文件
compress({
algorithm: "gzip",
ext: ".gz",
threshold: 10240, // 超过 10kb 才压缩
deleteOriginFile: false, // 切记:不要删除源文件!
}),
// 2. 生成 .br 文件
compress({
algorithm: "brotliCompress",
ext: ".br",
threshold: 10240,
deleteOriginFile: false,
}),
];

极致性能与保底兼容的完美平衡;这是“性能上限”与“兼容下限”的博弈:
Brotli (br) 是上限:它的压缩率比 Gzip 高出 17%-25%。这意味着同样的 JS 文件,用 br 传输会更小,用户打开你的“学习类网站”速度更快。
Gzip (gz) 是下限:虽然 br 已经覆盖了 95% 以上的现代浏览器,但仍有极少数场景(如旧版 Android 浏览器、某些公司内网的过时代理服务器、或者特定的爬虫)不支持 br。此时,Gzip 就是最稳妥的保底方案。


二、 静态资源与图片优化

图片通常占页面体积的 70% 以上。

1. 图片压缩与转码

使用 vite-imagetools 在打包时自动压缩图片,或者使用 vite-plugin-webp 将图片转换为下一代 WebP 格式。

2. 资源内联限制

通过 assetsInlineLimit 控制 Base64 内联。太大的图片内联会导致 JS 体积过大,过小的图片内联可以减少 HTTP 请求。

1
2
3
4
build: {
assetsInlineLimit: 4096, // 4kb 以下的资源内联为 base64
}


三、 开发环境体验优化

1. 预构建依赖 (Dependency Pre-Bundling)

如果你的项目有很多 CommonJS 的依赖,Vite 启动时会慢。可以在 optimizeDeps 中强制预构建某些包。

1
2
3
4
optimizeDeps: {
include: ['axios', 'echarts', '@antfu/utils'],
}

2. 开启缓存

确保 Vite 的 .vite 缓存目录没有被误删。Vite 会将预构建的依赖缓存,第二次启动几乎是瞬间完成。


四、 进阶:按需引入与 Tree Shaking

1. 自动导入插件

使用 unplugin-auto-importunplugin-vue-components。这不仅能减少 import 语句,更重要的是它支持 组件级别的 Tree Shaking,只打包你用到的组件。

1
2
3
4
5
6
7
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";

plugins: [
AutoImport({ resolvers: [ElementPlusResolver()] }),
Components({ resolvers: [ElementPlusResolver()] }),
];

2. 视觉分析工具

安装 rollup-plugin-visualizer,打包后它会生成一个 HTML 页面,通过矩形树图清晰展示每个依赖占用的空间,帮助你“针对性减肥”。


五. 配置参考

优化项

  1. 启用预压缩 (Gzip & Brotli)
    安装vite-plugin-compression,配置 gzip、br 静态压缩
  2. manualChunks 调整
    通过rollup-plugin-visualizer,调整manualChunks 配置,对 nodemodules、页面分包
  3. 图片处理
    安装vite-imagetools,转换图片
1
2
import productOverviewPcWebp from "@/assets/home/overview.png?format=webp";
import productOverviewMobileAvif from "@/assets/home/overview.png?w=750&format=avif";
  1. 按需引入
    安装unplugin-auto-import/vite、unplugin-vue-components/vite,组件级 tree Shaking
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
import { copyFileSync, existsSync } from "node:fs";
import path from "node:path";

import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import postCssPxToRem from "postcss-pxtorem";
import { visualizer } from "rollup-plugin-visualizer";
import AutoImport from "unplugin-auto-import/vite";
import { TDesignResolver } from "unplugin-vue-components/resolvers";
import Components from "unplugin-vue-components/vite";
import type { ConfigEnv, UserConfig } from "vite";
import { loadEnv } from "vite";
import { imagetools } from "vite-imagetools";
import viteCompression from "vite-plugin-compression";
import { viteMockServe } from "vite-plugin-mock";
import svgLoader from "vite-svg-loader";

const CWD = process.cwd();

// https://vitejs.dev/config/
export default ({ mode }: ConfigEnv): UserConfig => {
const { VITE_BASE_URL, VITE_API_URL_PREFIX, VITE_API_URL } = loadEnv(
mode,
CWD
);

return {
base: VITE_BASE_URL,
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],
},

esbuild: {
drop:
mode === "release" || mode === "site" ? ["console", "debugger"] : [],
},

build: {
target: "esnext", // 现代浏览器,产物更小
cssTarget: "chrome80",
chunkSizeWarningLimit: 1000, // 提高大包警告阈值
assetsInlineLimit: 4096, // 内联小于4k的资源
minify: "esbuild", // esbuild 比 terser 快 10-40 倍
cssCodeSplit: true, // CSS 拆分
sourcemap: false, // 生产环境关闭 sourcemap 减小体积

rollupOptions: {
output: {
chunkFileNames: "assets/js/[name]-[hash].js", // 分块文件- 非页面
entryFileNames: "assets/js/[name]-[hash].js", // 页面入口文件
assetFileNames: "assets/[ext]/[name]-[hash].[ext]", // 非js资源
manualChunks: (id) => {
if (id.includes("node_modules")) {
if (id.includes("tdesign-vue-next")) {
return "tdesign-vendor";
}
if (id.includes("echarts")) {
return "echarts-vendor";
}
if (id.includes("lodash")) {
return "lodash-vendor";
}
// 调整 vue相关库 顺序,避免tdesign-vue-next 合入
if (
id.includes("pinia") ||
id.includes("vue-router") ||
id.includes("@vueuse")
) {
return "vue-vendor";
}
return "vendor";
}

// 逻辑:如果是 src/views 目录下的文件
if (id.includes("src/pages")) {
// 提取模块名,例如 src/views/order/index.vue -> order
const modules = id.toString().split("src/pages/")[1].split("/");
const moduleName = modules[0];

// 将该模块下的所有视图组件聚合在一起
return `view-${moduleName}`;
}
},
},
},
},

css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve(
"src/style/variables.less"
)}";`,
},
math: "strict",
javascriptEnabled: true,
},
},
postcss: {
plugins: [
postCssPxToRem({
// 自适应,px>rem转换
rootValue: 192, // pc端建议:192,移动端建议:75;设计稿宽度的1 / 10
propList: ["*", "!border"], // 除 border 外所有px 转 rem // 需要转换的属性,这里选择全部都进行转换
selectorBlackList: [".norem"], // 过滤掉norem-开头的class,不进行rem转换,这个内容可以不写
unitPrecision: 5, // 转换后的精度,即小数点位数
replace: true, // 是否直接更换属性值而不添加备份属性
mediaQuery: false, // 是否在媒体查询中也转换px为rem
minPixelValue: 0, // 设置要转换的最小像素值
exclude: (file: string) => {
const whiteList = ["home/index.vue"]; // 白名单,只转换白名单内
// 如果文件路径包含白名单中的任意一项,则不排除(即进行转换)
return !whiteList.some((item) => file.includes(item));
},
}),
],
},
},

plugins: [
vue(),
vueJsx(),
viteMockServe({
mockPath: "mock",
enable: true,
}),
imagetools(),
svgLoader(),
AutoImport({
imports: ["vue", "vue-router", "pinia", "@vueuse/core"],
dts: "src/auto-imports.d.ts",
resolvers: [TDesignResolver({ library: "vue-next" })],
}),
Components({
dts: "src/components.d.ts",
resolvers: [TDesignResolver({ library: "vue-next" })],
}),
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: "gzip",
ext: ".gz",
}),
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: "brotliCompress",
ext: ".br",
}),
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
}) as any,
{
name: "generate-spa-fallback",
apply: "build",
enforce: "post",
closeBundle() {
const distIndex = path.resolve(CWD, "dist/index.html");
const distSpa = path.resolve(CWD, "dist/fallback.html");
if (existsSync(distIndex)) {
copyFileSync(distIndex, distSpa);
console.log("\nGenerated SPA fallback: dist/fallback.html");
}
},
},
{
name: "generate-sitemap",
apply: "build",
enforce: "post",
closeBundle() {
// 动态导入Sitemap生成脚本
import("./scripts/generate-sitemap.js" as string)
.then((module: any) => {
module.main();
})
.catch((error) => {
console.error("❌ Failed to generate sitemap:", error);
});
},
},
],

server: {
port: 3002,
host: "0.0.0.0",
open: true, // 这里开启自动打开浏览器是可选项
warmup: {
clientFiles: ["./src/main.ts", "./src/pages/**/*.vue"],
},
proxy: {
[VITE_API_URL_PREFIX]: {
target: VITE_API_URL,
changeOrigin: true,
rewrite: (path) => {
return path.replace(/^\/api/, "/portalSrv/api");
},
},
},
},

ssr: {
noExternal: [
"tdesign-vue-next",
"tvision-color",
"@material/material-color-utilities",
"chroma-js",
],
},

ssgOptions: {
script: "async",
formatting: "minify",
crittersOptions: false,
includedRoutes() {
// 仅包含首页
return ["/"];
},
},
};
};

💡 关键建议:

  • 不要过度优化:如果项目不大,默认配置已经足够。
  • 优先 Brotli:既然你已经折腾好了 Nginx 的 Brotli 模块,前端构建务必产出 .br 文件,这是收益最高的一项。
  • CDN 加速:对于极大的库(如 Echarts, Three.js),考虑使用 vite-plugin-cdn-import 从 CDN 加载,而不是打入本地包。

2025-09-23 【架构】vite性能优化最佳实践
https://zhangyingxuan.github.io/SEO/2025-09-23 【SEO优化】vite性能优化最佳实践/
作者
blowsysun
许可协议