Vite 模式 是做什么的?
适用于 Vite 8+ 项目的构建工具和开发服务器模式。涵盖配置、环境变量、代理设置、库模式、依赖预构建以及常见的生产陷阱。
何时使用
- 配置
vite.config.ts或vite.config.js - 设置环境变量或
.env文件 - 配置开发服务器代理用于 API 后端
- 优化构建输出(代码分割、压缩、资源)
- 使用
build.lib发布库 - 调试依赖预构建或 CJS/ESM 互操作
- 调试 HMR、开发服务器或构建错误
- 选择或排序 Vite 插件
工作原理
- 开发模式 将源文件作为原生 ESM 提供——无需打包。转换按需进行,基于每个模块请求,这就是为什么冷启动速度快且 HMR 精确。
- 构建模式 使用 Rolldown(v7+)或 Rollup(v5–v6)打包应用程序用于生产,附带树摇、代码分割和基于 Oxc 的压缩。
- 依赖预构建 通过 esbuild 将 CJS/UMD 依赖项一次性转换为 ESM,并将结果缓存到
node_modules/.vite下,因此后续启动可以跳过此步骤。 - 插件 在开发和构建之间共享统一接口——同一个插件对象同时适用于开发服务器的按需转换和生产流水线。
- 环境变量 在构建时静态内联。
VITE_前缀的变量成为包中的公共常量;未带前缀的任何变量对客户端代码不可见。
示例
配置结构
基本配置
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': new URL('./src', import.meta.url).pathname },
},
})
条件配置
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd()) // 仅包含 VITE_ 前缀的变量(安全)
return {
plugins: [react()],
server: command === 'serve' ? { port: 3000 } : undefined,
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
关键配置选项
| 键 | 默认值 | 描述 |
|---|---|---|
root | '.' | 项目根目录(index.html 所在位置) |
base | '/' | 部署资源的公共基础路径 |
envPrefix | 'VITE_' | 客户端暴露环境变量的前缀 |
build.outDir | 'dist' | 输出目录 |
build.minify | 'oxc' | 压缩器('oxc'、'terser' 或 false) |
build.sourcemap | false | true、'inline' 或 'hidden' |
插件
必要插件
大多数插件需求可以通过少数几个维护良好的包来满足。在编写自己的插件之前,请优先考虑这些。
| 插件 | 用途 | 何时使用 |
|---|---|---|
@vitejs/plugin-react-swc | 通过 SWC 实现 React HMR + Fast Refresh | React 应用的默认选择(比 Babel 变体更快) |
@vitejs/plugin-react | 通过 Babel 实现 React HMR + Fast Refresh | 仅在需要 Babel 插件时使用(emotion、MobX 装饰器) |
@vitejs/plugin-vue | Vue 3 SFC 支持 | Vue 应用 |
vite-plugin-checker | 在工作线程中运行 tsc + ESLint,并带有 HMR 覆盖层 | 任何 TypeScript 应用——Vite 在 vite build 期间不进行类型检查 |
vite-tsconfig-paths | 遵守 tsconfig.json 中的 paths 别名 | 只要您已经在 tsconfig.json 中有别名 |
vite-plugin-dts | 在库模式下生成 .d.ts 文件 | 发布 TypeScript 库 |
vite-plugin-svgr | 将 SVG 作为 React 组件导入 | 使用 SVG 作为组件的 React 应用 |
rollup-plugin-visualizer | 生成包树图/旭日图报告 | 定期进行包大小审计(使用 enforce: 'post') |
vite-plugin-pwa | 零配置 PWA + Workbox | 需要离线功能的应用 |
关键提示: vite build 会转译但不会进行类型检查。类型错误会静默地发布到生产环境,除非您添加 vite-plugin-checker 或在 CI 中运行 tsc --noEmit。
编写自定义插件
编写自定义插件的情况很少见——大多数需求已被现有插件覆盖。当您确实需要时,先在 vite.config.ts 中内联编写,只有在重用时才提取出来。
// vite.config.ts — 最小内联插件
function myPlugin(): Plugin {
return {
name: 'my-plugin', // 必需,必须唯一
enforce: 'pre', // 'pre' | 'post'(可选)
apply: 'build', // 'build' | 'serve'(可选)
transform(code, id) {
if (!id.endsWith('.custom')) return
return { code: transformCustom(code), map: null }
},
}
}
关键钩子: transform(修改源代码)、resolveId + load(虚拟模块)、transformIndexHtml(注入到 HTML 中)、configureServer(添加开发中间件)、hotUpdate(自定义 HMR——在 v7+ 中取代已弃用的 handleHotUpdate)。
虚拟模块 使用 \0 前缀约定——resolveId 返回 '\0virtual:my-id',以便其他插件跳过它。用户代码导入 'virtual:my-id'。
有关完整的插件 API,请参阅 vite.dev/guide/api-plugin。在开发过程中使用 vite-plugin-inspect 来调试转换流水线。
HMR API
框架插件(@vitejs/plugin-react、@vitejs/plugin-vue 等)会自动处理 HMR。只有在构建需要在更新之间持久化状态的自定义状态存储、开发工具或框架无关的实用程序时,才直接使用 import.meta.hot。
// src/store.ts — 手动为普通模块实现 HMR
if (import.meta.hot) {
// 在更新之间持久化状态(必须 MUTATE,绝不能重新赋值 .data)
import.meta.hot.data.count = import.meta.hot.data.count ?? 0
// 在模块被替换前清理副作用
import.meta.hot.dispose((data) => clearInterval(data.intervalId))
// 接受此模块自身的更新
import.meta.hot.accept()
}
所有 import.meta.hot 的代码都会在生产构建中被树摇移除——无需额外删除保护。
环境变量
Vite 按顺序加载 .env、.env.local、.env.[mode] 和 .env.[mode].local(后面的覆盖前面的);*.local 文件被 gitignore,用于本地机密。
客户端访问
只有 VITE_ 前缀的变量暴露给客户端代码:
import.meta.env.VITE_API_URL // 字符串
import.meta.env.MODE // 'development' | 'production' | 自定义
import.meta.env.BASE_URL // base 配置值
import.meta.env.DEV // 布尔值
import.meta.env.PROD // 布尔值
import.meta.env.SSR // 布尔值
在配置中使用环境变量
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd()) // 仅包含 VITE_ 前缀的变量(安全)
return {
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
安全
VITE_ 前缀不是安全边界
任何以 VITE_ 为前缀的变量都会在构建时静态内联到客户端包中。压缩、base64 编码和禁用 source map 都无法隐藏它。一个坚定的攻击者可以从已发布的 JavaScript 中提取任何 VITE_ 变量。
规则: 只有公共值(API URL、功能标志、公钥)才能放入 VITE_ 变量中。机密(API 令牌、数据库 URL、私钥)必须通过 API 或无服务器函数驻留在服务器端。
loadEnv('') 陷阱
// 错误:将 '' 作为第三个参数传递会加载所有环境变量——包括服务器机密——
// 并使其可通过 `define` 内联到客户端代码中。
const env = loadEnv(mode, process.cwd(), '')
// 正确:显式的前缀列表
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
生产环境中的 Source Map
生产环境中的 source map 会泄露您的原始源代码。除非您将 source map 上传到错误跟踪器(Sentry、Bugsnag)并在本地删除,否则请禁用它们:
build: {
sourcemap: false, // 默认值——保持这样
}
.gitignore 检查清单
.env.local,.env.*.local— 本地机密覆盖dist/— 构建输出node_modules/.vite— 预构建缓存(过时条目会导致幻影错误)
服务器代理
// vite.config.ts — server.proxy
server: {
proxy: {
'/foo': 'http://localhost:4567', // 字符串简写
'/api': {
target: 'http://localhost:8080',
changeOrigin: true, // 对于虚拟主机后端是必需的
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
}
对于 WebSocket 代理,向路由配置添加 ws: true。
构建优化
手动代码分割
// vite.config.ts — build.rolldownOptions
build: {
rolldownOptions: {
output: {
// 对象形式:对特定包进行分组
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
},
},
},
}
// 函数形式:根据启发式规则进行分割
manualChunks(id) {
if (id.includes('node_modules/react')) return 'react-vendor'
if (id.includes('node_modules')) return 'vendor'
}
性能
避免桶文件
桶文件(重新导出目录中所有内容的 index.ts)会强制 Vite 加载每个重新导出的文件,即使您只导入一个符号。这是官方文档指出的开发服务器速度变慢的首要原因。
// 错误——导入一个工具函数会强制 Vite 加载整个桶文件
import { slash } from '@/utils'
// 正确——直接导入,只加载一个文件
import { slash } from '@/utils/slash'
明确导入扩展名
每个隐式扩展名会通过 resolve.extensions 强制进行多达 6 次文件系统检查。在大型代码库中,这会累积起来。
// 错误
import Component from './Component'
// 正确
import Component from './Component.tsx'
将 tsconfig.json 中的 allowImportingTsExtensions 和 resolve.extensions 限制为您实际使用的扩展名。
预热热路径路由
server.warmup.clientFiles 会在浏览器请求之前预先转换已知的热门入口点——从而消除大型应用上的冷加载请求瀑布效应。
// vite.config.ts
server: {
warmup: {
clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
},
}
分析缓慢的开发服务器
当 vite dev 感觉缓慢时,从 vite --profile 开始,与应用程序交互,然后按 p+enter 保存一个 .cpuprofile。在 Speedscope 中加载它以找出哪些插件消耗了时间——通常是社区插件中的 buildStart、config 或 configResolved 钩子。
库模式
当发布 npm 包时,使用 build.lib。两个陷阱比配置细节更重要:
- 类型不会自动生成——添加
vite-plugin-dts或单独运行tsc --emitDeclarationOnly。 - 对等依赖必须外部化——未列出的对等依赖会被打包到您的库中,导致使用者出现重复运行时错误。
// vite.config.ts
build: {
lib: {
entry: 'src/index.ts',
formats: ['es', 'cjs'],
fileName: (format) => `my-lib.${format}.js`,
},
rolldownOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'], // 每个对等依赖
},
}
SSR 外部化
裸 createServer({ middlewareMode: true }) 设置属于框架作者的领域。大多数应用应使用 Nuxt、Remix、SvelteKit、Astro 或 TanStack Start 代替。作为框架用户,您可能需要在依赖项在 SSR 中出错时调整外部化配置:
// vite.config.ts — ssr 选项
ssr: {
external: ['node-native-package'], // 在 SSR 包中保持为 require()
noExternal: ['esm-only-package'], // 强制打包到 SSR 输出中(修复大多数 SSR 错误)
target: 'node', // 'node' 或 'webworker'
}
依赖预构建
Vite 预构建依赖项,以将 CJS/UMD 转换为 ESM 并减少请求数量。
// vite.config.ts — optimizeDeps
optimizeDeps: {
include: [
'lodash-es', // 强制预构建已知的重依赖
'cjs-package', // 导致互操作问题的 CJS 依赖项
'deep-lib/components/**', // 深度导入的 glob 模式
],
exclude: ['local-esm-package'], // 如果排除,则必须是有效的 ESM
force: true, // 忽略缓存,重新优化(临时调试)
}
常见陷阱
开发与构建不一致
开发使用 esbuild/Rolldown 进行转换;构建使用 Rolldown 进行打包。CJS 库在两者之间的行为可能不同。务必使用 vite build && vite preview 进行验证后再部署。
部署后的过期块
新的构建会产生新的块哈希。具有活动会话的用户请求旧文件名,而这些文件已不存在。Vite 没有内置的解决方案。缓解措施:
- 在部署窗口期间,保持旧的
dist/assets/文件可用 - 在路由器中捕获动态导入错误并强制页面重新加载
Docker 和容器
Vite 默认绑定到 localhost,这在容器外部无法访问:
// vite.config.ts — Docker/容器设置
server: {
host: true, // 绑定 0.0.0.0
hmr: { clientPort: 3000 }, // 如果在反向代理后面
}
Monorepo 文件访问
Vite 将文件服务限制在项目根目录内。根目录外的包会被阻止:
// vite.config.ts — monorepo 文件访问
server: {
fs: {
allow: ['..'], // 允许父目录(工作区根)
},
}
反模式
// 错误:将 envPrefix 设置为 '' 会将所有环境变量(包括机密)暴露给客户端
envPrefix: ''
// 错误:假设 require() 在 Vite 中有效(除非明确配置 SSR 外部化)
const fs = require('fs')
// 错误:依赖 CJS 仅在构建时有效;在开发模式下,包被作为 ESM 提供
// 错误:在生产构建中保留 source map,除非上传到错误跟踪器并在本地删除
build: {
sourcemap: true
}