Vite 构建工具完全指南
Vite 简介
Vite 是下一代前端构建工具,由 Vue.js 的作者尤雨溪开发。它利用浏览器原生 ES 模块支持,提供极速的开发服务器启动和热模块替换(HMR)。
核心优势
- 极速启动:不需要打包,直接提供原生 ESM 模块
- 闪电般 HMR:模块更新速度快,不受应用规模影响
- 丰富的功能:开箱支持 TypeScript、JSX、CSS 等
- 优化的构建:基于 Rollup,高度可配置
- 插件系统:兼容 Rollup 插件生态
快速开始
创建项目
# 使用 npm
npm create vite@latest my-app
# 使用 yarn
yarn create vite my-app
# 使用 pnpm
pnpm create vite my-app
# 使用 bun
bun create vite my-app
# 指定模板
npm create vite@latest my-app -- --template vue-ts
npm create vite@latest my-app -- --template react-ts
npm create vite@latest my-app -- --template vanilla-ts
npm create vite@latest my-app -- --template preact-ts
npm create vite@latest my-app -- --template lit-ts
npm create vite@latest my-app -- --template svelte-ts项目结构
my-app/
├── index.html # 入口 HTML
├── package.json # 项目配置
├── vite.config.ts # Vite 配置
├── tsconfig.json # TypeScript 配置
├── public/ # 静态资源(不经过构建)
│ └── favicon.ico
└── src/ # 源代码
├── main.ts # 应用入口
├── App.vue # 根组件(Vue)
├── components/ # 组件
├── assets/ # 资源文件(经过构建)
└── styles/ # 样式文件启动开发服务器
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 或
npx vite
# 指定端口
npx vite --port 3000
# 指定主机
npx vite --host 0.0.0.0
# 打开浏览器
npx vite --open
# HTTPS
npx vite --https配置文件详解
Vite 配置文件为 vite.config.ts(或 .js、.mjs)。
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import compression from 'vite-plugin-compression';
export default defineConfig(({ mode, command }) => {
// mode: 'development' | 'production'
// command: 'serve' | 'build'
return {
// 根目录(默认 process.cwd())
root: process.cwd(),
// 公共基础路径
base: '/',
// 模式
mode: mode,
// 插件
plugins: [
vue(),
// react(),
],
// 解析选项
resolve: {
// 路径别名
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
'@assets': resolve(__dirname, 'src/assets'),
},
// 自动补全扩展名
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
// 条件导出
conditions: ['import'],
},
// CSS 配置
css: {
// CSS 模块
modules: {
localsConvention: 'camelCaseOnly',
generateScopedName: '[name]__[local]___[hash:base64:5]',
},
// PostCSS 配置
postcss: {
plugins: [
require('autoprefixer'),
require('tailwindcss'),
],
},
// 预处理器选项
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`,
javascriptEnabled: true,
},
less: {
modifyVars: {
'@primary-color': '#1890ff',
},
javascriptEnabled: true,
},
stylus: {
// stylus 选项
},
},
// 是否启用 CSS 代码分割
codeSplitting: true,
// 开发服务器中启用 source map
devSourcemap: true,
},
// JSON 配置
json: {
stringify: false, // 启用后 JSON 文件会被导入为字符串
},
// 静态资源处理
assetsInclude: ['**/*.gltf', '**/*.hdr'],
// 开发服务器配置
server: {
// 服务器主机
host: 'localhost',
// 服务器端口
port: 3000,
// 启动时自动打开浏览器
open: true,
// 启用 CORS
cors: true,
// 强制转换请求(用于开发)
force: false,
// 代理配置
proxy: {
// 字符串简写
'/api': 'http://localhost:8080',
// 对象配置
'/api/v1': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ''),
},
// WebSocket 代理
'/socket.io': {
target: 'ws://localhost:3000',
ws: true,
changeOrigin: true,
},
// 自定义代理
'^/fallback/.*': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fallback/, ''),
configure: (proxy, options) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('Proxying:', req.url);
});
},
},
},
// 文件系统监听
watch: {
usePolling: false,
interval: 100,
},
// HTTPS 配置
https: {
key: './server.key',
cert: './server.cert',
},
// 预热文件(预转换)
warmup: {
clientFiles: ['./src/main.ts', './src/App.vue'],
},
},
// 预览服务器配置(类似 server)
preview: {
port: 4173,
},
// 构建选项
build: {
// 输出目录
outDir: 'dist',
// 清空输出目录
emptyOutDir: true,
// 静态资源目录
assetsDir: 'assets',
// 生成 manifest 文件
manifest: false,
// 禁用 gzip 压缩大小报告
brotliSize: true,
// 代码分割策略
chunkSizeWarningLimit: 500,
// 静态资源内联阈值
assetsInlineLimit: 4096, // 4kb
// CSS 代码分割
cssCodeSplit: true,
// source map
sourcemap: false,
// 压缩选项
minify: 'esbuild', // 'terser' | 'esbuild' | false
// Terser 选项(当 minify 为 'terser' 时)
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
// Rollup 选项
rollupOptions: {
// 外部依赖(不打包)
external: ['vue', 'react', 'react-dom'],
// 输出选项
output: {
// 手动分块
manualChunks: {
'vendor': ['vue', 'vue-router', 'pinia'],
'utils': ['lodash', 'dayjs'],
},
// 文件名格式
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
// 全局变量(配合 external 使用)
globals: {
vue: 'Vue',
react: 'React',
},
},
// 插件
plugins: [
// rollup 插件
],
// treeshaking 选项
treeshake: {
moduleSideEffects: false,
},
},
// CommonJS 兼容
commonjsOptions: {
include: [/node_modules/],
},
// 动态导入预加载
modulePreload: {
polyfill: true,
},
// 报告压缩大小
reportCompressedSize: true,
},
// 依赖优化
optimizeDeps: {
// 预转换依赖
include: ['lodash-es', 'axios'],
// 排除依赖
exclude: ['@vue/compiler-sfc'],
// 强制预转换
force: false,
// 自动处理
entries: ['index.html'],
// esbuild 选项
esbuildOptions: {
target: 'es2020',
},
},
// SSR 特定选项
ssr: {
// SSR 外部化依赖
external: [
'lodash',
'moment',
],
// 不外部化的依赖
noExternal: ['some-package'],
// 优化选项
optimizeDeps: {
include: ['axios'],
},
},
// 环境变量前缀
envPrefix: 'VITE_',
// 工作区根目录
workspaceRoot: process.cwd(),
// 清除屏幕
clearScreen: true,
// 日志级别
logLevel: 'info', // 'info' | 'warn' | 'error' | 'silent'
// 自定义 logger
customLogger: undefined,
};
});环境变量
环境变量文件
Vite 使用 dotenv 加载环境变量。
# .env
VITE_APP_TITLE=我的应用
VITE_API_URL=http://localhost:8080/api
VITE_ENABLE_ANALYTICS=true
# .env.development
VITE_API_URL=http://localhost:8080/api
VITE_DEBUG=true
# .env.production
VITE_API_URL=https://api.example.com
VITE_ENABLE_ANALYTICS=false
# .env.staging
VITE_API_URL=https://staging-api.example.com在代码中使用
// 访问环境变量
const appTitle = import.meta.env.VITE_APP_TITLE;
const apiUrl = import.meta.env.VITE_API_URL;
// 内置环境变量
console.log(import.meta.env.MODE); // 'development' | 'production'
console.log(import.meta.env.DEV); // 开发模式为 true
console.log(import.meta.env.PROD); // 生产模式为 true
console.log(import.meta.env.SSR); // 是否为 SSR
// TypeScript 类型定义
// env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string;
readonly VITE_API_URL: string;
readonly VITE_ENABLE_ANALYTICS: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}环境变量模式
# 开发模式
vite # 加载 .env 和 .env.development
# 生产构建
vite build # 加载 .env 和 .env.production
# 自定义模式
vite build --mode staging # 加载 .env 和 .env.staging
vite --mode development # 开发模式HTML 入口
Vite 支持 HTML 文件作为入口。
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 环境变量替换 -->
<title>%VITE_APP_TITLE%</title>
<!-- 预加载关键资源 -->
<link rel="modulepreload" href="/src/main.ts" />
</head>
<body>
<div id="app"></div>
<!-- 入口脚本(必须是 type="module") -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>静态资源处理
导入资源
// 直接导入(会被转换为 URL)
import logo from './assets/logo.png';
import video from './assets/video.mp4';
// 使用资源
<img :src="logo" />
<video :src="video"></video>
// URL 引用
const logoUrl = new URL('./assets/logo.png', import.meta.url).href;Public 目录
public/
├── favicon.ico
├── robots.txt
└── images/
└── background.jpg<!-- 在 HTML 中直接引用 -->
<img src="/images/background.jpg" />
<link rel="icon" href="/favicon.ico" />资源内联
// 小于 4kb 的资源会被内联为 base64
// 可以通过 ?url 强制使用 URL
import smallImage from './small.png'; // 可能是 base64
import largeImage from './large.png?url'; // 始终是 URL
// 通过 ?raw 导入原始内容
import textContent from './data.txt?raw';插件系统
官方插件
// Vue 支持
import vue from '@vitejs/plugin-vue';
// React 支持
import react from '@vitejs/plugin-react';
// Preact 支持
import preact from '@vitejs/plugin-preact';
// Legacy 浏览器支持
import legacy from '@vitejs/plugin-legacy';常用社区插件
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { visualizer } from 'rollup-plugin-visualizer';
import compression from 'vite-plugin-compression';
import vitePluginImp from 'vite-plugin-imp';
import viteSvgIcons from 'vite-plugin-svg-icons';
import viteCompression from 'vite-plugin-compression';
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(),
// 包体积分析
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
}),
// Gzip 压缩
compression({
algorithm: 'gzip',
ext: '.gz',
threshold: 10240, // 10kb
}),
// 按需导入(Element Plus)
vitePluginImp({
libList: [
{
libName: 'element-plus',
style: (name) => `element-plus/theme-chalk/${name}.css`,
},
],
}),
// SVG 图标
viteSvgIcons({
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
symbolId: 'icon-[dir]-[name]',
}),
// 自动导入 API
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()],
dts: 'src/auto-imports.d.ts',
}),
// 自动导入组件
Components({
resolvers: [ElementPlusResolver()],
dts: 'src/components.d.ts',
}),
],
});编写自定义插件
// 简单的 Vite 插件
import type { Plugin } from 'vite';
function myPlugin(): Plugin {
return {
name: 'my-plugin',
// 解析配置
configResolved(config) {
console.log('Config resolved:', config.mode);
},
// 转换模块
transform(code, id) {
if (id.endsWith('.vue')) {
// 转换 Vue 文件
return {
code: code.replace(/console\.log\(.*?\);?/g, ''),
map: null,
};
}
},
// 转换 HTML
transformIndexHtml(html) {
return html.replace(
'</body>',
'<script>console.log("Injected!")</script></body>'
);
},
// 生成 bundle 后
generateBundle(options, bundle) {
// 处理生成的文件
},
};
}
// 带选项的插件
interface MyPluginOptions {
include?: string[];
exclude?: string[];
}
function myPluginWithOptions(options: MyPluginOptions = {}): Plugin {
const { include = [], exclude = [] } = options;
return {
name: 'my-plugin-with-options',
transform(code, id) {
if (include.some(pattern => id.includes(pattern))) {
// 转换
}
if (exclude.some(pattern => id.includes(pattern))) {
return; // 跳过
}
},
};
}多页面应用
// vite.config.ts
import { resolve } from 'path';
export default defineConfig({
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/main.ts'),
admin: resolve(__dirname, 'src/admin/main.ts'),
user: resolve(__dirname, 'src/user/main.ts'),
},
output: {
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
},
},
},
});<!-- index.html -->
<script type="module" src="/src/main.ts"></script>
<!-- admin.html -->
<script type="module" src="/src/admin/main.ts"></script>
<!-- user.html -->
<script type="module" src="/src/user/main.ts"></script>库模式
将项目打包为库供他人使用。
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLibrary',
fileName: (format) => `my-library.${format}.js`,
formats: ['es', 'umd', 'iife'],
},
rollupOptions: {
// 外部化依赖
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
},
},
},
});性能优化
代码分割
// 路由级别的代码分割
const routes = [
{
path: '/',
component: () => import('./views/Home.vue'),
},
{
path: '/about',
component: () => import('./views/About.vue'),
},
{
path: '/admin',
component: () => import('./views/Admin.vue'),
},
];
// 组件级别的懒加载
const HeavyComponent = defineAsyncComponent(
() => import('./components/HeavyComponent.vue')
);预加载
// vite.config.ts
export default defineConfig({
build: {
modulePreload: {
polyfill: true,
},
},
});<!-- 手动预加载 -->
<link rel="modulepreload" href="/assets/vendor-hash.js" />
<link rel="modulepreload" href="/assets/main-hash.js" />依赖预构建
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: [
'lodash-es',
'axios',
'dayjs',
],
exclude: [
'@vue/compiler-sfc',
],
},
});常见问题
1. 开发服务器慢
// 预构建依赖
optimizeDeps: {
include: ['large-package'],
force: true, // 强制重新预构建
}2. 热更新不生效
// 检查文件路径是否正确
server: {
watch: {
usePolling: true,
interval: 100,
},
}3. 构建后路径错误
// 配置正确的 base
export default defineConfig({
base: '/my-app/', // 如果部署在子目录
});4. 环境变量不生效
# 确保使用 VITE_ 前缀
VITE_APP_TITLE=MyApp
# 重启开发服务器
# 清除缓存
rm -rf node_modules/.vite5. TypeScript 路径别名不生效
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}// vite.config.ts
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
}