Appearance
Vite 全面指南
Vite(法语意为"快速")是由Vue.js作者尤雨溪开发的新一代前端构建工具。它利用ES模块的原生支持和现代浏览器的能力,提供了极快的开发服务器启动速度和热更新体验。
Vite 简介
核心特性
- 极速的服务器启动:利用ES模块,无需打包即可启动
- 闪电般的热更新:基于ES模块的HMR,更新速度不受应用大小影响
- 丰富的功能:支持TypeScript、JSX、CSS预处理器等
- 优化的构建:使用Rollup进行生产构建,内置优化
- 通用的插件接口:兼容Rollup插件生态
- 完全类型化的API:TypeScript支持
为什么选择Vite?
传统构建工具的问题
传统工具(如Webpack):
启动 → 打包整个应用 → 启动服务器
更新 → 重新打包相关模块 → 热更新
问题:
- 启动时间随项目增大而增长
- 热更新速度受影响
- 开发体验下降Vite的解决方案
Vite:
启动 → 直接启动服务器 → 按需编译
更新 → 只编译变更的模块 → 即时热更新
优势:
- 启动时间恒定
- 热更新速度恒定
- 开发体验极佳快速开始
创建新项目
bash
# 使用npm
npm create vite@latest my-vite-app
# 使用yarn
yarn create vite my-vite-app
# 使用pnpm
pnpm create vite my-vite-app
# 直接指定模板
npm create vite@latest my-vue-app -- --template vue
npm create vite@latest my-react-app -- --template react
npm create vite@latest my-svelte-app -- --template svelte可用模板
bash
# JavaScript模板
vite create my-app --template vanilla
vite create my-app --template vue
vite create my-app --template react
vite create my-app --template preact
vite create my-app --template lit
vite create my-app --template svelte
# TypeScript模板
vite create my-app --template vanilla-ts
vite create my-app --template vue-ts
vite create my-app --template react-ts
vite create my-app --template preact-ts
vite create my-app --template lit-ts
vite create my-app --template svelte-ts项目结构
my-vite-app/
├── public/
│ └── favicon.ico
├── src/
│ ├── assets/
│ ├── components/
│ ├── App.vue
│ └── main.js
├── index.html # 入口HTML文件
├── package.json
├── vite.config.js # Vite配置文件
└── README.md基本命令
bash
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build
# 预览生产构建
npm run previewVite配置
基础配置
javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
// 插件配置
plugins: [vue()],
// 开发服务器配置
server: {
port: 3000,
open: true,
cors: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
// 构建配置
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin.html')
},
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]'
}
}
},
// 路径别名
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
'@assets': resolve(__dirname, 'src/assets')
}
},
// CSS配置
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
},
less: {
modifyVars: {
'primary-color': '#1890ff'
},
javascriptEnabled: true
}
},
modules: {
localsConvention: 'camelCase'
}
},
// 环境变量
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version)
}
})条件配置
javascript
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig(({ command, mode }) => {
const config = {
plugins: [],
server: {},
build: {}
}
if (command === 'serve') {
// 开发环境配置
config.server = {
port: 3000,
open: true
}
} else {
// 生产环境配置
config.build = {
minify: 'terser',
sourcemap: false
}
}
if (mode === 'development') {
config.define = {
__DEV__: true
}
}
return config
})TypeScript配置
javascript
// 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')
}
},
server: {
port: 3000
}
})json
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}插件系统
官方插件
javascript
// Vue项目
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [
vue(),
vueJsx()
]
})
// React项目
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()]
})
// Svelte项目
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
plugins: [svelte()]
})常用社区插件
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// 自动导入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// PWA
import { VitePWA } from 'vite-plugin-pwa'
// 环境变量类型
import { loadEnv } from 'vite'
// ESLint
import eslint from 'vite-plugin-eslint'
// Mock
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
plugins: [
vue(),
// ESLint检查
eslint({
include: ['src/**/*.js', 'src/**/*.vue', 'src/**/*.ts'],
exclude: ['node_modules']
}),
// 自动导入API
AutoImport({
imports: [
'vue',
'vue-router',
'pinia'
],
resolvers: [ElementPlusResolver()],
dts: true
}),
// 自动导入组件
Components({
resolvers: [ElementPlusResolver()],
dts: true
}),
// PWA
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}']
},
manifest: {
name: 'My Vite App',
short_name: 'ViteApp',
description: 'My Awesome Vite App',
theme_color: '#ffffff',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png'
}
]
}
}),
// Mock服务
viteMockServe({
mockPath: 'mock',
localEnabled: mode === 'development',
prodEnabled: false
})
]
}
})自定义插件
javascript
// plugins/custom-plugin.js
function customPlugin(options = {}) {
return {
name: 'custom-plugin',
configResolved(config) {
// 配置解析完成后调用
console.log('Config resolved:', config)
},
buildStart(opts) {
// 构建开始时调用
console.log('Build started')
},
load(id) {
// 加载模块时调用
if (id === 'virtual:my-module') {
return 'export const msg = "Hello from virtual module"'
}
},
transform(code, id) {
// 转换代码时调用
if (id.endsWith('.special')) {
return `export default ${JSON.stringify(code)}`
}
},
generateBundle(options, bundle) {
// 生成bundle时调用
console.log('Bundle generated')
}
}
}
// 使用自定义插件
export default defineConfig({
plugins: [
vue(),
customPlugin({
option1: 'value1'
})
]
})开发特性
热模块替换 (HMR)
javascript
// 在Vue组件中使用HMR API
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 模块更新时的回调
console.log('Module updated:', newModule)
})
import.meta.hot.dispose(() => {
// 模块销毁时的清理工作
console.log('Module disposed')
})
}
// 条件性HMR
if (import.meta.hot) {
import.meta.hot.accept('./dependency.js', (newDep) => {
// 只有当dependency.js更新时才触发
updateDependency(newDep)
})
}环境变量
bash
# .env
VITE_APP_TITLE=My Vite App
VITE_API_URL=https://api.example.com
# .env.local
VITE_SECRET_KEY=secret123
# .env.development
VITE_API_URL=http://localhost:3000
VITE_DEBUG=true
# .env.production
VITE_API_URL=https://prod-api.example.com
VITE_DEBUG=falsejavascript
// 在代码中使用环境变量
console.log(import.meta.env.VITE_APP_TITLE)
console.log(import.meta.env.VITE_API_URL)
console.log(import.meta.env.MODE) // 'development' 或 'production'
console.log(import.meta.env.DEV) // boolean
console.log(import.meta.env.PROD) // boolean
// TypeScript类型定义
// src/vite-env.d.ts
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
readonly VITE_API_URL: string
readonly VITE_SECRET_KEY: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}静态资源处理
javascript
// 导入静态资源
import imgUrl from './img.png'
import workerUrl from './worker.js?worker'
import inlineWorker from './worker.js?worker&inline'
// 显式URL导入
import assetAsURL from './asset.js?url'
// 导入为字符串
import assetAsString from './shader.glsl?raw'
// 动态导入
const modules = import.meta.glob('./dir/*.js')
const components = import.meta.glob('./components/*.vue', { eager: true })
// 使用示例
function loadAssets() {
// 图片
const img = new Image()
img.src = imgUrl
// Worker
const worker = new Worker(workerUrl)
// 动态加载模块
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod)
})
}
}CSS功能
scss
// CSS Modules
// styles.module.css
.button {
background: blue;
color: white;
}
.button:hover {
background: darkblue;
}javascript
// 使用CSS Modules
import styles from './styles.module.css'
function Button() {
return <button className={styles.button}>Click me</button>
}scss
// SCSS预处理器
// variables.scss
$primary-color: #3498db;
$secondary-color: #2ecc71;
@mixin button-style($bg-color) {
background: $bg-color;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
&:hover {
background: darken($bg-color, 10%);
}
}
// component.scss
@import './variables.scss';
.primary-button {
@include button-style($primary-color);
}
.secondary-button {
@include button-style($secondary-color);
}css
/* PostCSS自动前缀 */
.container {
display: flex;
transform: translateX(100px);
user-select: none;
}
/* 编译后自动添加前缀 */
.container {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-transform: translateX(100px);
transform: translateX(100px);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}构建优化
代码分割
javascript
// 路由级别的代码分割
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/about',
component: () => import('./views/About.vue')
},
{
path: '/user',
component: () => import('./views/User.vue')
}
]
// 组件级别的代码分割
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
// 手动代码分割
const loadModule = () => import('./heavy-module.js')
function handleClick() {
loadModule().then(module => {
module.doSomething()
})
}构建配置优化
javascript
// vite.config.js
export default defineConfig({
build: {
// 启用/禁用 gzip 压缩大小报告
reportCompressedSize: false,
// chunk 大小警告的限制(以 kbs 为单位)
chunkSizeWarningLimit: 500,
// 自定义底层的 Rollup 打包配置
rollupOptions: {
output: {
// 分包策略
manualChunks: {
// 将 React 相关库打包成单独的 chunk
react: ['react', 'react-dom'],
// 将 Lodash 打包成单独的 chunk
lodash: ['lodash'],
// 将组件库打包成单独的 chunk
antd: ['antd']
}
}
},
// 压缩配置
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}
})预构建优化
javascript
// vite.config.js
export default defineConfig({
optimizeDeps: {
// 强制预构建链接的包
include: ['lodash', 'axios'],
// 在预构建中强制排除的依赖项
exclude: ['your-local-package'],
// 传递给 esbuild 的选项
esbuildOptions: {
target: 'es2020'
}
}
})部署
静态部署
bash
# 构建项目
npm run build
# 部署到静态服务器
# dist/ 目录包含所有构建产物服务器配置
nginx
# Nginx配置
server {
listen 80;
server_name example.com;
root /var/www/dist;
index index.html;
# 处理SPA路由
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}apache
# Apache配置 (.htaccess)
RewriteEngine On
RewriteBase /
# 处理SPA路由
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
# 静态资源缓存
<FilesMatch "\.(js|css|png|jpg|jpeg|gif|ico|svg)$">
ExpiresActive On
ExpiresDefault "access plus 1 year"
</FilesMatch>Docker部署
dockerfile
# Dockerfile
FROM node:18-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]yaml
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "80:80"
environment:
- NODE_ENV=productionCI/CD配置
yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Deploy to S3
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Sync to S3
run: aws s3 sync dist/ s3://my-bucket --delete
- name: Invalidate CloudFront
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"迁移指南
从Webpack迁移
javascript
// webpack.config.js → vite.config.js
// Webpack配置
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
]
}
}
// 对应的Vite配置
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
css: {
preprocessorOptions: {
scss: {
// SCSS配置
}
}
}
})常见迁移问题
javascript
// 1. require() → import
// 旧的写法
const utils = require('./utils')
// 新的写法
import utils from './utils'
// 2. 动态require → 动态import
// 旧的写法
const module = require(`./modules/${name}.js`)
// 新的写法
const module = await import(`./modules/${name}.js`)
// 3. process.env → import.meta.env
// 旧的写法
if (process.env.NODE_ENV === 'development') {
// ...
}
// 新的写法
if (import.meta.env.DEV) {
// ...
}
// 4. __dirname → import.meta.url
// 旧的写法
const currentDir = __dirname
// 新的写法
import { fileURLToPath, URL } from 'node:url'
const currentDir = fileURLToPath(new URL('.', import.meta.url))性能优化
开发时性能
javascript
// vite.config.js
export default defineConfig({
server: {
// 预热常用文件
warmup: {
clientFiles: ['./src/components/*.vue', './src/utils/*.ts']
}
},
optimizeDeps: {
// 预构建大型依赖
include: ['lodash', 'moment', 'echarts']
}
})构建时性能
javascript
// vite.config.js
export default defineConfig({
build: {
// 使用更快的压缩器
minify: 'esbuild', // 比terser快但压缩率稍低
// 禁用压缩大小报告以加快构建
reportCompressedSize: false,
// 调整chunk大小警告限制
chunkSizeWarningLimit: 1000,
rollupOptions: {
// 外部化依赖
external: ['vue', 'vue-router'],
output: {
globals: {
vue: 'Vue',
'vue-router': 'VueRouter'
}
}
}
}
})故障排除
常见问题
javascript
// 1. 依赖预构建问题
// 清除缓存
rm -rf node_modules/.vite
// 强制重新预构建
vite --force
// 2. 路径解析问题
// 检查别名配置
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
// 3. 环境变量问题
// 确保变量以VITE_开头
VITE_API_URL=http://localhost:3000
// 4. 热更新不工作
// 检查文件监听配置
server: {
watch: {
usePolling: true // 在某些系统上需要
}
}调试技巧
javascript
// 启用调试模式
DEBUG=vite:* npm run dev
// 分析构建产物
npm run build -- --mode analyze
// 检查依赖预构建
vite optimize --force
// 查看配置
vite config最佳实践
1. 项目组织
src/
├── assets/ # 静态资源
├── components/ # 可复用组件
├── composables/ # 组合式函数
├── layouts/ # 布局组件
├── pages/ # 页面组件
├── plugins/ # 插件
├── stores/ # 状态管理
├── styles/ # 样式文件
├── utils/ # 工具函数
├── App.vue
└── main.js2. 性能优化
javascript
// 懒加载路由
const routes = [
{
path: '/heavy',
component: () => import('./views/Heavy.vue')
}
]
// 预加载关键资源
<link rel="preload" href="/critical.css" as="style">
<link rel="prefetch" href="/next-page.js">
// 使用Web Workers
const worker = new Worker(new URL('./worker.js', import.meta.url))3. 代码质量
javascript
// ESLint + Prettier配置
// .eslintrc.js
module.exports = {
extends: [
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier'
]
}
// Husky + lint-staged
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,vue,ts}": ["eslint --fix", "git add"]
}
}总结
Vite是现代前端开发的优秀构建工具,具有以下特点:
优势
- 极快的启动速度:基于ES模块的开发服务器
- 即时热更新:不受项目大小影响的HMR
- 现代化:原生支持TypeScript、JSX、CSS预处理器
- 优化的构建:基于Rollup的生产构建
- 丰富的生态:兼容Rollup插件,活跃的社区
适用场景
- 现代前端项目:Vue、React、Svelte等
- 快速原型开发:极快的启动和热更新
- 大型项目:优秀的构建性能
- 团队协作:统一的开发体验
学习建议
- 理解ES模块:Vite的核心原理
- 掌握配置:灵活的配置系统
- 学习插件:扩展Vite功能
- 实践项目:在实际项目中使用
- 关注生态:跟上社区发展
Vite代表了前端构建工具的未来方向,其快速、现代化的特性使其成为新项目的首选构建工具。
