Vite 构建工具实践指南,涵盖配置文件、插件开发、热更新 (HMR)、环境变量、代理设置、SSR、库模式 (Library Mode)、依赖预构建以及构建打包优化。当处理 vite.config.ts、Vite 插件或基于 Vite 开发的项目时激活此 Skill。
Vite 实践指南 (Vite Patterns)
适用于 Vite 8+ 项目的构建工具与开发服务器最佳实践指南。内容涵盖配置项解析、环境变量管理、开发代理设置、库模式打包、依赖预构建以及常见的生产环境踩坑避坑指南。
适用场景
- 配置
vite.config.ts或vite.config.js时 - 设置环境变量或
.env文件时 - 配置开发服务器代理(Dev Server Proxy)对接 API 后端时
- 优化生产打包产物(分包 Chunk、代码压缩 Minification、静态资源 Asset 处理)时
- 使用
build.lib发布 npm 库时 - 排查依赖预构建或 CJS/ESM 模块兼容性问题时
- 调试热更新 (HMR)、开发服务器或构建报错时
- 选择或配置 Vite 插件的加载与执行顺序时
工作原理
- 开发模式 (Dev mode):直接将源码作为原生 ESM 提供服务——完全无需打包(Bundling)。模块转换按需随请求触发,这也是冷启动极快且 HMR 热更新精准的根本原因。
- 构建模式 (Build mode):在 v7+ 版本中使用 Rolldown(v5–v6 版本使用 Rollup)进行生产环境打包,内置 Tree-shaking、代码分割(Code-splitting)以及基于 Oxc 的高效产物压缩。
- 依赖预构建 (Dependency pre-bundling):通过 esbuild 首次运行将 CommonJS/UMD 依赖一次性转换为 ESM 格式,并将结果缓存到
node_modules/.vite,后续启动直接跳过该阶段。 - 插件机制 (Plugins):在开发与构建阶段共享统一的插件接口——同一个插件对象既能处理开发服务器的按需转换,也能无缝接入生产打包流水线。
- 环境变量 (Environment variables):在构建时会被静态硬编码(Inline)注入到代码中。带有
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 |
SourceMap 配置(true、'inline' 或 'hidden') |
插件 (Plugins)
常用核心插件
绝大多数插件需求都可以通过社区成熟且维护良好的官方/第三方包解决,在尝试自己编写插件前建议优先使用以下包:
| 插件 | 功能说明 | 适用场景 |
|---|---|---|
@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 |
在 Worker 线程中运行 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 组件直接 import | 需要将 SVG 当作组件调用的 React 项目 |
rollup-plugin-visualizer |
生成打包体积分析图(Treemap/Sunburst 报告) | 定期做打包体积审计时使用(建议配置 enforce: 'post') |
vite-plugin-pwa |
零配置生成 PWA + Workbox 支持 | 具备离线使用能力的 Web 应用 |
极其重要的坑点提醒: vite build 只负责语法转译(Transpile),完全不进行类型检查(Type-check)。如果不配置 vite-plugin-checker 或者在 CI 中追加 tsc --noEmit,类型报错也会被静默打包并直接上到生产环境!
编写自定义插件
编写自定义插件的情况其实非常少见——绝大多数需求已有现成插件。如果确实需要定制,建议先在 vite.config.ts 中以内联形式(Inline)编写,后续确认有复用需求时再抽离。
// 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 }
},
}
}
核心 Hook 说明: transform(源码修改)、resolveId + load(虚拟模块解析与加载)、transformIndexHtml(往 HTML 注入代码)、configureServer(扩展开发服务器中间件)、hotUpdate(自定义 HMR,在 v7+ 中替代了已废弃的 handleHotUpdate)。
虚拟模块(Virtual modules): 惯例使用 \0 前缀——resolveId 返回 '\0virtual:my-id' 从而避免被后续其他插件二次拦截处理。而在业务代码中正常使用 import '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) {
// 跨热更新保存模块状态(必须修改现有属性,绝不要重新给 .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 相关代码在生产环境打包时会被自动 Tree-shaking 裁切掉,无需手动写防御判断。
环境变量
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_ 前缀的环境变量,在构建阶段都会被直接静态硬编码(Inline)注入到客户端打包代码中。不管是代码压缩、Base64 编码还是关闭 Source Map,都无法阻止变量泄露。有经验的攻击者可以轻松从你发布的 JavaScript 静态资源中还原出所有 VITE_ 变量。
铁律: 只有公开信息(API 服务地址、功能开关 Feature Flag、公钥等)才能放到 VITE_ 变量中。敏感信息(API Token、数据库连接串、私钥等)必须保留在服务端,通过后台 API 或 Serverless 函数托管访问。
loadEnv('') 的安全陷阱
// 错误写法:第三个参数传空字符串 '' 会加载所有环境变量(包含服务端密钥!),
// 并且极易通过 `define` 不小心硬编码暴露到前端代码中。
const env = loadEnv(mode, process.cwd(), '')
// 正确写法:明确指定安全的前缀白名单列表
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
生产环境 Source Maps
生产环境输出的 Source Map 会直接暴露你的源代码细节。除非打包后会自动上传到错误监控平台(如 Sentry、Bugsnag)并在本地立即销毁,否则请始终关闭:
build: {
sourcemap: false, // 默认关闭 — 保持此默认配置
}
.gitignore 安全自查清单
.env.local,.env.*.local— 本地密钥重写文件dist/— 构建打包产物目录node_modules/.vite— 依赖预构建缓存目录(残留的旧缓存极易引起诡异的报错)
服务端代理 (Server Proxy)
// 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 即可。
构建打包优化
手动分包 (Manual Chunks)
// 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'
}
性能优化
规避 Barrel 文件(桶文件/统一导出文件)
Barrel 文件(即在 index.ts 中把某个目录下的所有模块集中重新导出)会导致 Vite 即便只需要引入单个函数,也必须强行加载并解析该目录下所有导出的模块。官方文档明确指出这是导致开发服务器变慢的第一大诱因。
// 避坑写法 — 哪怕只用一个工具函数,也会迫使 Vite 加载整桶模块
import { slash } from '@/utils'
// 推荐写法 — 精确按需引入,只加载对应的文件
import { slash } from '@/utils/slash'
显式指定 import 路径扩展名
省去文件扩展名会导致 Vite 每次解析路径时执行多达 6 次文件系统查找(通过 resolve.extensions)。在大中型项目积累下来,性能开销非常巨大。
// 避坑写法
import Component from './Component'
// 推荐写法
import Component from './Component.tsx'
建议在 tsconfig.json 中收紧 allowImportingTsExtensions 并将 resolve.extensions 严格缩减至项目真正用到的扩展名。
预热关键路径路由 (Warm-Up Hot-Path Routes)
通过配置 server.warmup.clientFiles,让 Vite 在浏览器发起网络请求之前预先转换已知的高频热点入口模块——能有效消灭大型应用冷启动时的请求瀑布流(Waterfall)。
// vite.config.ts
server: {
warmup: {
clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
},
}
排查卡顿的开发服务器
当感到 vite dev 运行迟缓时,可以通过运行 vite --profile 启动,并在页面上进行常规交互操作后,按下 p + Enter 保存 CPU 采样文件 .cpuprofile。将其拖入 Speedscope 进行可视化分析,排查是哪个插件拖累了速度——常见原因多出自第三方社区插件的 buildStart、config 或 configResolved 钩子。
库模式打包 (Library Mode)
当使用 Vite 打包发布 npm 组件库/工具库时,配置 build.lib 是核心操作。比起琐碎的配置参数,更需要警惕以下两大“天坑”:
- 默认不会生成类型声明文件 — 必须补充
vite-plugin-dts插件,或单独运行tsc --emitDeclarationOnly。 - Peer Dependencies 必须手动配置 external 剔除 — 没在 external 中排除的同伴依赖会被强行打包进你的库代码中,导致使用方在运行时因出现多份同名依赖实例而报 Duplicate Runtime 错误。
// 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'], // 必须列出所有 peer 依赖
},
}
SSR 外置依赖 (SSR Externals)
使用裸 createServer({ middlewareMode: true }) 进行极简搭建通常是应用级框架作者才需要接触的底层领域。绝大多数项目应当直接选择基于 Nuxt、Remix、SvelteKit、Astro 或 TanStack Start 开发。而作为框架使用者,当依赖在 SSR 阶段打不出期望结果时,你唯一需要手动调整的主要是 externals 配置:
//
<!-- truncated for translation batch; full body continues in source -->






