Vite(法语里是”快”的意思)是尤雨溪在 2020 年推出的构建工具。它解决了一个困扰前端开发者很久的问题:项目越大,开发服务器启动越慢。
用 Webpack 开发一个大型项目,冷启动可能要等 30 秒甚至更久。Vite 把这个时间压缩到了几秒,改代码的热更新更是毫秒级。
为什么这么快#
Vite 的核心思路是:开发环境不打包。
传统打包器的工作流程是:分析入口 → 递归解析所有依赖 → 打包成一个或多个 bundle → 启动服务器。项目越大,这个过程越慢。
Vite 换了个思路:直接启动服务器,用浏览器原生的 ES Module 能力按需加载模块。你打开页面时,浏览器请求 index.html,发现里面有 <script type="module" src="/src/main.js">,就去请求 main.js。main.js 里 import 了其他模块,浏览器继续请求……
这样一来,启动时只需要处理极少量的代码,大部分模块是懒加载的。
依赖预构建#
但有个问题:node_modules 里的库很多是 CommonJS 格式,不能直接被浏览器加载。而且像 lodash-es 这种库,一个模块 import 了几百个内部文件,浏览器要发几百个请求。
Vite 的解决方案是依赖预构建(Pre-bundling)。启动时用 esbuild 把 node_modules 里的依赖预先打包成单个 ESM 文件,缓存起来。esbuild 是 Go 写的,速度极快,这一步通常在 1 秒内完成。
预构建的结果缓存在 node_modules/.vite/ 目录。只要 package.json 没变,下次启动直接用缓存。
即时编译#
对于源代码(src/ 下的文件),Vite 做即时编译。请求 .vue 文件时,Vite 实时把它编译成 JavaScript 返回给浏览器。
这个过程非常快,因为只编译一个文件,不需要分析整个依赖图。
快速开始#
创建项目#
# npmnpm create vite@latest my-app
# pnpmpnpm create vite my-app
# yarnyarn create vite my-app选择框架和模板:
✔ Select a framework: › Vue✔ Select a variant: › TypeScript可用的模板:
| 框架 | 变体 |
|---|---|
| vanilla | vanilla, vanilla-ts |
| vue | vue, vue-ts |
| react | react, react-ts, react-swc, react-swc-ts |
| preact | preact, preact-ts |
| lit | lit, lit-ts |
| svelte | svelte, svelte-ts |
| solid | solid, solid-ts |
| qwik | qwik, qwik-ts |
项目结构#
my-app/├── index.html # 入口 HTML├── package.json├── vite.config.ts # Vite 配置├── tsconfig.json├── public/ # 静态资源(不经过处理)│ └── favicon.ico└── src/ ├── main.ts # 入口文件 ├── App.vue ├── components/ └── assets/ # 需要处理的资源开发命令#
# 启动开发服务器npm run dev
# 构建生产版本npm run build
# 预览生产构建npm run preview配置详解#
Vite 配置文件是 vite.config.ts(或 .js、.mjs):
import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'
export default defineConfig({ plugins: [vue()],})基础配置#
import { defineConfig } from 'vite'
export default defineConfig({ // 项目根目录 root: process.cwd(),
// 公共基础路径 base: '/',
// 开发模式 mode: 'development',
// 定义全局常量 define: { __APP_VERSION__: JSON.stringify('1.0.0'), },
// 静态资源目录 publicDir: 'public',
// 缓存目录 cacheDir: 'node_modules/.vite',})开发服务器#
export default defineConfig({ server: { // 端口 port: 3000,
// 自动打开浏览器 open: true,
// 主机名 host: '0.0.0.0',
// HTTPS https: false,
// 代理 proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), }, },
// CORS cors: true,
// 预热常用文件 warmup: { clientFiles: ['./src/main.ts', './src/App.vue'], }, },})构建配置#
export default defineConfig({ build: { // 输出目录 outDir: 'dist',
// 静态资源目录 assetsDir: 'assets',
// 小于此大小的资源内联为 base64 assetsInlineLimit: 4096,
// CSS 代码分割 cssCodeSplit: true,
// 生成 sourcemap sourcemap: false,
// Rollup 配置 rollupOptions: { input: { main: 'index.html', admin: 'admin.html', }, output: { manualChunks: { vendor: ['vue', 'vue-router'], utils: ['lodash-es', 'dayjs'], }, }, },
// 压缩方式:terser | esbuild | false minify: 'esbuild',
// 目标浏览器 target: 'es2020',
// 启用 CSS 压缩 cssMinify: true, },})解析配置#
import { resolve } from 'path'
export default defineConfig({ resolve: { // 路径别名 alias: { '@': resolve(__dirname, 'src'), '@components': resolve(__dirname, 'src/components'), '@utils': resolve(__dirname, 'src/utils'), },
// 扩展名解析顺序 extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
// 条件导出 conditions: ['import', 'module', 'browser', 'default'], },})CSS 配置#
export default defineConfig({ css: { // CSS Modules 配置 modules: { localsConvention: 'camelCase', scopeBehaviour: 'local', },
// 预处理器配置 preprocessorOptions: { scss: { additionalData: `@import "@/styles/variables.scss";`, }, less: { modifyVars: { 'primary-color': '#1890ff', }, javascriptEnabled: true, }, },
// PostCSS 配置 postcss: { plugins: [require('autoprefixer')], },
// 开发时是否启用 sourcemap devSourcemap: true, },})依赖优化#
export default defineConfig({ optimizeDeps: { // 强制预构建的依赖 include: ['vue', 'vue-router', 'pinia', 'axios'],
// 排除预构建的依赖 exclude: ['your-local-package'],
// esbuild 配置 esbuildOptions: { target: 'es2020', }, },})Vue 项目配置#
import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import vueJsx from '@vitejs/plugin-vue-jsx'import AutoImport from 'unplugin-auto-import/vite'import Components from 'unplugin-vue-components/vite'import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({ plugins: [ vue(), vueJsx(),
// 自动导入 Vue API AutoImport({ imports: ['vue', 'vue-router', 'pinia'], resolvers: [ElementPlusResolver()], dts: 'src/auto-imports.d.ts', }),
// 自动注册组件 Components({ resolvers: [ElementPlusResolver()], dts: 'src/components.d.ts', }), ],
resolve: { alias: { '@': '/src', }, },})React 项目配置#
import { defineConfig } from 'vite'import react from '@vitejs/plugin-react'// 或者用 SWC(更快)// import react from '@vitejs/plugin-react-swc'
export default defineConfig({ plugins: [ react({ // Fast Refresh fastRefresh: true,
// Babel 配置(plugin-react 专用) babel: { plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], }, }), ],
resolve: { alias: { '@': '/src', }, },
// 处理 React 相关优化 optimizeDeps: { include: ['react', 'react-dom', 'react-router-dom'], },})环境变量#
Vite 使用 dotenv 加载环境变量。
文件命名#
.env # 所有环境.env.local # 所有环境,git 忽略.env.development # 开发环境.env.production # 生产环境.env.[mode] # 指定模式.env.[mode].local # 指定模式,git 忽略变量规则#
只有 VITE_ 开头的变量才会暴露给客户端:
VITE_API_URL=https://api.example.comVITE_APP_TITLE=My App
# 这个不会暴露给客户端DATABASE_URL=postgresql://localhost:5432/db在代码中使用:
console.log(import.meta.env.VITE_API_URL)console.log(import.meta.env.VITE_APP_TITLE)console.log(import.meta.env.MODE) // 'development' | 'production'console.log(import.meta.env.DEV) // true | falseconsole.log(import.meta.env.PROD) // true | falseTypeScript 类型#
创建 src/env.d.ts:
/// <reference types="vite/client" />
interface ImportMetaEnv { readonly VITE_API_URL: string readonly VITE_APP_TITLE: string}
interface ImportMeta { readonly env: ImportMetaEnv}静态资源处理#
导入资源#
// 导入图片,返回解析后的 URLimport imgUrl from './img.png'
// 导入为字符串import text from './text.txt?raw'
// 导入为 URLimport workerUrl from './worker.js?url'
// 导入为 Workerimport Worker from './worker.js?worker'
// 导入 WASMimport init from './example.wasm?init'public 目录#
public/ 目录下的文件:
- 不会被处理,直接复制到输出目录
- 必须用绝对路径引用:
/favicon.ico - 适合放不需要处理的静态文件
<link rel="icon" href="/favicon.ico" />assets 目录#
src/assets/ 目录下的文件:
- 会被处理(hash、压缩等)
- 用相对路径或别名引用
- 小文件会内联为 base64
<template> <img :src="logo" /></template>
<script setup>import logo from '@/assets/logo.png'</script>插件开发#
Vite 插件兼容 Rollup 插件 API,还有一些 Vite 专用钩子。
基本结构#
import type { Plugin } from 'vite'
export default function myPlugin(options = {}): Plugin { return { name: 'vite-plugin-my-plugin',
// 配置解析后调用 configResolved(config) { console.log('配置:', config.mode) },
// 配置开发服务器 configureServer(server) { server.middlewares.use((req, res, next) => { // 自定义中间件 next() }) },
// 转换 index.html transformIndexHtml(html) { return html.replace( '</head>', `<script>console.log('injected')</script></head>` ) },
// 解析模块 ID resolveId(source) { if (source === 'virtual:my-module') { return source } },
// 加载虚拟模块 load(id) { if (id === 'virtual:my-module') { return `export const msg = 'Hello from virtual module'` } },
// 转换代码 transform(code, id) { if (id.endsWith('.custom')) { return `export default ${JSON.stringify(code)}` } }, }}实战:自动导入 SVG#
import { readFileSync } from 'fs'import type { Plugin } from 'vite'
export default function svgLoader(): Plugin { return { name: 'vite-plugin-svg-loader',
transform(code, id) { if (!id.endsWith('.svg')) return
const svg = readFileSync(id, 'utf-8') const base64 = Buffer.from(svg).toString('base64')
return { code: `export default "data:image/svg+xml;base64,${base64}"`, map: null, } }, }}实战:版本注入#
import type { Plugin } from 'vite'import { readFileSync } from 'fs'
export default function versionPlugin(): Plugin { const pkg = JSON.parse(readFileSync('package.json', 'utf-8'))
return { name: 'vite-plugin-version',
config() { return { define: { __APP_VERSION__: JSON.stringify(pkg.version), __BUILD_TIME__: JSON.stringify(new Date().toISOString()), }, } }, }}性能优化#
依赖预构建优化#
明确指定需要预构建的依赖:
export default defineConfig({ optimizeDeps: { include: [ 'vue', 'vue-router', 'pinia', 'axios', 'lodash-es', // 动态导入的依赖也要加 'echarts', ], },})分包策略#
export default defineConfig({ build: { rollupOptions: { output: { manualChunks(id) { // node_modules 按包名分割 if (id.includes('node_modules')) { const name = id.split('node_modules/')[1].split('/')[0] // 大型库单独分包 if (['vue', 'vue-router', 'pinia'].includes(name)) { return 'vue-vendor' } if (['echarts', 'zrender'].includes(name)) { return 'echarts' } if (['element-plus'].includes(name)) { return 'element-plus' } return 'vendor' } }, }, }, },})按需加载#
路由懒加载:
const routes = [ { path: '/dashboard', component: () => import('@/views/Dashboard.vue'), }, { path: '/settings', component: () => import('@/views/Settings.vue'), },]组件懒加载:
<script setup>import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))</script>图片优化#
使用 vite-plugin-imagemin:
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig({ plugins: [ viteImagemin({ gifsicle: { optimizationLevel: 3 }, optipng: { optimizationLevel: 7 }, mozjpeg: { quality: 80 }, svgo: { plugins: [{ name: 'removeViewBox', active: false }], }, }), ],})构建分析#
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({ plugins: [ visualizer({ open: true, gzipSize: true, brotliSize: true, }), ],})常见问题#
1. 依赖预构建问题#
[vite] new dependencies found: xxx, updating...开发时频繁出现这个提示,说明有依赖没被预构建。加到 optimizeDeps.include 里:
optimizeDeps: { include: ['xxx']}2. 第三方库兼容问题#
有些库没有正确的 ESM 导出:
// 强制使用 CommonJS 入口optimizeDeps: { include: ['problematic-lib > some-dep']}3. 热更新不生效#
检查几个常见原因:
- 文件路径大小写问题(macOS 不区分,Linux 区分)
- 组件没有正确导出
- 使用了不支持 HMR 的语法
4. 构建后样式丢失#
CSS 代码分割导致的,检查是否正确引入了 CSS:
import './styles/index.css'或者关闭 CSS 代码分割:
build: { cssCodeSplit: false}5. 路径别名在生产环境不工作#
确保 tsconfig.json 和 vite.config.ts 的别名配置一致:
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } }}6. 代理不生效#
检查 proxy 配置的 target 是否正确,以及是否需要 changeOrigin:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, secure: false, ws: true, } }}与其他工具对比#
| 对比项 | Vite | Webpack | Parcel | esbuild |
|---|---|---|---|---|
| 冷启动 | 极快 | 慢 | 中等 | 极快 |
| 热更新 | 极快 | 中等 | 快 | 无 |
| 生产构建 | 快(Rollup) | 中等 | 快 | 极快 |
| 配置复杂度 | 简单 | 复杂 | 极简 | 简单 |
| 插件生态 | 丰富 | 最丰富 | 一般 | 发展中 |
| 框架支持 | 全面 | 全面 | 全面 | 基础 |
| SSR 支持 | 内置 | 需配置 | 不支持 | 不支持 |