Vite项目实战中遇到的常见问题与高效解决方案
我的写法,亲测靠谱
Vite 用了一年多,从最早搭内部工具、到上线三个中型业务项目(含 SSR 和微前端),踩的坑够写半本《前端运维事故录》了。不吹不黑,Vite 真快,但快得有点“脆”——配置一错,热更新就卡住;插件一乱,build 出来的东西连 console 都不打印。下面这些,全是我在 jenkins 流水线失败后、凌晨两点改 config、重启十次 dev server 才抠出来的经验。
先说最核心的一条:我永远把 vite.config.ts 拆成三份。
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
import baseConfig from './vite.config.base'
import devConfig from './vite.config.dev'
import prodConfig from './vite.config.prod'
export default defineConfig(({ command, mode }) => {
if (command === 'serve') return { ...baseConfig, ...devConfig }
return { ...baseConfig, ...prodConfig }
})
别学网上教程里那种“一个 config 文件塞 300 行”的写法。我试过,改个 alias 就得翻半天,CI 构建失败时根本分不清是 dev 插件漏了还是 prod 的 minify 规则冲突。现在 base 里只放 resolve.alias、define、plugins: [] 占位;dev 加 server.port、hmr.overlay 关闭、esbuild.logLevel 调高;prod 只加 build.rollupOptions 和 minify: 'terser'。干净,易 debug,交接给新同事也看得懂。
这几种错误写法,别再踩坑了
第一个雷:在 define 里直接写对象或函数。
// ❌ 错误!会变成字符串字面量,JSON.stringify 后注入
define: {
__API_BASE__: { host: 'https://jztheme.com', timeout: 5000 }
}
结果你在代码里 console.log(__API_BASE__.host) —— 报 undefined。因为 Vite 是做字符串替换的,不是 AST 注入。它实际替换成:var __API_BASE__ = "{ host: 'https://jztheme.com', timeout: 5000 }",纯字符串。我踩了两次,第一次以为是环境变量没生效,折腾半小时才发现文档里小字写着“仅支持 JSON-safe 值”。
✅ 正确做法:全转成字符串,运行时 JSON.parse,或者直接用 import.meta.env:
// ✅ 推荐:用 env,类型安全 + 自动注入
// .env.development
VITE_API_HOST=https://jztheme.com
VITE_TIMEOUT=5000
// 代码里
const apiHost = import.meta.env.VITE_API_HOST
第二个雷:滥用 optimizeDeps.include。
有次加了个 UI 组件库,文档说“加到 include 里能提速”,我就一股脑把所有 @ant-design/* 全塞进去。结果 dev 启动慢了 8 秒,而且某天突然 HMR 失效——改个按钮颜色,页面完全没反应。查了半天发现是 Vite 把组件库里的 esm 模块预构建成了 cjs,而该库内部用了 export * from './xxx' 动态导出,cjs 不支持,导致模块循环引用。后来我删掉所有 include,只留了一个 'lodash-es'(它确实需要),问题当场消失。
✅ 记住:90% 的项目根本不需要碰 optimizeDeps.include。真要加,只加明确有 ESM/CJS 混用问题的包,且加完必须手动跑 vite --force 清缓存再验证。
实际项目中的坑
我们有个管理后台,用 Vue 3 + TypeScript,路由懒加载 + 权限动态生成。上线后发现 build 后部分路由白屏,控制台报 Failed to fetch dynamically imported module。查 network 发现 chunk 加载 404。
原因:我们用了 build.rollupOptions.output.manualChunks 拆包,但没配 build.rollupOptions.output.entryFileNames 和 chunkFileNames 的 hash。Nginx 静态服务没开缓存失效,用户刷新时加载了旧 HTML,但新 chunk 名变了,找不到文件。
✅ 解决方案:强制加 hash,且 HTML 里用绝对路径(避免相对路径跨目录出错):
// vite.config.prod.ts
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash].[ext]'
}
},
// 必须加!否则 public 目录下 index.html 引用的 js 还是无 hash 的
manifest: true,
sourcemap: false
}
顺带一提:public 目录下的资源,比如 public/favicon.ico,Vite 默认不会 hash。如果 favicon 经常改,又不想清用户缓存,就别放 public,改成 src/assets/favicon.ico,然后在 main.ts 里动态设置:document.querySelector('link[rel="icon"]').setAttribute('href', new URL('@/assets/favicon.ico', import.meta.url).href)。虽然多一行,但可控。
还有个细节:Vite 的 server.proxy 在开发时好用,但很多人忘了它只作用于 dev server,和 build 完全无关。我们曾经在代码里写 fetch('/api/user'),dev 时 proxy 到后端,build 后部署到 Nginx,结果全 404。后来统一改成:
// api/utils.ts
export const API_BASE = import.meta.env.PROD
? 'https://jztheme.com/api'
: '/api' // dev 用 proxy,build 后 Nginx rewrite /api → 后端
export const request = (url: string, options?: RequestInit) =>
fetch(API_BASE + url, options)
然后 Nginx 配置一条:location /api { proxy_pass https://backend-server; }。前后端解耦,不用改代码。
最后一点:别迷信插件
看到 vite-plugin-svgr、vite-plugin-pwa、vite-plugin-mock… 我一开始也全装上。结果 dev 内存飙到 4GB,HMR 延迟 2 秒起。后来砍掉所有非必要插件,只留 vite-plugin-react(或 vue)、@vitejs/plugin-legacy(IE11 需求)、vite-plugin-inspect(debug 用)。mock 改用 MSW,PWA 用 workbox-cli 单独构建。速度立马回来。
以上是我总结的最佳实践,有更好的方案欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。这个方案不是最优的,但最简单,上线三个月没出过构建相关故障——对我而言,这就够了。

暂无评论