Vite 原理与配置
Vite 是一个快速的开发构建工具,基于 ESBuild(开发)和 Rollup(生产),提供极速的开发体验。
📋 学习目标
- ✅ 理解 Vite 的核心原理和优势
- ✅ 掌握 Vite 的基础配置
- ✅ 能够搭建 React、Vue 等项目
- ✅ 理解开发和生产环境的差异
- ✅ 掌握 Vite 的插件系统
什么是 Vite
Vite(法语意为”快速”)是一个由 Vue.js 作者尤雨溪开发的构建工具,主要特点:
- 极速开发服务器:基于 ESBuild,启动速度极快
- 按需编译:只编译当前页面需要的模块
- 原生 ESM:开发环境使用原生 ES 模块
- 生产构建:使用 Rollup 进行优化打包
核心原理深度剖析
开发环境的革命性设计
1. 原生 ESM 的威力
传统构建工具的问题:
- Webpack 需要先打包整个应用才能启动
- 随着项目增长,启动时间线性增长
- 修改一个文件需要重新打包整个依赖图
Vite 的解决方案:
// index.html
<script type="module" src="/src/main.js"></script>
// main.js
import { createApp } from 'vue'
import App from './App.vue'
// 浏览器直接请求这些模块,无需打包工作原理:
- 浏览器作为模块加载器:利用浏览器原生 ES 模块支持
- 按需请求:只请求当前页面需要的模块
- HTTP/2 多路复用:充分利用 HTTP/2 的并行请求能力
性能对比:
项目规模:1000 个模块
Webpack 启动:~30s(需要打包所有模块)
Vite 启动:~300ms(只启动开发服务器,按需编译)
2. 依赖预构建机制
为什么需要预构建:
- 第三方库通常是 CommonJS 或 UMD 格式
- 浏览器无法直接使用
- 大量小文件导致 HTTP 请求过多
预构建过程:
// Vite 自动检测 node_modules 中的依赖
// 使用 ESBuild 将 CommonJS 转换为 ESM
// 合并多个小文件为单个文件
// 例如:lodash 有 600+ 文件
// 预构建后:lodash-es.js (单个文件)预构建配置深度解析:
export default defineConfig({
optimizeDeps: {
// 强制包含的依赖(即使不在 node_modules)
include: ['lodash', 'vue'],
// 排除的依赖(不进行预构建)
exclude: ['@some/package'],
// ESBuild 选项
esbuildOptions: {
target: 'es2020',
// 定义全局变量(用于替换)
define: {
'process.env.NODE_ENV': '"development"'
},
// 插件
plugins: []
},
// 强制重新构建(清除缓存)
force: false
}
})预构建缓存机制:
- 缓存位置:
node_modules/.vite - 缓存键:基于
package.json和lockfile - 失效条件:依赖版本变化或
force: true
3. 按需编译的智能策略
编译时机:
// 用户访问 /src/App.vue
// 1. 浏览器请求:GET /src/App.vue
// 2. Vite 拦截请求
// 3. 实时编译 .vue 文件
// 4. 返回编译后的 JavaScript编译流程:
请求 → Vite 拦截 → 插件处理 → ESBuild 编译 → 返回结果
性能优化技巧:
- 文件系统缓存:编译结果缓存到内存
- 增量编译:只编译变更的文件
- 并行编译:利用多核 CPU
生产环境的 Rollup 集成
为什么生产环境使用 Rollup?
- 代码质量:Rollup 的 Tree-shaking 更彻底
- 输出格式:支持多种输出格式(ESM、CJS、UMD)
- 插件生态:Rollup 插件生态成熟
- 代码分割:Rollup 的代码分割策略更优
生产构建流程
// 1. 依赖预构建(使用 Rollup)
// 2. 应用代码打包(使用 Rollup)
// 3. 代码分割(基于动态 import)
// 4. 资源优化(压缩、优化)构建配置深度解析:
export default defineConfig({
build: {
// 输出目录
outDir: 'dist',
// 资源内联阈值(小于此大小的资源内联为 base64)
assetsInlineLimit: 4096,
// CSS 代码分割
cssCodeSplit: true,
// Source Map
sourcemap: true, // 或 'inline' | 'hidden'
// 最小化
minify: 'esbuild', // 'esbuild' | 'terser' | false
// Terser 选项(当 minify: 'terser' 时)
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// Rollup 选项
rollupOptions: {
// 入口
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin.html')
},
// 输出配置
output: {
// 手动代码分割
manualChunks: (id) => {
// node_modules 中的包
if (id.includes('node_modules')) {
// 将 React 相关包单独打包
if (id.includes('react')) {
return 'react-vendor'
}
// 其他第三方库
return 'vendor'
}
},
// 文件命名
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]'
},
// 外部依赖(不打包)
external: ['react', 'react-dom']
},
// 构建大小警告阈值(KB)
chunkSizeWarningLimit: 1000
}
})快速开始
安装
# 使用 npm
npm create vite@latest my-project
# 使用 yarn
yarn create vite my-project
# 使用 pnpm
pnpm create vite my-project项目结构
my-project/
├── src/
│ ├── main.js
│ └── App.vue
├── index.html
├── package.json
└── vite.config.js
基础配置
vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
// 插件配置
plugins: [
react(), // React 项目
// vue(), // Vue 项目
],
// 开发服务器配置
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
// 构建配置
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: true,
minify: 'esbuild', // 或 'terser'
},
// 路径别名
resolve: {
alias: {
'@': '/src',
},
},
})React 项目配置
安装依赖
npm install react react-dom
npm install -D @vitejs/plugin-reactvite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})Vue 项目配置
安装依赖
npm install vue
npm install -D @vitejs/plugin-vuevite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
})环境变量
.env 文件
# .env
VITE_API_URL=http://localhost:8080
VITE_APP_TITLE=My App使用环境变量
// 在代码中使用
const apiUrl = import.meta.env.VITE_API_URL
const appTitle = import.meta.env.VITE_APP_TITLE插件系统深度解析
Vite 插件架构
Vite 插件基于 Rollup 插件系统,但扩展了开发服务器特定的钩子。
插件执行顺序
// 插件按数组顺序执行
plugins: [
plugin1(), // 先执行
plugin2(), // 后执行
]插件钩子详解
构建钩子(Rollup 兼容):
export default function myPlugin() {
return {
name: 'my-plugin',
// 选项解析(修改配置)
config(config, { command }) {
// 开发环境
if (command === 'serve') {
config.server.port = 3000
}
// 生产环境
if (command === 'build') {
config.build.minify = 'terser'
}
},
// 配置解析后(读取最终配置)
configResolved(resolvedConfig) {
console.log('最终配置:', resolvedConfig)
},
// 转换代码(最重要的钩子)
transform(code, id) {
// id: 文件路径
// code: 文件内容
// 只处理特定文件
if (id.endsWith('.vue')) {
// 转换 Vue 文件
return transformVue(code)
}
// 返回 null 表示不处理
return null
},
// 加载文件(自定义加载逻辑)
load(id) {
if (id === 'virtual:my-module') {
return 'export default "Hello"'
}
return null
},
// 解析 ID(路径解析)
resolveId(id, importer) {
if (id === 'virtual:my-module') {
return id // 返回虚拟模块 ID
}
return null
}
}
}开发服务器钩子(Vite 特有):
export default function myPlugin() {
return {
name: 'my-plugin',
// 配置开发服务器
configureServer(server) {
server.middlewares.use((req, res, next) => {
// 自定义中间件
if (req.url === '/api/custom') {
res.end('Custom Response')
} else {
next()
}
})
},
// 转换 HTML
transformIndexHtml(html) {
// 注入脚本、样式等
return html.replace(
'<head>',
'<head><script src="/inject.js"></script>'
)
},
// 热更新处理
handleHotUpdate({ file, server }) {
if (file.endsWith('.vue')) {
// 自定义 HMR 逻辑
server.ws.send({
type: 'update',
updates: [{
type: 'js-update',
path: file,
acceptedPath: file,
timestamp: Date.now()
}]
})
return [] // 阻止默认更新
}
}
}
}常用插件深度解析
@vitejs/plugin-react
工作原理:
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
// 使用新的 JSX 运行时(React 17+)
jsxRuntime: 'automatic',
// JSX 导入源
jsxImportSource: 'react',
// Babel 选项
babel: {
plugins: [
// 自定义 Babel 插件
]
},
// 排除的文件
exclude: /node_modules/
})
]
})内部实现:
- 使用 ESBuild 转换 JSX
- 自动注入 React import(如果使用 classic runtime)
- Fast Refresh 支持(HMR)
@vitejs/plugin-vue
工作原理:
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
// 模板编译选项
template: {
compilerOptions: {
// 自定义指令
directives: {},
// 是否是自定义元素
isCustomElement: (tag) => tag.startsWith('my-')
}
},
// 响应式转换(Vue 3.2+)
reactivityTransform: true,
// 排除的文件
exclude: /node_modules/
})
]
})自定义插件实战
案例 1:自动注入环境变量
export default function envPlugin() {
return {
name: 'env-plugin',
config(config) {
// 读取 .env 文件
const env = loadEnv(config.mode, process.cwd())
// 注入到 define
config.define = {
...config.define,
'import.meta.env.CUSTOM_VAR': JSON.stringify(env.CUSTOM_VAR)
}
}
}
}案例 2:虚拟模块
export default function virtualModulePlugin() {
const virtualModuleId = 'virtual:my-data'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'virtual-module',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
// 动态生成模块内容
return `export const data = ${JSON.stringify(getData())}`
}
}
}
}
// 使用
import { data } from 'virtual:my-data'案例 3:资源处理
export default function assetPlugin() {
return {
name: 'asset-plugin',
transform(code, id) {
// 处理 SVG 文件
if (id.endsWith('.svg')) {
const svg = fs.readFileSync(id, 'utf-8')
// 转换为 React 组件
return `
import React from 'react'
export default function SvgIcon() {
return ${JSON.stringify(svg)}
}
`
}
}
}
}性能优化深度策略
依赖预构建优化
优化策略 1:精确控制预构建范围
export default defineConfig({
optimizeDeps: {
// 只预构建必要的依赖
include: [
// 大型库(如 lodash)
'lodash',
// 频繁使用的工具库
'axios',
'dayjs'
],
// 排除不需要预构建的包
exclude: [
// 已经是 ESM 格式的包
'@some/esm-package',
// 动态导入的包
'@dynamic/package'
],
// 强制重新构建(开发时调试用)
force: process.env.FORCE_OPTIMIZE === 'true'
}
})优化策略 2:预构建性能分析
# 查看预构建信息
DEBUG=vite:optimize-deps npm run dev
# 输出示例:
# [vite] Optimizing dependencies...
# [vite] Pre-bundling lodash (this will be run only when your dependencies or config have changed)性能指标:
- 首次预构建:5-30s(取决于依赖数量)
- 后续启动:< 1s(使用缓存)
代码分割深度优化
策略 1:基于路由的代码分割
// router.js
const routes = [
{
path: '/home',
// 动态导入,自动代码分割
component: () => import('./pages/Home.vue')
},
{
path: '/about',
component: () => import('./pages/About.vue')
}
]策略 2:手动代码分割
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: (id) => {
// 1. 第三方库分离
if (id.includes('node_modules')) {
// React 生态
if (id.includes('react') || id.includes('react-dom')) {
return 'react-vendor'
}
// Vue 生态
if (id.includes('vue')) {
return 'vue-vendor'
}
// UI 库
if (id.includes('ant-design') || id.includes('element-plus')) {
return 'ui-vendor'
}
// 工具库
if (id.includes('lodash') || id.includes('axios')) {
return 'utils-vendor'
}
// 其他第三方库
return 'vendor'
}
// 2. 业务代码分离
if (id.includes('/src/pages/')) {
// 按页面目录分割
const match = id.match(/\/src\/pages\/([^/]+)/)
if (match) {
return `page-${match[1]}`
}
}
// 3. 工具函数分离
if (id.includes('/src/utils/')) {
return 'utils'
}
}
}
}
}
})策略 3:预加载优化
// 使用 import() 的预加载提示
const Home = () => import(
/* webpackPreload: true */
/* webpackChunkName: "home" */
'./pages/Home.vue'
)HMR 性能优化
优化策略
export default defineConfig({
server: {
hmr: {
// 覆盖端口(默认与 server.port 相同)
port: 24678,
// 覆盖主机(默认与 server.host 相同)
host: 'localhost',
// 协议('ws' | 'wss')
protocol: 'ws',
// WebSocket 服务器路径
path: '/hmr',
// 客户端尝试重新连接的间隔
clientPort: 24678
}
}
})HMR 边界优化:
// 避免 HMR 边界问题
// ❌ 错误:在模块顶层使用动态导入
const data = await import('./data.js')
// ✅ 正确:在函数中使用
async function loadData() {
const data = await import('./data.js')
return data
}构建性能优化
1. 并行构建
export default defineConfig({
build: {
// 启用多线程压缩(Terser)
minify: 'terser',
terserOptions: {
compress: {
// 并行处理
parallel: true
}
}
}
})2. 缓存策略
export default defineConfig({
build: {
// 启用构建缓存
cacheDir: 'node_modules/.vite',
// 输出文件包含 hash(利用浏览器缓存)
rollupOptions: {
output: {
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]'
}
}
}
})3. 构建分析
# 安装分析工具
npm install -D rollup-plugin-visualizer
# 配置
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true,
gzipSize: true,
brotliSize: true
})
]
})运行时性能优化
1. 资源预加载
<!-- index.html -->
<link rel="modulepreload" href="/src/main.js">
<link rel="preload" href="/assets/font.woff2" as="font" type="font/woff2" crossorigin>2. 懒加载优化
// 使用 Intersection Observer 实现可视区域加载
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 加载组件
import('./HeavyComponent.vue').then(module => {
// 渲染组件
})
observer.unobserve(entry.target)
}
})
})3. 资源压缩
export default defineConfig({
build: {
// 启用 gzip 压缩(需要服务器支持)
// 或使用 vite-plugin-compression
assetsInlineLimit: 4096, // 小于 4KB 的资源内联
}
})与 Webpack 对比
| 特性 | Vite | Webpack |
|---|---|---|
| 开发启动 | 极快(ESBuild) | 较慢 |
| HMR | 极快 | 较快 |
| 生产构建 | Rollup | Webpack |
| 配置复杂度 | 简单 | 复杂 |
| 生态 | 较新 | 成熟 |
最佳实践
- 使用 TypeScript:Vite 原生支持 TypeScript
- 路径别名:使用
@等别名简化导入 - 环境变量:使用
.env文件管理环境变量 - 插件选择:优先使用官方插件
- 性能优化:合理配置依赖预构建
相关链接
最后更新:2025