一、Vite 适用前提
Vite 适合解决的,本质上不是“怎么把前端项目跑起来”,而是“怎么让现代前端项目跑得更快、改得更顺手、构建得更轻”。如果你的项目已经进入 ESM、组件化、工程化阶段,那么 Vite 往往是非常自然的选择。
更具体地说,以下场景通常非常适合 Vite:
- 开发现代前端应用,如 Vue、React、Svelte、Solid 等项目。
- 项目依赖较多、页面较多,传统打包工具的冷启动和热更新已经开始拖慢开发节奏。
- 团队希望减少配置成本,让项目尽快进入业务开发阶段。
- 项目运行在 Monorepo 中,需要和 pnpm Workspaces、Turborepo、共享包协同工作。
- 需要同时兼顾开发阶段的极致速度和生产阶段的稳定构建产物。
以下场景则建议先评估,再决定是否采用:
- 项目必须兼容 IE11 这类传统浏览器,且无法接受额外兼容成本。
- 依赖大量历史遗留的 CommonJS、UMD 或浏览器全局脚本包,迁移成本较高。
- 已经深度绑定 Webpack 专属 loader、plugin 或内部构建链,替换的收益未必大于改造成本。
- 项目本身并不属于现代前端工程体系,例如老旧 jQuery 项目、非模块化项目。
一句话概括:Vite 不是“适合所有前端项目”,但它非常适合现代前端项目。
二、Vite 简介
Vite 由尤雨溪发起,核心目标是提升现代前端开发体验。它最重要的设计思路有两个:
- 开发环境尽量不做整包打包,而是利用浏览器原生 ESM 做按需加载。
- 生产环境交给成熟的打包器完成优化,保证上线产物的稳定性和性能。
传统工具在开发时,往往需要先把整个依赖图打包一遍,项目越大,等待越久。Vite 则把这件事拆开了:
- 对第三方依赖做预构建,避免浏览器一次次解析庞杂的依赖树。
- 对业务源码按请求即时转换,浏览器请求到哪个模块,服务端就处理哪个模块。
- 当文件变更时,只让受影响的模块热更新,而不是重新打整个包。
这让 Vite 在开发阶段通常表现为:冷启动很快、HMR 很快、配置更少、对主流框架支持成熟。
同时,Vite 并不是只管“开发快”,它在生产环境下会走完整构建流程,支持代码分割、资源压缩、Tree-Shaking、静态资源处理等工程化能力。因此它更准确的定位不是“只适合本地开发的小工具”,而是一个完整的现代前端构建方案。
Vite 核心特性
mindmap
root((Vite))
开发体验
秒级冷启动
精准 HMR
原生 ESM
工程能力
插件体系
环境变量
代理转发
静态资源处理
生产构建
Rollup 打包
Tree Shaking
代码分割
资源压缩
协作场景
Monorepo
共享包
多应用
- 极速冷启动:开发服务器启动时,不需要先把整个应用完整打包。
- 高效 HMR:模块变更后尽量只更新受影响部分,反馈更快。
- 原生 ESM 支持:开发期直接拥抱现代浏览器模块能力。
- 插件体系清晰:既能使用 Vite 插件,也能复用一部分 Rollup 插件能力。
- 生产构建稳定:上线阶段仍然具备成熟打包优化能力,而不是把开发模式直接搬到生产环境。
- Monorepo 友好:在多应用、多共享包协作中表现自然。
三、Vite 与其他前端构建工具对比
Vite 并不是为了“全面取代所有工具”而出现的,它更像是在现代前端语境下,对开发速度与工程复杂度做了一次重新平衡。
| 工具 | 核心定位 | 优势 | 劣势 | 更适合的场景 |
|---|---|---|---|---|
| Vite | 现代前端开发与构建工具 | 冷启动快、HMR 快、配置轻、开发体验好 | 对老旧生态兼容成本较高 | Vue、React、Monorepo、现代 Web 应用 |
| Webpack | 高可定制的全能构建工具 | 插件生态深、定制能力强、兼容历史项目能力好 | 配置复杂、开发期速度通常偏慢 | 历史项目、深度定制构建链、大型存量工程 |
| Parcel | 零配置构建工具 | 上手简单、适合原型验证 | 大型项目可控性与生态相对弱一些 | 小型项目、快速试验 |
四、Vite 快速上手
这一部分先解决“怎么把项目跑起来”,再顺手把几个最常见的坑放进对应步骤里说明,避免读者先照着命令敲,后面再回头查问题。
1. 前置准备
建议先确认两件事:
- 使用受当前 Vite 版本支持的 Node.js 版本,优先选择当前 LTS。
- 包管理器保持团队一致,本文以 pnpm 为例。
# 查看 Node.js 版本
node -v
# 查看 pnpm 版本
pnpm -v
如果后续运行时报出与 ESM、依赖解析或语法转换有关的问题,第一步通常不是急着改配置,而是先确认 Node.js 版本是否匹配。
2. 创建项目
# 交互式创建
pnpm create vite@latest
# 创建 Vue 项目
pnpm create vite@latest my-vite-vue -- --template vue
# 创建 Vue + TypeScript 项目
pnpm create vite@latest my-vite-vue-ts -- --template vue-ts
# 创建 React 项目
pnpm create vite@latest my-vite-react -- --template react
# 创建 React + TypeScript 项目
pnpm create vite@latest my-vite-react-ts -- --template react-ts
创建完成后,模板通常已经帮你准备好了基础的 vite.config.*、入口文件和启动脚本。大多数情况下,不需要一上来就改配置,先跑起来再说,效率反而更高。
3. 安装依赖并启动开发服务
cd my-vite-vue
pnpm install
pnpm dev
默认情况下,Vite 会启动本地开发服务器,并输出访问地址。项目启动后,修改源码通常会立即在浏览器中体现。
如果你发现“代码改了但页面没有热更新”,优先检查这些点:
- 当前修改的是不是被模块系统接管的文件,而不是
public/里的静态文件。 - 项目路径、文件路径是否包含一些容易引发工具链兼容问题的特殊字符。
- 是否有编辑器、虚拟机、网络盘等环境影响文件监听。
- 是否误配了
server.hmr,或者缓存异常,此时可尝试删除node_modules/.vite后重启。
4. 生产构建与预览
pnpm build
pnpm preview
pnpm build用于生成生产环境产物,默认输出到dist/。pnpm preview用于本地预览构建结果,帮助你在部署前发现路径、资源引用等问题。
如果构建没报错,但打开页面后是空白页,先别急着怀疑业务代码,优先检查:
- 是否部署在子路径下,却没有正确设置
base。 - 是否手写了以
/开头的绝对资源路径,导致部署后资源地址错误。 - 是否把开发期代理当成了生产期能力,导致接口地址在上线后失效。
5. 单应用目录结构
my-vite-vue/
├── public/ # 原样拷贝到构建产物中的静态资源
├── src/ # 业务源码
│ ├── assets/ # 会进入构建流程的资源
│ ├── components/
│ ├── views/
│ ├── App.vue
│ └── main.ts
├── .env # 所有模式共享的环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── index.html # Vite 的 HTML 入口
├── package.json
├── tsconfig.json
└── vite.config.ts
这里有一个很容易忽略的区别:
public/中的资源不会经过打包器处理,适合放 favicon、robots.txt 这类稳定文件。src/assets/中的资源会参与构建、哈希命名和依赖分析,更适合放业务图片、字体、样式资源。
五、配置文件介绍
很多人第一次接触 Vite 时,会把注意力都放在 vite.config.ts 上。其实真正影响项目体验的,往往不是某一个配置文件,而是几个文件共同组成的协作关系。
graph LR
A[index.html] --> B[Vite Dev Server]
C[vite.config.ts] --> B
D[.env.*] --> B
E[tsconfig.json] --> F[TypeScript 工具链]
B --> G[src 业务代码]
H[.gitignore] --> I[仓库整洁度]
可以把它们理解成这样:
vite.config.*决定 Vite 怎样开发、怎样构建。tsconfig.*决定 TypeScript 怎样检查、怎样解析模块。.env*决定不同环境下可注入哪些变量。index.html是应用真正进入浏览器的入口。.gitignore决定哪些本地产物不该进入版本库。
1. vite.config.ts:项目的构建与开发中枢
这是最核心的配置文件。一个相对完整、适合讲解的示例如下:
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'node:path'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
const rootDir = fileURLToPath(new URL('.', import.meta.url))
return {
base: env.VITE_PUBLIC_BASE || '/',
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(rootDir, './src')
}
},
server: {
host: '0.0.0.0',
port: 5173,
open: true,
proxy: {
'/api': {
target: env.VITE_API_TARGET,
changeOrigin: true,
rewrite: (url) => url.replace(/^\/api/, '')
}
}
},
preview: {
port: 4173
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue']
}
}
}
}
}
})
这份配置里最常用、也最值得理解的字段有:
base:默认值是/。只有应用部署到子目录或 CDN 前缀下时通常才需要改;一旦配错,最常见表现就是资源 404、页面空白。plugins:默认只保留框架必需插件即可。只有要接入额外文件类型或增强能力时再扩展,插件越多,调试和升级成本越高。resolve.alias:默认可以不用配。只有相对路径过深、共享包较多、Monorepo 协作时才特别值得加;如果只在 Vite 中配置、不在 TS 中同步,编辑器会先“闹脾气”。server:开发服务器相关配置,包括端口、host、代理、HMR 等。通常在本地联调、多端口开发或局域网调试时修改。preview:本地预览构建产物的配置。大多数项目很少改,但它适合用来模拟部署前的最终访问效果。build:生产构建配置,如输出目录、sourcemap、分包策略、压缩方式等。只有当你开始关心部署目录、调试能力和体积优化时,才需要逐步细调。
几个高频注意点也建议直接记住:
proxy只在开发服务器阶段生效,不会带到生产环境。base一旦配置错误,最常见表现就是“打包正常,但部署后资源 404 或页面空白”。- 如果项目是 ESM 配置环境,路径工具建议使用
node:path,并留意__dirname在不同模块模式下的写法差异。 - 某些老旧第三方包如果不能被顺利解析,优先检查包本身的模块格式,再考虑
optimizeDeps、build.commonjsOptions或替代包,而不是一上来堆插件。
2. tsconfig.json:类型检查与路径解析的基础
如果项目使用 TypeScript,那么 tsconfig.json 不只是“让 TS 不报错”,它还会影响编辑器提示、路径别名识别、模块解析方式和团队协作体验。
示例:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"types": ["vite/client"]
},
"include": ["src", "vite.config.ts"]
}
重点说明:
moduleResolution: "Bundler"更贴近 Vite 的现代解析方式,适合新项目。types: ["vite/client"]可以让import.meta.env等 Vite 提供的类型提示生效。- 如果你在
vite.config.ts中配了@别名,但tsconfig.json没同步paths,编辑器通常会提示路径找不到,虽然项目未必立刻跑不起来,但协作体验会很差。
从使用角度看,可以把它理解得更简单一点:
- 默认情况下,脚手架生成的 TypeScript 配置已经够启动项目,没必要一开始就把它改得很“满”。
- 当你开始配置路径别名、提高类型严格度、拆分
tsconfig.app.json和tsconfig.node.json时,说明项目已经从“能跑”进入“更适合协作”阶段。 - 如果
tsconfig改错了,最常见的现象不是页面直接报错,而是编辑器提示异常、路径解析失效、import.meta.env没类型,或者 Node 侧脚本类型检查不对劲。
如果是更完整的工程,常见做法还包括:
- 用
tsconfig.app.json管应用源码。 - 用
tsconfig.node.json管 Node 侧脚本和配置文件。 - 根
tsconfig.json只做共享继承。
这在 Monorepo 或大型项目中会更清晰。
3. .env 系列文件:环境变量的边界
Vite 支持使用 .env、.env.local、.env.development、.env.production 等文件管理环境变量。
常见示例:
VITE_APP_TITLE=管理后台
VITE_API_TARGET=http://localhost:8080
VITE_PUBLIC_BASE=/
在业务代码中可以这样访问:
console.log(import.meta.env.VITE_APP_TITLE)
console.log(import.meta.env.VITE_API_TARGET)
这里最容易踩的坑只有一个,但非常高频:想在前端代码里访问的变量,必须以 VITE_ 开头。
如果你写成:
API_URL=http://localhost:8080
那么在前端代码里默认是拿不到的。这样设计的目的,是明确区分“可以暴露给前端”的变量和“只应存在于 Node/服务端上下文”的变量。
另外还要注意两点:
.env.local、.env.development.local这类本地私有变量通常不应提交到仓库。- 环境变量改动后,通常需要重启开发服务器,才能保证配置完全生效。
如果只想抓住最实用的原则,记住这三条就够了:
- 默认把
.env当作“按环境切换前端运行参数”的地方,而不是“存放秘密”的地方。 - 只有真的需要因环境不同而变化的值,才值得进入
.env,例如接口基地址、站点标题、功能开关。 - 如果变量写了却取不到,优先怀疑前缀、模式文件是否写对,以及开发服务器是否已经重启。
4. index.html:Vite 的真正入口
和很多传统构建工具不同,Vite 把 index.html 也纳入了构建体系,而不是仅仅把它当作一个静态模板文件。
常见写法:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%VITE_APP_TITLE%</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
它的重要性体现在三个方面:
- 它决定浏览器最先加载哪个模块入口。
- 它可以直接参与 Vite 的变量替换和 HTML 转换流程。
- 它往往也是部署路径问题最先暴露的地方。
如果项目部署到子路径,index.html 中的资源引用方式和 base 配置必须一致,否则就容易出现首页能开、资源却加载失败的问题。
在实际项目里,index.html 通常只会在这些时候改动:
- 调整页面标题、描述信息、SEO 元信息。
- 修改入口脚本或接入多页面结构。
- 插入极少量必须在首屏前执行的脚本。
如果这里改错了,最常见的表现是入口脚本没加载、标题变量没替换,或者部署后路径和资源地址对不上。
5. .gitignore:工程卫生的一部分
.gitignore 看起来不起眼,但它直接影响仓库是否干净、协作是否省心。
推荐内容如下:
# 依赖
node_modules/
.pnpm-store/
# 构建产物
dist/
dist-ssr/
coverage/
# 本地环境变量
.env*.local
# 日志
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Vite 缓存
node_modules/.vite/
# 编辑器
.idea/
.vscode/
*.swp
*.swo
尤其是 dist/、node_modules/、.env*.local,几乎不应该进入版本库。前者会污染提交历史,后者可能带来安全和协作风险。
六、Vite 核心能力实操
理解完配置文件以后,再来看 Vite 的几个高频实战能力,会更容易建立“配置为什么这样写”的感觉。
1. 常见资源在 Vite 中的处理方式
如果想快速建立对 Vite 的整体认知,最有效的方法不是先背配置,而是先看“不同资源进入 Vite 后会发生什么”。
| 资源类型 | 开发阶段 | 生产构建后 | 你需要记住的点 |
|---|---|---|---|
main.ts、main.js | 按请求即时转换为浏览器可执行模块 | 进入打包入口,参与代码分割 | 它们决定应用从哪里启动 |
.vue、.tsx、.jsx | 先经过插件转换,再交给浏览器加载 | 被打进构建产物,和依赖一起优化 | 框架文件几乎都依赖对应插件 |
.css、.scss、.less | 可以直接被导入,修改后支持热更新 | 被抽取、合并、压缩 | 样式也是模块图的一部分,不是“额外附属物” |
src/ 下图片、字体、SVG | 会参与依赖分析和资源处理 | 一般会生成带哈希的资源文件 | 适合放业务资源,便于缓存控制 |
public/ 下静态文件 | 原样对外提供,不参与模块转换 | 原样拷贝到产物目录 | 适合放 favicon、robots.txt 这类稳定资源 |
.json | 可直接导入并参与模块图 | 会进入打包流程 | 适合小型静态配置,不适合大体量动态数据 |
把这张表记住后,很多问题都会自然变得好判断:
- 某个文件为什么能热更新,是因为它进入了模块图。
- 某个资源为什么带哈希,是因为它走了构建处理链。
- 某个文件为什么只能通过 URL 访问,是因为它在
public/下而不是src/下。
2. CSS 与静态资源处理
Vite 对 CSS 的态度非常“现代前端化”:样式不是构建流程外的补充,而是和 JavaScript、组件一样,直接纳入模块系统。
最常见的几种写法如下:
import './styles/base.css'
import './styles/theme.scss'
import logoUrl from './assets/logo.png'
普通 CSS
普通 CSS 文件可以直接导入,开发阶段支持热更新,生产阶段会参与压缩与提取。
CSS Modules
如果文件名是 *.module.css、*.module.scss,它会被当作 CSS Modules 处理,适合组件级样式隔离:
import styles from './button.module.css'
console.log(styles.primary)
预处理器
如果你要用 Sass、Less、Stylus,只需要安装对应依赖即可,Vite 会把它们接入处理流程:
pnpm add -D sass
资源引用
在 CSS 里通过 url(...) 引用的图片、字体,只要它们位于 src/ 下,通常也会进入资源处理链;如果你希望某个文件完全原样保留,再考虑放到 public/。
这里最值得记住的不是配置细节,而是两条经验:
- 样式文件能被
import,说明它和脚本、组件一样,都是 Vite 模块图的一部分。 src/里的资源适合参与构建优化,public/里的资源适合保持稳定 URL,这两类资源不要混着理解。
3. 热模块替换(HMR)
Vite 默认已经内置 HMR,Vue、React 这类主流框架的日常开发一般无需额外配置。
如果你需要定制一些行为,可以这样写:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
hmr: {
overlay: true,
timeout: 3000
}
}
})
这里的重点不是“怎么把 HMR 配出来”,而是理解它什么时候会失效或看起来像失效:
- 修改的是不会进入模块图的文件。
- 文件监听环境不稳定。
- 某些状态不是热更新能安全保留的,框架会主动触发整页刷新。
- 插件转换链不完整,导致模块边界判断异常。
因此,看到热更新表现不符合预期时,先判断是“完全不触发”,还是“触发了但退化成整页刷新”,排查方向会更清楚。
4. 环境变量
# .env.development
VITE_API_BASE_URL=http://localhost:8080/api
VITE_APP_TITLE=开发环境 - 我的应用
console.log(import.meta.env.VITE_API_BASE_URL)
console.log(import.meta.env.VITE_APP_TITLE)
console.log(import.meta.env.DEV)
console.log(import.meta.env.PROD)
在实际项目里,比较推荐的做法是:
- 用环境变量控制接口基地址、应用标题、资源前缀等“环境相关但不涉密”的信息。
- 不把真正的服务端密钥放进前端环境变量里,因为最终仍会进入前端产物。
- 约定命名规则,并在团队内保持统一,避免有人写
VITE_API_URL,有人写VITE_BASE_API。
5. 插件使用
插件是 Vite 可扩展性的核心,但在教程语境里,更重要的不是“列出很多插件”,而是先建立一个克制的选择顺序。
可以先把常见插件分成三类:
- 框架必需插件:例如 Vue 项目的
@vitejs/plugin-vue、React 项目的@vitejs/plugin-react。这类插件通常是“项目正常工作”的基础。 - 工程增强插件:例如自动导入、路径别名增强、SVG 转组件、检查器、可视化分析等。它们提升开发体验,但不是项目起步必需。
- 场景型插件:例如文件系统路由、PWA、Mock、特定平台适配插件。这类插件通常只在明确业务场景下才需要。
对于大多数读者,最稳妥的起点反而很简单:先只用官方框架插件,把项目跑顺,再按场景加插件。
以 Vue 项目为例,最小可用配置通常就是:
pnpm add -D @vitejs/plugin-vue
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()]
})
选择插件时建议保持一个原则:先保证必要,再追求丰富。
因为插件越多,意味着:
- 转换链越长。
- 调试成本越高。
- 升级时的兼容点越多。
如果某个旧包在 Vite 里表现不好,不一定要立刻找一个“兼容插件”去补。更稳妥的顺序通常是:
- 确认包是否仍在维护。
- 确认它导出的模块格式。
- 再决定是否通过 Vite 配置、插件或替代方案解决。
换句话说,好的插件策略不是“看到好用就装”,而是“先有明确问题,再引入明确工具”。
6. 生产构建优化
import { defineConfig } from 'vite'
export default defineConfig({
build: {
minify: 'esbuild',
sourcemap: false,
rollupOptions: {
output: {
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
manualChunks: {
framework: ['vue']
}
}
}
}
})
优化时最常见的三个方向是:
- 减体积:合理分包、移除无用依赖、按需加载。
- 提稳定性:避免把过于激进的压缩或分包策略直接上线。
- 提可观测性:按需保留 sourcemap,方便排错。
如果应用部署在二级目录,例如 https://example.com/admin/,务必补上:
export default defineConfig({
base: '/admin/'
})
很多“本地一切正常,线上白屏”的问题,根因都在这里。
7. 与后端联调
以前后端分离开发为例,可以通过代理转发解决本地跨域和接口地址切换问题:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
这样前端请求 /api/user 时,会由开发服务器转发给后端服务。
这里要特别注意:
- 这只是开发期代理,不是生产环境网关。
- 如果代理配置正确却仍然报跨域,往往不是 Vite 本身失效,而是请求没有真正经过 Vite 开发服务器,例如你请求了完整后端地址。
- 如果后端接口本身依赖特定前缀,就不要盲目
rewrite,否则可能把路径改坏。
七、Monorepo 场景使用
Vite 在 Monorepo 中很常见,因为它本身只负责前端应用的开发和构建,而不试图接管整个仓库的任务编排。这一点和 pnpm、Turborepo 正好互补。
graph LR
A[pnpm Workspaces] --> B[apps/web]
A --> C[apps/admin]
A --> D[packages/ui]
A --> E[packages/utils]
F[Turborepo] --> B
F --> C
B --> D
C --> D
B --> E
C --> E
linkStyle 0 stroke:#4285F4,stroke-width:2px
linkStyle 1 stroke:#4285F4,stroke-width:2px
linkStyle 2 stroke:#4285F4,stroke-width:2px
linkStyle 3 stroke:#4285F4,stroke-width:2px
linkStyle 4 stroke:#0F9D58,stroke-width:2px
linkStyle 5 stroke:#0F9D58,stroke-width:2px
linkStyle 6 stroke:#F4B400,stroke-width:2px
linkStyle 7 stroke:#F4B400,stroke-width:2px
linkStyle 8 stroke:#DB4437,stroke-width:2px
linkStyle 9 stroke:#DB4437,stroke-width:2px
1. 推荐目录结构
monorepo-project/
├── apps/
│ ├── web/ # Vite + Vue
│ ├── admin/ # Vite + React
│ └── api/ # 后端服务
├── packages/
│ ├── ui/ # 共享组件库
│ └── utils/ # 共享工具库
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
2. 创建应用
cd apps
pnpm create vite@latest web -- --template vue
pnpm create vite@latest admin -- --template react
3. 配置 pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
相比把每个包都手写出来,使用通配写法通常更易维护;除非你的仓库结构非常特殊,否则没必要把每个应用路径一条条写死。
4. 配置 turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"dev": {
"cache": false,
"persistent": true
},
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"lint": {},
"test": {}
}
}
这里要注意一个细节:如果示例写的是 JSON,就不要混入注释,否则严格 JSON 解析会失败。很多教程为了讲解方便把注释直接写进 json 代码块,读者复制后反而容易踩坑。
5. 共享包与别名
如果应用需要引用 packages/ui,可以在 Vite 和 TypeScript 中同时配置别名。例如:
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
resolve: {
alias: {
'@ui': fileURLToPath(new URL('../../packages/ui/src', import.meta.url))
}
}
})
与此同时,也要在 tsconfig.json 中同步 paths,否则运行时能找到、编辑器却报错,是 Monorepo 新手最常见的不协调体验之一。
八、生产部署与协作建议
这一章不再单独做“常见问题”,而是把真正影响上线和协作的点提炼成几个部署前应确认的结论。
1. 部署前至少确认三件事
base是否与真实部署路径一致。- 构建后的资源是否都通过
pnpm preview本地验证过。 - 环境变量是否区分了“前端可见”和“仅服务端可见”。
2. 不要把开发代理当成上线方案
server.proxy 很方便,但它只是开发阶段的本地转发。真正上线后,请求通常需要由以下角色接手:
- Nginx
- 网关层
- BFF
- 后端服务本身
如果团队成员误以为“本地代理配好了,线上也会自动转发”,那部署后大概率会遇到接口 404 或跨域问题。
3. 让构建结果可验证、可复现
建议至少保留这些脚本:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
保持脚本直白有一个好处:无论是本地调试、CI 构建还是排查问题,大家看到的入口都一致,不容易因为包装层太多而失去判断依据。
九、Vite 核心架构与工作流程
理解 Vite 最好的方式,不是背配置项,而是搞清楚它在开发和生产两条链路里分别做了什么。
1. 整体架构:开发与构建分离
graph TB
A[源码与配置] --> B[Vite]
B --> C[开发服务器链路]
B --> D[生产构建链路]
C --> E[依赖预构建]
C --> F[源码按需转换]
C --> G[HMR 通知]
D --> H[Rollup 打包]
D --> I[Tree-Shaking]
D --> J[代码分割]
D --> K[产出 dist]
这张图可以概括 Vite 的核心设计:
- 开发阶段 关注“快反馈”,所以按需处理源码。
- 生产阶段 关注“可交付”,所以走完整打包与优化流程。
这也是为什么 Vite 的体验常常是“双相”的:
- 本地开发时像一个很聪明、很轻的服务器。
- 生产构建时又像一个成熟的工程化打包器前端。
2. 开发环境工作流程
当你执行 pnpm dev 时,Vite 大致会经历下面的过程:
flowchart TD
A[执行 pnpm dev] --> B[读取 vite.config.*]
B --> C[加载插件]
C --> D[扫描并预构建依赖]
D --> E[启动 Dev Server]
E --> F[浏览器请求 index.html]
F --> G[返回入口 HTML]
G --> H[浏览器继续请求 src/main.ts 等模块]
H --> I[Vite 按需转换源码为浏览器可执行模块]
I --> J[浏览器原生 ESM 加载]
J --> K[监听文件变化]
K --> L[变更后发送 HMR 更新]
这里最关键的是三件事:
(1)依赖预构建不是“完整打包项目”
Vite 在开发阶段并不是完全不处理依赖。它通常会先把第三方依赖做一次预构建,主要目的包括:
- 把 CommonJS、UMD 等包整理成更适合浏览器处理的 ESM 形式。
- 把一个包内部过深、过碎的模块引用收敛起来,减少浏览器请求压力。
- 利用缓存,让后续启动更快。
这一步通常由 esbuild 参与完成,所以速度很高。
(2)源码是“按请求转换”的
浏览器请求 src/main.ts,Vite 才去转换 main.ts;请求到某个 Vue 组件,再去处理对应的 SFC;请求到 CSS、图片、JSON,也按相应规则返回转换后的结果。
因此开发阶段的等待时间,不再集中爆发在“先打完整个包”,而是分散到“请求到哪里,处理到哪里”。
(3)HMR 依赖的是模块图,而不是简单文件刷新
Vite 内部会维护模块之间的依赖关系。当某个文件变化时,它会判断:
- 哪些模块受影响。
- 这些模块能否局部替换。
- 如果不能安全替换,是否需要退化成整页刷新。
所以 HMR 的快,不只是因为“监听到了文件变化”,更因为它知道“应该通知谁更新”。
3. 插件体系在流程中的位置
插件不是附属功能,而是 Vite 工作流的重要组成部分。
graph LR
A[浏览器请求模块] --> B[resolveId]
B --> C[load]
C --> D[transform]
D --> E[返回可执行内容]
你可以把插件理解成一组介入点:
resolveId:决定某个导入路径最终解析到哪里。load:决定某个模块内容从哪里来。transform:决定源码如何被转换。
Vue 单文件组件、React Fast Refresh、SVG 转组件、Markdown 转页面,本质上都建立在这类机制之上。
这也解释了为什么插件顺序有时会影响结果:因为它们不是简单“并排存在”,而是在同一条解析与转换链上协作。
4. 生产构建工作流程
当你执行 pnpm build 时,Vite 的目标就从“快速反馈”切换成“稳定交付”:
flowchart TD
A[执行 pnpm build] --> B[读取配置与模式]
B --> C[加载插件]
C --> D[Rollup 构建模块图]
D --> E[Tree-Shaking]
E --> F[代码分割]
F --> G[处理 CSS 与静态资源]
G --> H[压缩与产物输出]
H --> I[生成 dist]
此时关注点变成了:
- 有没有无用代码可以移除。
- 首屏资源是否合理拆分。
- 公共依赖是否应该单独抽离。
- 资源文件是否应加哈希,便于缓存。
换句话说,开发阶段的“快”来自于少做无意义的整包工作;生产阶段的“稳”来自于把该做的优化认真做完。
5. 用一句话串起整套机制
如果要用一句话概括 Vite 的架构理念,可以这样理解:
开发时尽量把工作推迟到真正被请求的那一刻,构建时再把所有应该系统化完成的优化集中做完。
理解这句话之后,再看 vite.config.ts 中的 server、plugins、build、resolve 等配置,你会更容易判断它们分别影响的是哪一条链路,以及某个问题应该去开发链路排查,还是去生产链路排查。