Vite极简入门
本文面向有一定前端基础的开发者,快速介绍 Vite 这款新一代前端构建工具的核心概念和使用方法。如果你已经熟悉 JavaScript 和基本的前端开发,这篇文章将帮助你快速上手 Vite。
# 一、Vite 简介
# 什么是 Vite
Vite(法语意为"快速",发音 /vit/,类似 "veet")是由 Vue.js 作者尤雨溪开发的下一代前端构建工具,于 2020 年发布。
核心特点:
- 极速的开发服务器启动:不需要打包,即开即用
- 即时的热模块更新(HMR):无论应用大小,HMR 始终快速
- 真正的按需编译:只编译当前页面需要的代码
- 优化的构建:基于 Rollup 的预配置构建
- 丰富的功能:TypeScript、JSX、CSS 预处理器等开箱即用
- 框架无关:支持 Vue、React、Svelte 等多种框架
# Vite vs Webpack
| 特性 | Vite | Webpack |
|---|---|---|
| 开发服务器启动 | 秒级 | 分钟级(大型项目) |
| HMR 速度 | 毫秒级 | 秒级 |
| 基础原理 | ESM + esbuild | Bundle |
| 配置复杂度 | 简单 | 复杂 |
| 生态成熟度 | 快速增长 | 非常成熟 |
| 生产构建 | Rollup | Webpack |
为什么 Vite 这么快?
- 开发环境:利用浏览器原生 ES 模块(ESM),不需要打包
- 预构建:使用 esbuild 预构建依赖(比 JavaScript 快 10-100 倍)
- 按需编译:只编译浏览器请求的模块
- 智能缓存:充分利用 HTTP 缓存
# Vite 工作原理
# 传统构建工具(Webpack)
源代码 → Bundle(打包所有模块)→ 开发服务器 → 浏览器
↑
耗时较长(整个项目)
# Vite
源代码 → 预构建依赖(esbuild)→ 开发服务器
↓
浏览器请求模块 → 按需编译(单个文件)→ 浏览器
↑
极快(仅处理请求的文件)
# 二、快速开始
# 系统要求
- Node.js 版本 18+ 或 20+(推荐使用最新 LTS 版本)
验证 Node.js 版本:
node -v
# 创建 Vite 项目
使用 npm create vite 快速创建项目:
# 创建项目
npm create vite@latest
# 按照提示选择
✔ Project name: my-vite-app
✔ Select a framework: › Vue
✔ Select a variant: › TypeScript
# 进入项目目录
cd my-vite-app
# 安装依赖
npm install
# 启动开发服务器
npm run dev
支持的框架模板:
vanilla:原生 JavaScriptvue:Vue 3react:Reactpreact:Preactlit:Litsvelte:Sveltesolid:Solidqwik:Qwik
每个框架都有 JavaScript 和 TypeScript 两种变体。
# 一键创建特定框架项目
# Vue
npm create vite@latest my-vue-app -- --template vue-ts
# React
npm create vite@latest my-react-app -- --template react-ts
# Vanilla
npm create vite@latest my-vanilla-app -- --template vanilla-ts
# 项目结构
my-vite-app/
├── public/ # 静态资源(不会被处理)
│ └── favicon.ico
├── src/
│ ├── assets/ # 资源文件(会被处理)
│ ├── components/ # 组件
│ ├── App.vue # 根组件
│ └── main.ts # 入口文件
├── index.html # HTML 入口(重要!)
├── package.json # 项目配置
├── tsconfig.json # TypeScript 配置
└── vite.config.ts # Vite 配置
关键点:
index.html是项目入口,而不是在public目录下- Vite 将
index.html视为源码和模块图的一部分
# package.json 脚本
{
"scripts": {
"dev": "vite", // 启动开发服务器
"build": "vite build", // 生产构建
"preview": "vite preview" // 预览生产构建
}
}
# 三、核心功能
# HTML 入口文件
Vite 以 index.html 作为入口:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<!-- 直接引入 TypeScript/JavaScript 模块 -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>
注意:
- 使用
<script type="module">引入入口文件 - 路径
/src/main.ts相对于项目根目录
# NPM 依赖预构建
Vite 会自动预构建 npm 依赖:
// 直接导入 npm 包
import { createApp } from 'vue'
import axios from 'axios'
import _ from 'lodash-es'
// Vite 自动处理这些导入
预构建的好处:
- 统一模块格式:将 CommonJS/UMD 转换为 ESM
- 性能优化:将多个内部模块合并,减少 HTTP 请求
- 缓存:预构建结果会被缓存
强制重新预构建:
# 清除缓存并重新预构建
npx vite --force
# TypeScript 支持
Vite 原生支持 TypeScript:
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
注意:
- Vite 只执行转译,不做类型检查
- 类型检查应由 IDE 和构建过程负责
在构建时进行类型检查:
npm install -D typescript
# 在 package.json 中添加
{
"scripts": {
"build": "tsc && vite build"
}
}
# CSS 处理
# 直接导入 CSS
// 导入全局样式
import './style.css'
// CSS Modules
import styles from './App.module.css'
// 预处理器
import './styles.scss'
import './styles.less'
# CSS Modules
/* App.module.css */
.container {
padding: 20px;
}
.title {
color: #42b983;
}
<script setup>
import styles from './App.module.css'
</script>
<template>
<div :class="styles.container">
<h1 :class="styles.title">Hello Vite</h1>
</div>
</template>
# CSS 预处理器
安装对应的预处理器即可使用:
# Sass
npm install -D sass
# Less
npm install -D less
# Stylus
npm install -D stylus
<style lang="scss">
$primary-color: #42b983;
.container {
color: $primary-color;
.title {
font-size: 2rem;
}
}
</style>
# PostCSS
Vite 自动应用 PostCSS 配置:
npm install -D autoprefixer
// postcss.config.js
export default {
plugins: {
autoprefixer: {}
}
}
# 静态资源处理
# 导入资源
// 导入为 URL
import imgUrl from './assets/logo.png'
// 在模板中使用
<img :src="imgUrl" alt="Logo" />
// 导入为字符串(需要添加 ?raw)
import textContent from './data.txt?raw'
// 导入为 Worker
import Worker from './worker.js?worker'
# public 目录
public 目录下的文件不会被处理,直接复制到输出目录:
public/
└── favicon.ico
# 访问方式
<link rel="icon" href="/favicon.ico" />
使用场景:
- 永远不会在源码中引用的资源
- 需要保持原文件名的资源
- 特定的第三方库文件
# 环境变量
Vite 使用 import.meta.env 暴露环境变量:
// 访问环境变量
console.log(import.meta.env.MODE) // 'development' 或 'production'
console.log(import.meta.env.BASE_URL) // 基础 URL
console.log(import.meta.env.PROD) // 是否生产环境
console.log(import.meta.env.DEV) // 是否开发环境
console.log(import.meta.env.SSR) // 是否 SSR
# .env 文件
# .env # 所有情况下都会加载
# .env.local # 所有情况下都会加载,但会被 git 忽略
# .env.[mode] # 只在指定模式下加载
# .env.[mode].local # 只在指定模式下加载,但会被 git 忽略
# .env.development
VITE_API_URL=http://localhost:3000
VITE_APP_TITLE=My App (Dev)
# .env.production
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App
重要:只有以 VITE_ 开头的变量才会暴露给客户端代码。
// 使用环境变量
const apiUrl = import.meta.env.VITE_API_URL
const appTitle = import.meta.env.VITE_APP_TITLE
# TypeScript 智能提示
// src/vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
# 构建优化
# 代码分割
Vite 自动进行代码分割:
// 动态导入自动代码分割
const UserProfile = () => import('./components/UserProfile.vue')
// 路由懒加载
const routes = [
{
path: '/user',
component: () => import('./views/User.vue')
}
]
# 手动分块
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
// 将 vue 相关库打包到一起
'vue-vendor': ['vue', 'vue-router', 'pinia'],
// 将工具库打包到一起
'utils': ['axios', 'lodash-es']
}
}
}
}
})
# 压缩
// vite.config.ts
export default defineConfig({
build: {
// 压缩方式:'terser' | 'esbuild'
minify: 'esbuild',
// terser 选项(使用 terser 时)
terserOptions: {
compress: {
drop_console: true, // 删除 console
drop_debugger: true // 删除 debugger
}
}
}
})
# 四、配置详解
# 基础配置
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path 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, // 是否生成 source map
minify: 'esbuild', // 压缩方式
// Rollup 配置
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]'
}
}
},
// 路径解析
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@utils': path.resolve(__dirname, './src/utils')
}
},
// CSS 配置
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
}
}
}
})
# 路径别名
// vite.config.ts
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
})
// 使用别名
import { useCounter } from '@/composables/useCounter'
import Button from '@/components/Button.vue'
TypeScript 配置:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
# 代理配置
开发时解决跨域问题:
// vite.config.ts
export default defineConfig({
server: {
proxy: {
// 字符串简写
'/api': 'http://localhost:8080',
// 完整配置
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
// 正则表达式
'^/fallback/.*': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fallback/, '')
},
// WebSocket
'/socket.io': {
target: 'ws://localhost:8080',
ws: true
}
}
}
})
# 多页面应用
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
export default defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin.html')
}
}
}
})
项目结构:
├── index.html
├── admin.html
├── src/
│ ├── main.ts
│ └── admin.ts
# 条件配置
根据命令或模式返回不同配置:
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig(({ command, mode }) => {
if (command === 'serve') {
// 开发环境配置
return {
server: {
port: 3000
}
}
} else {
// 生产环境配置
return {
build: {
minify: 'terser'
}
}
}
})
# 五、插件系统
# 常用官方插件
# Vue 插件
npm install -D @vitejs/plugin-vue
// vite.config.ts
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()]
})
# Vue JSX 插件
npm install -D @vitejs/plugin-vue-jsx
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [vue(), vueJsx()]
})
# React 插件
npm install -D @vitejs/plugin-react
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()]
})
# 常用社区插件
# Auto Import(自动导入)
npm install -D unplugin-auto-import
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts'
})
]
})
<script setup>
// 无需导入,自动可用
const count = ref(0)
const router = useRouter()
const store = useCounterStore()
</script>
# Components(组件自动导入)
npm install -D unplugin-vue-components
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
Components({
dts: 'src/components.d.ts',
dirs: ['src/components']
})
]
})
<template>
<!-- 无需导入,自动识别 -->
<Button />
<UserCard />
</template>
# Compression(压缩)
npm install -D vite-plugin-compression
import compression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
compression({
algorithm: 'gzip',
ext: '.gz'
})
]
})
# Visualizer(构建分析)
npm install -D rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true,
gzipSize: true,
brotliSize: true
})
]
})
# SVG 组件
npm install -D vite-svg-loader
import svgLoader from 'vite-svg-loader'
export default defineConfig({
plugins: [svgLoader()]
})
<script setup>
import IconLogo from '@/assets/logo.svg'
</script>
<template>
<IconLogo />
</template>
# 编写自定义插件
// plugins/my-plugin.ts
import type { Plugin } from 'vite'
export default function myPlugin(): Plugin {
return {
name: 'my-plugin',
// 服务器启动时调用
configResolved(config) {
console.log('配置已解析', config)
},
// 转换代码
transform(code, id) {
if (id.endsWith('.md')) {
return {
code: `export default ${JSON.stringify(code)}`,
map: null
}
}
},
// 处理 HMR
handleHotUpdate({ file, server }) {
if (file.endsWith('.md')) {
console.log('markdown 文件更新:', file)
server.ws.send({
type: 'custom',
event: 'md-update',
data: { file }
})
}
}
}
}
// vite.config.ts
import myPlugin from './plugins/my-plugin'
export default defineConfig({
plugins: [myPlugin()]
})
# 六、开发技巧
# HMR(热模块替换)
Vite 提供了 HMR API:
// 接受自身更新
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
console.log('模块已更新:', newModule)
})
}
// 接受依赖更新
import.meta.hot?.accept('./utils.ts', (newUtils) => {
console.log('依赖已更新')
})
// 自定义事件
import.meta.hot?.on('custom-event', (data) => {
console.log('收到自定义事件:', data)
})
// 清理副作用
import.meta.hot?.dispose((data) => {
// 保存状态
data.state = currentState
})
# Glob 导入
批量导入多个模块:
// 导入所有组件
const modules = import.meta.glob('./components/*.vue')
// 懒加载
const lazyModules = import.meta.glob('./views/*.vue')
// 立即导入
const eagerModules = import.meta.glob('./utils/*.ts', { eager: true })
// 只导入特定内容
const configs = import.meta.glob('./configs/*.json', {
query: '?raw',
import: 'default'
})
// 使用示例
Object.entries(modules).forEach(([path, module]) => {
console.log(path, module)
})
# 动态导入
// 动态导入
const module = await import('./module.ts')
// 带变量的动态导入
const moduleName = 'UserProfile'
const module = await import(`./components/${moduleName}.vue`)
// 条件导入
let api
if (isDevelopment) {
api = await import('./api-dev.ts')
} else {
api = await import('./api-prod.ts')
}
# Web Workers
// 方式1:使用 ?worker 后缀
import MyWorker from './worker?worker'
const worker = new MyWorker()
worker.postMessage({ msg: 'Hello' })
worker.addEventListener('message', (e) => {
console.log('收到消息:', e.data)
})
// 方式2:内联 Worker
import MyWorker from './worker?worker&inline'
// 方式3:使用 URL
const worker = new Worker(new URL('./worker.ts', import.meta.url))
// worker.ts
self.addEventListener('message', (e) => {
console.log('Worker 收到消息:', e.data)
self.postMessage({ result: e.data.msg.toUpperCase() })
})
# 构建优化
# 预加载
// vite.config.ts
export default defineConfig({
build: {
modulePreload: {
polyfill: true
}
}
})
# 压缩图片
npm install -D vite-plugin-imagemin
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig({
plugins: [
viteImagemin({
gifsicle: {
optimizationLevel: 7
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 80
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
}
]
}
})
]
})
# CDN 加速
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
external: ['vue', 'vue-router'],
output: {
globals: {
vue: 'Vue',
'vue-router': 'VueRouter'
}
}
}
}
})
<!-- index.html -->
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@4"></script>
# 七、部署
# 构建应用
# 构建
npm run build
# 预览构建结果
npm run preview
构建产物在 dist 目录:
dist/
├── assets/
│ ├── index-[hash].js
│ ├── index-[hash].css
│ └── logo-[hash].png
└── index.html
# 基础路径
部署到非根路径时需要配置 base:
// vite.config.ts
export default defineConfig({
base: '/my-app/'
})
# 静态资源路径
// 公共基础路径
export default defineConfig({
base: 'https://cdn.example.com/'
})
// 相对路径(推荐)
export default defineConfig({
base: './'
})
# 部署到不同平台
# Vercel
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
# Netlify
# netlify.toml
[build]
command = "npm run build"
publish = "dist"
# GitHub Pages
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm run build
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
// vite.config.ts(如果部署到 username.github.io/repo)
export default defineConfig({
base: '/repo/'
})
# Docker
# Dockerfile
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# Nginx
# nginx.conf
server {
listen 80;
server_name example.com;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# 缓存静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# 八、常见问题
# 依赖预构建问题
问题:某些依赖导致开发服务器缓慢或报错。
解决:
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: ['some-package'], // 强制预构建
exclude: ['another-package'], // 排除预构建
esbuildOptions: {
// esbuild 配置
}
}
})
清除缓存:
npx vite --force
# CommonJS 模块
问题:某些库是 CommonJS 格式,导致警告。
解决:
// vite.config.ts
export default defineConfig({
build: {
commonjsOptions: {
include: [/node_modules/],
transformMixedEsModules: true
}
}
})
# 大文件警告
问题:打包后单个文件过大。
解决:
// vite.config.ts
export default defineConfig({
build: {
chunkSizeWarningLimit: 1000, // 提高警告阈值(KB)
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
}
})
# 环境变量不生效
问题:.env 文件中的变量无法访问。
检查清单:
- 变量是否以
VITE_开头 .env文件是否在项目根目录- 是否重启了开发服务器
- 是否正确使用
import.meta.env.VITE_XXX
# 路径别名报错
问题:TypeScript 无法识别路径别名。
解决:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
# 九、最佳实践
# 1. 合理使用路径别名
// 推荐
import Button from '@/components/Button.vue'
import { formatDate } from '@/utils/date'
// 不推荐
import Button from '../../../components/Button.vue'
# 2. 环境变量管理
# .env.development
VITE_API_URL=http://localhost:3000
VITE_ENABLE_MOCK=true
# .env.production
VITE_API_URL=https://api.example.com
VITE_ENABLE_MOCK=false
# 3. 代码分割
// 路由懒加载
const routes = [
{
path: '/about',
component: () => import('./views/About.vue')
}
]
// 组件懒加载
const AsyncComponent = defineAsyncComponent(() =>
import('./components/Heavy.vue')
)
# 4. 优化依赖
// 只导入需要的部分
import { debounce } from 'lodash-es' // ✅ 推荐
import _ from 'lodash' // ❌ 不推荐(导入整个库)
# 5. 利用 Tree Shaking
// 使用 ES 模块
export function add(a, b) { return a + b }
export function subtract(a, b) { return a - b }
// 只导入需要的
import { add } from './math' // subtract 不会被打包
# 十、学习资源
# 官方文档
# 推荐资源
- Awesome Vite (opens new window):精选插件和资源
- Vite Rollup Plugins (opens new window):兼容的 Rollup 插件
# 相关文章
# 插件市场
- unplugin (opens new window):通用插件系统
- Vite PWA (opens new window):PWA 支持
- vite-plugin-pages (opens new window):文件系统路由
祝你变得更强!
编辑 (opens new window)
上次更新: 2025/11/12