Vite 构建工具详解
大约 12 分钟约 3701 字
Vite 构建工具详解
简介
Vite 是下一代前端构建工具,利用浏览器原生 ES Module 实现极速开发服务器启动,使用 Rollup 进行生产构建。相比 Webpack,Vite 开发启动快 10-100 倍,热更新几乎即时。已成为 Vue、React 等主流框架的推荐构建工具。
Vite(法语意为"快速")由 Vue 作者尤雨溪于 2020 年创建。它解决了 Webpack 在大型项目中开发服务器启动慢的核心痛点:Webpack 在启动时需要从入口文件开始递归解析和打包所有模块依赖,而 Vite 利用浏览器原生 ES Module 支持,在开发模式下不对源码做打包转换,而是让浏览器直接加载模块源码。
Vite 的工作原理:
- 开发模式:基于浏览器原生 ESM,按需编译模块,利用 esbuild(Go 编写)进行 TypeScript/JSX 的极速转换
- 生产构建:基于 Rollup 进行打包,支持代码分割、Tree-shaking、懒加载等优化
- 依赖预构建:首次启动时使用 esbuild 将 node_modules 中的依赖预构建为 ESM 格式
特点
Vite vs Webpack 对比
| 维度 | Vite | Webpack |
|---|---|---|
| 开发启动速度 | 极快(秒级) | 慢(随项目增大而增大) |
| 热更新(HMR) | 毫秒级 | 秒级(取决于项目规模) |
| 生产构建 | Rollup | Webpack 自身 |
| 配置复杂度 | 低(零配置) | 高(loader + plugin) |
| 插件生态 | Rollup 插件 + Vite 专属插件 | 庞大的 Webpack 生态 |
| CommonJS 支持 | 需预构建 | 原生支持 |
| 代码分割 | Rollup 自动 | 需手动配置 splitChunks |
| 模块联邦 | 不支持 | 支持(Module Federation) |
| SSR | 内置支持 | 需额外配置 |
| 生态成熟度 | 快速增长中 | 非常成熟 |
// Webpack 启动过程(冷启动慢的原因)
// 1. 从入口文件开始
// 2. 递归解析所有 import/require
// 3. 对每个模块执行所有 loader(babel, ts-loader, sass-loader...)
// 4. 生成完整的模块依赖图
// 5. 打包所有模块到一个或多个 bundle
// 6. 启动开发服务器
// 项目越大,步骤 2-4 耗时越长
// Vite 启动过程(为什么快)
// 1. 启动开发服务器(几乎瞬间)
// 2. 浏览器请求入口文件
// 3. Vite 按需编译被请求的模块
// 4. 浏览器解析 ESM import,继续请求下一个模块
// 5. 每个模块只在被请求时才编译
// 无论项目多大,首次启动都是秒级项目配置
Vite 配置
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components')
}
},
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
},
build: {
outDir: 'dist',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
elementPlus: ['element-plus']
}
}
},
chunkSizeWarningLimit: 1000
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
}
}
}
})代理配置详解
// vite.config.ts —— 多目标代理
export default defineConfig({
server: {
port: 3000,
host: '0.0.0.0', // 允许局域网访问
open: true, // 自动打开浏览器
// 代理配置
proxy: {
// 代理 API 请求到后端服务
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
// ws: true, // 代理 WebSocket
},
// 代理上传请求到文件服务
'/upload': {
target: 'http://file-server:8080',
changeOrigin: true,
},
// 代理多个后端(路径匹配)
'/api/v2': {
target: 'http://localhost:5001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v2/, '/api'),
},
},
},
})
// 使用 http-proxy-middleware 的更多能力
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
// 自定义代理行为
configure: (proxy, options) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
// 添加自定义请求头
proxyReq.setHeader('X-Custom-Header', 'value')
console.log(`[Proxy] ${req.method} ${req.url} -> ${options.target}`)
})
proxy.on('error', (err, req, res) => {
console.error('[Proxy Error]', err)
})
},
},
},
},
})路径别名与 TypeScript 配合
// vite.config.ts
import { resolve } from 'path'
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@views': resolve(__dirname, 'src/views'),
'@utils': resolve(__dirname, 'src/utils'),
'@api': resolve(__dirname, 'src/api'),
'@assets': resolve(__dirname, 'src/assets'),
'@stores': resolve(__dirname, 'src/stores'),
},
},
})
// tsconfig.json —— 需要同步配置,否则 TypeScript 找不到模块
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@views/*": ["src/views/*"],
"@utils/*": ["src/utils/*"],
"@api/*": ["src/api/*"],
"@assets/*": ["src/assets/*"],
"@stores/*": ["src/stores/*"]
}
}
}环境变量
多环境配置
# .env.development
VITE_API_BASE_URL=http://localhost:5000
VITE_APP_TITLE=MyApp (开发)
# .env.production
VITE_API_BASE_URL=https://api.example.com
VITE_APP_TITLE=MyApp
# .env.staging
VITE_API_BASE_URL=https://staging-api.example.com
VITE_APP_TITLE=MyApp (测试)// env.d.ts — 类型声明
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string
readonly VITE_APP_TITLE: string
}
// 使用
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL
const isDev = import.meta.env.DEV环境变量最佳实践
# .env — 所有环境共享的变量
VITE_APP_VERSION=1.0.0
VITE_APP_NAME=MyApp
# .env.development — 开发环境(npm run dev)
VITE_API_BASE_URL=http://localhost:5000
VITE_SHOW_DEVTOOLS=true
# .env.production — 生产环境(npm run build)
VITE_API_BASE_URL=https://api.example.com
VITE_ENABLE_MOCK=false
# .env.staging — 预发布环境
VITE_API_BASE_URL=https://staging-api.example.com
# .env.local — 本地覆盖(不提交到 git)
VITE_API_BASE_URL=http://192.168.1.100:5000
# 注意:
# 1. 只有 VITE_ 前缀的变量会暴露给客户端代码
# 2. 不以 VITE_ 开头的变量只在 vite.config.ts 中可用
# 3. .env.local 和 .env.*.local 会被 git 忽略
# 4. 加载优先级:.env.[mode].local > .env.[mode] > .env.local > .env// vite.config.ts 中使用环境变量
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
// loadEnv 加载指定 mode 的环境变量
const env = loadEnv(mode, process.cwd(), '')
return {
// 服务端专用变量(不暴露给客户端)
server: {
proxy: {
'/api': {
target: env.VITE_API_BASE_URL || 'http://localhost:5000',
changeOrigin: true,
},
},
},
// 注入全局常量(编译时替换)
define: {
__APP_VERSION__: JSON.stringify(env.VITE_APP_VERSION),
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
},
}
})
// 客户端代码中使用 define 注入的常量
// console.log(__APP_VERSION__) // "1.0.0"
// console.log(__BUILD_TIME__) // "2024-01-01T00:00:00.000Z"// src/utils/env.ts —— 环境工具函数
export function getEnv(): string {
return import.meta.env.MODE || 'development'
}
export function isDev(): boolean {
return import.meta.env.DEV
}
export function isProd(): boolean {
return import.meta.env.PROD
}
export function getApiBaseUrl(): string {
return import.meta.env.VITE_API_BASE_URL
}
// 使用
if (isDev()) {
console.log('开发模式')
// 开发模式下的特殊逻辑
}常用插件
插件配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { visualizer } from 'rollup-plugin-visualizer'
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
vue(),
// 自动导入 API
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts',
resolvers: [ElementPlusResolver()]
}),
// 自动注册组件
Components({
dirs: ['src/components'],
dts: 'src/components.d.ts',
resolvers: [ElementPlusResolver()]
}),
// 打包分析
visualizer({
open: true,
gzipSize: true,
filename: 'dist/stats.html'
}),
// Gzip 压缩
viteCompression({
algorithm: 'gzip',
threshold: 10240 // 大于 10KB 的文件压缩
})
]
})更多实用插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx' // JSX 支持
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' // 国际化
import { VitePWA } from 'vite-plugin-pwa' // PWA 支持
import viteSvgIcons from 'vite-plugin-svg-icons' // SVG 图标
import { createHtmlPlugin } from 'vite-plugin-html' // HTML 模板注入
export default defineConfig({
plugins: [
vue(),
vueJsx(), // 在 Vue 文件中使用 JSX 语法
// PWA 支持
VitePWA({
registerType: 'autoUpdate',
manifest: {
name: 'My App',
theme_color: '#ffffff',
icons: [
{ src: '/pwa-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: '/pwa-512x512.png', sizes: '512x512', type: 'image/png' },
],
},
}),
// SVG 图标自动导入
viteSvgIcons({
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
symbolId: 'icon-[dir]-[name]',
}),
// HTML 模板变量注入
createHtmlPlugin({
minify: true,
inject: {
data: {
title: 'My App',
injectScript: `<script src="https://cdn.example.com/analytics.js"></script>`,
},
},
}),
// Vue I18n
VueI18nPlugin({
include: resolve(__dirname, 'src/locales/**'),
}),
],
})自定义 Vite 插件
// plugins/vite-plugin-hello.ts —— 简单的自定义插件
import type { Plugin } from 'vite'
export function vitePluginHello(): Plugin {
return {
name: 'vite-plugin-hello',
// 在 config 加载完成后执行
configResolved(config) {
console.log(`Vite 配置已解析,根目录: ${config.root}`)
},
// 转换每个请求的模块
transform(code, id) {
if (id.endsWith('.vue')) {
console.log(`处理 Vue 文件: ${id}`)
}
return null // 返回 null 表示不做修改
},
// 在构建结束后执行
closeBundle() {
console.log('构建完成!')
},
}
}
// plugins/vite-plugin-progress.ts —— 终端进度条
import type { Plugin } from 'vite'
import readline from 'readline'
import { blue, green, yellow } from 'kolorist'
export function vitePluginProgress(): Plugin {
let progress = 0
let timer: ReturnType<typeof setInterval>
function renderBar() {
const barLength = 30
const filled = Math.round((progress / 100) * barLength)
const bar = '█'.repeat(filled) + '░'.repeat(barLength - filled)
readline.cursorTo(process.stdout, 0)
process.stdout.write(`${blue('构建中')} ${green(bar)} ${progress}%`)
}
return {
name: 'vite-plugin-progress',
apply: 'build',
buildStart() {
progress = 0
timer = setInterval(() => {
progress = Math.min(progress + Math.random() * 5, 95)
renderBar()
}, 100)
},
closeBundle() {
clearInterval(timer)
progress = 100
renderBar()
console.log(`\n${green('构建完成!')}`)
},
}
}构建优化
分包与性能
export default defineConfig({
build: {
// 分包策略
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('element-plus')) return 'element-plus'
if (id.includes('echarts')) return 'echarts'
if (id.includes('lodash')) return 'lodash'
return 'vendor'
}
}
}
},
// CSS 代码分割
cssCodeSplit: true,
// 资源内联阈值
assetsInlineLimit: 4096, // 小于 4KB 的资源内联为 base64
// 图片优化
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // 移除 console
drop_debugger: true
}
}
}
})生产构建最佳实践
// vite.config.ts —— 生产级完整配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { visualizer } from 'rollup-plugin-visualizer'
import viteCompression from 'vite-plugin-compression'
export default defineConfig(({ mode }) => ({
plugins: [
vue(),
mode === 'production' && visualizer({
open: false,
gzipSize: true,
brotliSize: true,
filename: 'dist/report.html',
}),
mode === 'production' && viteCompression({
algorithm: 'gzip',
threshold: 10240,
}),
mode === 'production' && viteCompression({
algorithm: 'brotliCompress',
threshold: 10240,
}),
].filter(Boolean),
build: {
target: 'es2020', // 目标浏览器
outDir: 'dist',
sourcemap: mode !== 'production', // 生产环境关闭 sourcemap
minify: 'esbuild', // esbuild 比 terser 更快
// 分包策略
rollupOptions: {
output: {
// 入口文件命名
entryFileNames: 'assets/js/[name]-[hash].js',
// 代码分割后的文件命名
chunkFileNames: 'assets/js/[name]-[hash].js',
// 静态资源命名
assetFileNames: (assetInfo) => {
const ext = assetInfo.name?.split('.').pop()
if (/png|jpe?g|gif|svg|webp|ico/i.test(ext || '')) {
return 'assets/img/[name]-[hash][extname]'
}
if (/woff2?|eot|ttf|otf/i.test(ext || '')) {
return 'assets/font/[name]-[hash][extname]'
}
if (/css/i.test(ext || '')) {
return 'assets/css/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
},
// 手动分包
manualChunks: (id) => {
if (!id.includes('node_modules')) return
// 核心框架
if (id.includes('vue') || id.includes('pinia') || id.includes('vue-router')) {
return 'framework'
}
// UI 组件库
if (id.includes('element-plus')) return 'ui-library'
// 图表库
if (id.includes('echarts')) return 'charts'
// 工具库
if (id.includes('lodash') || id.includes('axios') || id.includes('dayjs')) {
return 'utils'
}
// 其他第三方依赖
return 'vendor'
},
},
},
// CSS 代码分割(默认开启)
cssCodeSplit: true,
// 小于 4KB 的资源内联为 base64
assetsInlineLimit: 4096,
// chunk 大小警告阈值
chunkSizeWarningLimit: 500,
// 构建时清空输出目录
emptyOutDir: true,
},
}))CSS 构建优化
// vite.config.ts —— CSS 相关配置
export default defineConfig({
css: {
// 是否使用 PostCSS
postcss: {
plugins: [
// autoprefixer 已内置,不需要手动配置
],
},
// CSS 预处理器选项
preprocessorOptions: {
scss: {
// 全局注入 SCSS 变量和 mixin
additionalData: `
@use "@/styles/variables" as *;
@use "@/styles/mixins" as *;
`,
// SCSS API 版本
api: 'modern-compiler',
},
less: {
additionalData: `@import "@/styles/variables.less";`,
javascriptEnabled: true, // 启用 Less 内联 JavaScript
},
},
// 开发模式下是否使用 CSS Modules
modules: {
// CSS Modules 命名约定
localsConvention: 'camelCaseOnly',
},
},
})构建分析工具
// 方式 1:rollup-plugin-visualizer(推荐)
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
filename: 'dist/stats.html',
open: true,
gzipSize: true,
brotliSize: true,
}),
],
})
// 方式 2:命令行分析
// npm install -D rollup-plugin-visualizer
// npx vite-bundle-visualizer
// 方式 3:手动检查 dist 目录
// npm run build 后检查 dist/assets/js 下的文件大小常用命令
# 创建项目
npm create vite@latest my-app -- --template vue-ts
# 开发
npm run dev
# 构建
npm run build
# 预览构建结果
npm run preview
# 分析包大小
npx vite-bundle-visualizer自定义命令
{
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"build:staging": "vue-tsc --noEmit && vite build --mode staging",
"build:analyze": "vite build && vite-bundle-visualizer",
"preview": "vite preview",
"preview:staging": "vite preview --mode staging",
"lint": "eslint . --ext .vue,.js,.ts,.tsx --fix",
"format": "prettier --write \"src/**/*.{js,ts,vue,css,scss}\""
}
}依赖预构建
// vite.config.ts —— 依赖预构建配置
export default defineConfig({
optimizeDeps: {
// 强制预构建(某些依赖没有被自动检测到)
include: [
'vue',
'vue-router',
'pinia',
'axios',
'element-plus',
'lodash-es',
],
// 排除预构建(某些依赖已经是 ESM 格式)
exclude: [
'your-esm-package',
],
// 自定义 esbuild 选项
esbuildOptions: {
target: 'es2020',
supported: {
'top-level-await': true,
},
},
},
})
// 预构建缓存位置
// node_modules/.vite/deps/
// node_modules/.vite/deps_vite_xxx/ (带 hash,配置变化时自动失效)
// 强制重新预构建
// npm run dev -- --forceVite 的 SSR 支持
// vite.config.ts —— SSR 配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
ssr: true, // 构建 SSR 入口
},
})// server-entry.ts —— SSR 服务端入口
import { createApp } from './src/main'
export async function render(url: string) {
const { app, router } = createApp()
await router.push(url)
await router.isReady()
const ctx = {}
const html = await renderToString(app, ctx)
return { html }
}优点
缺点
总结
Vite 是现代前端构建工具的首选。核心优势:ESM 开发模式极速启动和热更新,Rollup 生产构建优化。Vue/React 项目推荐使用 Vite。分包策略用 manualChunks 拆分 vendor。代理配置用 server.proxy 转发 API 请求。常用插件:unplugin-auto-import 自动导入、vite-plugin-compression 压缩。
最佳实践清单:
- 配置路径别名时同步更新 tsconfig.json 的 paths
- 使用 manualChunks 合理分包,控制单文件大小在 500KB 以内
- 生产环境关闭 sourcemap,使用 terser 移除 console
- 使用 .env 文件管理多环境配置,区分客户端/服务端变量
- 使用 visualizer 定期分析包体积,及时发现问题依赖
- 使用预构建配置 optimizeDeps 处理非 ESM 依赖
关键知识点
- 先判断主题更偏浏览器原理、框架机制、工程化还是性能优化。
- 前端问题很多看似是页面问题,实际源头在构建、缓存、状态流或接口协作。
- 真正成熟的前端方案一定同时考虑首屏、交互、可维护性和线上诊断。
项目落地视角
- 把组件边界、状态归属、网络层规范和错误处理先定下来。
- 上线前检查包体积、缓存命中、接口失败路径和关键交互降级策略。
- 如果主题和性能有关,最好用 DevTools、Lighthouse 或埋点验证。
常见误区
- 只盯框架 API,不理解浏览器和运行时成本。
- 把状态、请求和 UI 更新混成一层,后期难维护。
- 线上问题出现时没有日志、埋点和性能基线可对照。
进阶路线
- 继续补齐 SSR、边缘渲染、设计系统和监控告警能力。
- 把主题和后端接口约定、CI/CD、缓存策略一起思考。
- 沉淀组件规范、页面模板和性能基线,减少团队差异。
适用场景
- 当你准备把《Vite 构建工具详解》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合中后台应用、门户站点、组件库和实时交互页面。
- 当需求涉及状态流、路由、网络缓存、SSR/CSR 或性能治理时,这类主题很关键。
落地建议
- 先定义组件边界和状态归属,再落地 UI 细节。
- 对核心页面做首屏、体积、缓存和错误路径检查。
- 把安全、兼容性和可访问性纳入默认交付标准。
排错清单
- 先用浏览器 DevTools 看请求、性能面板和控制台错误。
- 检查依赖版本、构建配置、环境变量和静态资源路径。
- 如果是线上问题,优先确认缓存、CDN 和构建产物是否一致。
复盘问题
- 如果把《Vite 构建工具详解》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Vite 构建工具详解》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Vite 构建工具详解》最大的收益和代价分别是什么?
