认识 Vite:第三代构建工具
Q: Vite 到底是脚手架还是构建工具?它的核心优势是什么?
A: Vite 既是脚手架也是构建工具,两者的界限在现代前端工具链中已经模糊。它可以搭建各种类型(Vue、React、Svelte、Solid.js)的项目,同时在内部包含两个构建工具——esbuild(用于开发环境)和rollup(用于生产环境)。
构建工具的演进
| 代际 | 代表工具 | 核心能力 |
|---|---|---|
| 第一代 | npm scripts、grunt、gulp | 编译、合并、压缩等基础任务 |
| 第二代 | webpack、rollup、parcel | 模块依赖处理、合并打包 |
| 第三代 | Vite、Turbopack、Rspack | 使用 Rust 重写工具链,极致性能 |
Vite 如何解决 webpack 的痛点
Webpack 在构建大型项目时非常慢,因为启动时必须先对项目进行全量打包,然后运行打包后的文件。
Vite 的做法则完全不同——完全跳过打包步骤,利用浏览器原生的 ES Module import 机制按需获取内容:
对于 .vue 等浏览器无法直接识别的模块,Vite 在开发服务器端实时编译为 JS 再返回给浏览器:
此外,Vite 的热更新(HMR)底层使用 WebSocket 实现,确保了模块替换的实时性和精准性。
Vite 配置文件全览
Q: Vite 配置文件如何组织?有哪些常用配置项?
A: Vite 配置文件位于项目根目录下的 vite.config.js,推荐使用 defineConfig 工具函数来获得更好的类型提示:
import { defineConfig } from 'vite';
export default defineConfig({
// 配置选项
});
配置分为几大类别:普通配置、开发服务器配置、构建配置、预览配置、依赖优化配置、SSR 配置和 Worker 配置。
普通配置
| 配置项 | 类型 | 说明 |
|---|---|---|
root |
string | index.html 所在位置,默认项目根目录 |
define |
Record | 定义全局常量替换,构建时静态替换 |
resolve.alias |
object | 路径别名,简化模块路径引用 |
resolve.extensions |
string[] | 导入时可省略的扩展名列表 |
define 示例——定义全局常量,无需 import 即可直接使用:
export default defineConfig({
define: {
__APP_VERSION__: JSON.stringify('v1.0.0'),
__API_URL__: 'window.__backend_api_url',
},
});
注意:Vite 使用 esbuild 的 define 进行替换,值必须是包含 JSON 可序列化值的字符串或单个标识符。
resolve.alias 配置路径别名:
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
});
CSS 配置
CSS 相关的核心配置项:
export default defineConfig({
css: {
postcss: { // 内联 PostCSS 配置
plugins: [autoprefixer(), cssnano()],
},
preprocessorOptions: { // CSS 预处理器配置
scss: {
additionalData: `@import "@/styles/mixins.scss";`, // 全局注入
},
},
devSourcemap: true, // 开发环境 source maps
transformer: 'postcss', // CSS 处理引擎:'postcss' 或 'lightningcss'
},
});
additionalData 非常实用——它会在每个 SCSS 文件的头部自动注入指定内容,常用于注入全局变量或 mixins。
开发服务器配置
export default defineConfig({
server: {
host: '0.0.0.0', // 监听所有网络接口,方便多设备测试
port: 5173, // 端口号,默认 5173
strictPort: true, // 端口被占用时直接失败
open: true, // 启动时自动打开浏览器
proxy: { // 代理配置
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
https: { // HTTPS 配置
key: fs.readFileSync('/path/to/server.key'),
cert: fs.readFileSync('/path/to/server.crt'),
},
watch: { // 文件监听(底层使用 chokidar)
ignored: ['**/ignored-directory/**'],
usePolling: true,
interval: 100,
},
},
});
其中 proxy 是最常用的配置之一,用于解决开发环境下的跨域问题。
依赖预构建:启动性能的秘诀
Q: 前面说 Vite 不打包,为什么又出现了「依赖预构建」?
A: 这里存在两个痛点:
- 如果完全不打包,某些依赖文件过多会导致浏览器请求数量爆炸。
- 部分依赖仍以 CommonJS 格式发布,不兼容浏览器原生 ESM 环境。
因此 Vite 在首次启动项目时,会针对 node_modules 中的依赖进行预构建。这个阶段的构建工具是 esbuild——一个用 Go 语言编写的高效构建工具,大部分工作并行处理,速度极快。
esbuild 在预构建阶段做的事情:
| 步骤 | 说明 |
|---|---|
| 格式转换 | 将 CommonJS、UMD 格式转换为 ESM 格式 |
| 打包 | 将多个内部模块合并为一个文件,减少请求数 |
| 最小化和压缩 | 在生产构建阶段执行 |
缓存机制
预构建产物缓存在 node_modules/.vite 目录下。以下情况会触发重新预构建:
- 包管理器的锁文件内容发生变化(
package-lock.json、yarn.lock等) - 补丁文件夹修改时间变化
vite.config.js中依赖预构建相关配置变化NODE_ENV的值变动
浏览器端也会对预构建依赖进行强缓存(max-age=31536000, immutable),版本变化会通过 URL 附加版本查询使缓存自动失效:
http://localhost:3000/node_modules/.vite/lodash.js?v=4.17.19
http://localhost:3000/node_modules/.vite/lodash.js?v=4.17.20 // 版本升级后 URL 变化
自定义预构建行为
通过 optimizeDeps 配置对预构建进行精细控制:
export default defineConfig({
optimizeDeps: {
entries: ['src/main.js', 'src/admin.js'], // 指定入口文件
include: ['my-lib/components/**/*.vue'], // 强制包含某些包
exclude: ['some-large-lib'], // 排除某些包
esbuildOptions: { // esbuild 选项
target: 'es2015',
},
force: true, // 强制重新预构建
},
});
构建生产版本:从开发到上线
Q: 为什么生产构建使用 rollup 而不是 esbuild?
A: 答案在于不同环境下构建目标不同:
- 开发环境:追求构建速度,esbuild 极快
- 生产环境:追求代码质量和优化程度,rollup 更强
rollup 的生产优势:
| 能力 | 说明 |
|---|---|
| 代码分割和动态导入 | 处理循环依赖、动态模块路径等复杂场景 |
| Tree Shaking | 更精准地剔除未使用代码,减少包体积 |
| 插件生态 | 成熟广泛的插件生态,支持各种构建需求 |
| 输出控制 | 更细粒度的输出格式和结构控制 |
| 生产优化 | 更复杂的代码分割和加载策略 |
常用构建配置
export default defineConfig({
build: {
target: 'es2015', // 浏览器兼容目标(默认 'modules')
outDir: 'dist', // 输出目录
assetsDir: 'assets', // 静态资源目录
cssMinify: 'esbuild', // CSS 压缩方式:'esbuild' | 'lightningcss' | false
minify: 'esbuild', // JS 压缩方式:'esbuild' | 'terser'
sourcemap: true, // 生成 source map
rollupOptions: { // 透传 rollup 配置
plugins: [],
external: ['some-external-lib'],
output: {
globals: { 'some-external-lib': 'SomeExternalLib' }
}
},
},
});
关于压缩工具的选择:
| 工具 | 速度 | 压缩率 | 适用场景 |
|---|---|---|---|
| esbuild | 极快(比 terser 快 20-40 倍) | 略低 1%-2% | 默认选择,大多数场景足够 |
| terser | 较慢 | 稍好 | 需要更精细控制时 |
公共基础路径 base
base 是一个独立于 build 的重要配置项。当应用部署在子路径而非根目录时,需要通过 base 确保静态资源正确加载:
export default defineConfig({
base: '/my-app/', // 所有静态资源以 /my-app/ 为前缀
});
库模式
如果要把应用打包成供其他项目引用的库,需要使用库模式:
import { resolve } from 'path';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'lib/main.js'), // 入口文件
name: 'MyLib', // UMD/IIFE 全局变量名
fileName: (format) => `my-lib.${format}.js`, // 输出文件名
},
rollupOptions: {
external: ['vue'], // 排除外部依赖
output: {
globals: { vue: 'Vue' },
},
},
},
});
库模式和普通应用的核心区别:
| 维度 | 普通应用 | 库模式 |
|---|---|---|
| 入口文件 | index.html | JS 文件 |
| 输出格式 | 针对浏览器 | UMD、ESM、CommonJS 多种格式 |
| 外部依赖 | 全部打包 | 需要排除(如 vue、react) |
打包完成后,还需在 package.json 中配置不同环境的入口:
{
"name": "my-lib",
"type": "module",
"main": "./dist/my-lib.umd.js",
"module": "./dist/my-lib.es.js",
"exports": {
".": {
"import": "./dist/my-lib.es.js",
"require": "./dist/my-lib.umd.js"
}
}
}
环境变量与模式
Q: Vite 的模式(mode)和 NODE_ENV 有什么不同?
A: NODE_ENV 是 Node.js 生态中广泛使用的环境变量,用于指定代码运行环境(development、production 或 test)。而Vite 中的模式是一个可以在启动时设置的属性,用来指导 Vite 如何加载 .env 文件。
两者的关系:使用 mode 可以在不影响 NODE_ENV 的前提下,实现更细粒度的场景控制。
Vite 内置环境变量
| 变量 | 说明 |
|---|---|
import.meta.env.MODE |
当前应用运行的模式 |
import.meta.env.PROD |
是否运行在生产环境 |
import.meta.env.DEV |
是否运行在开发环境 |
import.meta.env.SSR |
是否运行在 server 上 |
import.meta.env.BASE_URL |
公共基础路径 |
安全性:只有以 VITE_ 为前缀的变量才会暴露给客户端代码,防止敏感信息泄漏:
# .env
VITE_SOME_KEY=123 # ✅ 客户端可访问
DB_PASSWORD=foobar # ❌ 客户端无法访问(没有 VITE_ 前缀)
env 文件类型
.env # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略
典型项目结构:
my-vite-project/
├── .env # 默认环境变量
├── .env.development # 开发环境(VITE_API_URL=https://dev.api.example.com)
├── .env.production # 生产环境(VITE_API_URL=https://api.example.com)
└── vite.config.js
启动时指定模式:
vite --mode development # 开发环境
vite build --mode production # 生产构建
Vite CLI 命令全解
Q: Vite CLI 提供了哪些命令?各自有什么用?
A: Vite CLI 分为四大类:
1. 开发服务器
vite # 启动开发服务器
vite dev # 同 vite(别名)
vite serve # 同 vite(别名)
./node_modules/.bin/vite # 等价于上面
实际上 vite 命令指向的是 node_modules/.bin 目录下的可执行文件。
2. 构建生产版本
vite build # 执行生产构建
常用 CLI 参数(CLI 参数的优先级高于配置文件):
| 参数 | 说明 | 示例 |
|---|---|---|
--target |
编译目标 | vite build --target es2015 |
--outDir |
输出目录 | vite build --outDir build |
--assetsDir |
资源目录 | vite build --assetsDir static |
--manifest |
生成资源映射文件 | vite build --manifest |
-c, --config |
指定配置文件 | vite build -c vite.config.custom.js |
--base |
公共基础路径 | vite build --base /myapp/ |
CLI 和配置文件同时指定相同参数时,CLI 的优先级更高。
3. 依赖预构建(手动执行)
大多数情况下不需要手动执行,Vite 会自动处理。
4. 本地预览构建产物
vite preview # 启动本地预览服务器,默认监听 4173 端口
vite preview 的作用是检测打包后的应用是否正常工作,相比以前需要手动用 express 启动服务器的方式,Vite 提供了开箱即用的预览能力。常用参数:
vite preview --host 0.0.0.0 --port 8080
Vite 插件生态
Q: 如何在 Vite 中使用和选择插件?
A: 使用插件很简单——安装后在配置文件的 plugins 数组中引入即可:
import vue from '@vitejs/plugin-vue';
import eslintPlugin from 'vite-plugin-eslint';
export default defineConfig({
plugins: [vue(), eslintPlugin()],
});
关键细节
1. 传入配置选项
import legacy from '@vitejs/plugin-legacy';
export default defineConfig({
plugins: [
legacy({ targets: ['defaults', 'not IE 11'] }),
],
});
2. 执行顺序
多个插件按数组从前到后的顺序依次执行。
3. 按需引用
默认情况下插件在开发和生产模式都会调用。通过 apply 属性可按需启用:
plugins: [
{ ...typescript2(), apply: 'build' }, // 仅在构建时使用
],
插件分类
| 分类 | 代表插件 | 说明 |
|---|---|---|
| 官方插件 | @vitejs/plugin-vue | Vue 3 单文件组件支持 |
| 官方插件 | @vitejs/plugin-react | React 支持 + HMR |
| 官方插件 | @vitejs/plugin-legacy | 旧浏览器兼容 |
| 社区插件 | vite-plugin-compression | 构建时 gzip/brotli 压缩 |
| 社区插件 | vite-plugin-pwa | 渐进式网络应用(PWA) |
| 社区插件 | vite-plugin-svg-icons | SVG 图标管理 |
| rollup 插件 | @rollup/plugin-image | 图片文件导入 |
| rollup 插件 | @rollup/plugin-commonjs | CommonJS 转 ESM |
因为 Vite 生产构建底层使用 rollup,所以 rollup 插件也可以在 Vite 中使用。
自定义 Vite 插件
Vite 插件扩展了 rollup 插件接口,并添加了 Vite 独有的配置项。开发自定义插件需要:
- 了解 rollup 插件开发 基础
- 了解 Vite 的生命周期钩子方法,在不同时间点接入 Vite 内部机制
总结
本文全面解析了 Vite 构建工具的各个层面。Vite 的核心创新在于开发环境完全跳过打包,利用浏览器原生 ESM 实现按需加载,配合 esbuild 的极致编译速度;依赖预构建解决了 CommonJS 兼容和请求数爆炸问题,通过文件缓存和浏览器强缓存实现秒级冷启动;生产构建切换为 rollup,以更精准的 Tree Shaking、代码分割和成熟的插件生态来保证产物质量;环境变量系统通过 mode 和 VITE_ 前缀机制实现了安全的配置管理;插件体系同时兼容 Vite 社区插件和 rollup 插件,覆盖从开发到构建的全流程。掌握这些知识后,可以根据项目需求灵活配置 Vite,充分发挥其性能优势。