Appearance
Vite配置与插件
课程概述
本章节深入探讨 Vite 的配置系统和插件生态,学习如何通过配置和插件来定制和扩展 Vite 的功能。掌握 Vite 的配置技巧和插件开发能力,能够帮助您构建更强大、更高效的前端应用。
学习目标
- 深入理解 Vite 配置文件的结构和选项
- 掌握各种配置项的使用方法和最佳实践
- 学习如何选择和使用 Vite 插件
- 了解常用插件的功能和配置
- 掌握自定义插件的开发方法
- 学习插件的执行顺序和生命周期
第一部分:Vite 配置基础
1.1 配置文件概述
Vite 使用 vite.config.ts (或 .js, .mjs) 作为配置文件:
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
export default defineConfig({
// 配置选项
plugins: [react()],
server: {
port: 3000
}
})配置文件位置:
project/
├── vite.config.ts # 默认配置文件
├── vite.config.js # JavaScript 配置
├── vite.config.mjs # ES 模块配置
└── package.json1.2 智能配置提示
使用 defineConfig 获得 TypeScript 类型提示:
typescript
import { defineConfig } from 'vite'
import type { UserConfig } from 'vite'
// 方式 1: 使用 defineConfig
export default defineConfig({
// 自动类型提示
})
// 方式 2: 类型注解
const config: UserConfig = {
// 手动类型提示
}
export default config
// 方式 3: JSDoc
/** @type {import('vite').UserConfig} */
export default {
// JSDoc 类型提示
}1.3 条件配置
根据环境或命令返回不同配置:
typescript
import { defineConfig } from 'vite'
export default defineConfig(({ command, mode }) => {
if (command === 'serve') {
// 开发环境配置
return {
server: {
port: 3000
}
}
} else {
// 生产环境配置
return {
build: {
rollupOptions: {
// ...
}
}
}
}
})参数说明:
typescript
interface ConfigEnv {
command: 'build' | 'serve' // 运行的命令
mode: string // 环境模式
ssrBuild?: boolean // 是否为 SSR 构建
}1.4 异步配置
支持异步配置函数:
typescript
import { defineConfig } from 'vite'
export default defineConfig(async ({ command, mode }) => {
const data = await asyncFunction()
return {
// 使用异步数据的配置
define: {
__APP_DATA__: JSON.stringify(data)
}
}
})1.5 多配置文件
为不同场景创建多个配置文件:
typescript
// vite.config.base.ts - 基础配置
import { defineConfig } from 'vite'
export const baseConfig = defineConfig({
// 共享配置
resolve: {
alias: {
'@': '/src'
}
}
})typescript
// vite.config.dev.ts - 开发配置
import { defineConfig, mergeConfig } from 'vite'
import { baseConfig } from './vite.config.base'
export default mergeConfig(baseConfig, defineConfig({
server: {
port: 3000
}
}))typescript
// vite.config.prod.ts - 生产配置
import { defineConfig, mergeConfig } from 'vite'
import { baseConfig } from './vite.config.base'
export default mergeConfig(baseConfig, defineConfig({
build: {
minify: 'terser'
}
}))使用特定配置:
bash
# 使用开发配置
vite --config vite.config.dev.ts
# 使用生产配置
vite build --config vite.config.prod.ts第二部分:核心配置选项
2.1 根目录配置
typescript
export default defineConfig({
// 项目根目录
root: process.cwd(),
// 公共基础路径
base: '/',
// 环境变量目录
envDir: './',
// 环境变量前缀
envPrefix: 'VITE_'
})base 配置示例:
typescript
// 部署到子路径
export default defineConfig({
base: '/my-app/', // https://example.com/my-app/
})
// 相对路径 (使用 ./)
export default defineConfig({
base: './', // 相对于部署位置
})
// 使用环境变量
export default defineConfig({
base: process.env.VITE_BASE_URL || '/'
})2.2 路径解析配置
typescript
import { resolve } from 'path'
export default defineConfig({
resolve: {
// 路径别名
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
'@assets': resolve(__dirname, 'src/assets'),
'@hooks': resolve(__dirname, 'src/hooks'),
'@store': resolve(__dirname, 'src/store'),
'@services': resolve(__dirname, 'src/services'),
'@types': resolve(__dirname, 'src/types'),
'@styles': resolve(__dirname, 'src/styles'),
},
// 导入时想要省略的扩展名列表
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
// 解析包时的条件
conditions: ['import', 'module', 'browser', 'default'],
// 解析包入口点时的字段
mainFields: ['module', 'jsnext:main', 'jsnext'],
// 启用符号链接
preserveSymlinks: false,
}
})使用别名:
typescript
// 使用前
import Button from '../../../components/Button'
import { formatDate } from '../../../utils/date'
// 使用后
import Button from '@components/Button'
import { formatDate } from '@utils/date'TypeScript 配置:
json
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}2.3 服务器配置
typescript
export default defineConfig({
server: {
// 端口
port: 3000,
// 端口被占用时是否直接退出
strictPort: false,
// 服务器主机名
host: '0.0.0.0', // 或 true, 'localhost'
// 自动打开浏览器
open: true,
// HTTPS
https: false,
// 或使用证书
https: {
key: fs.readFileSync('path/to/key.pem'),
cert: fs.readFileSync('path/to/cert.pem')
},
// CORS
cors: true,
// 代理配置
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
},
// 预热文件
warmup: {
clientFiles: ['./src/components/*.tsx']
},
// WebSocket 配置
ws: true,
// 中间件模式
middlewareMode: false,
// 文件监听
watch: {
ignored: ['**/node_modules/**', '**/.git/**']
},
// HMR
hmr: {
overlay: true, // 错误覆盖层
port: 3001 // HMR 端口
}
}
})代理配置详解:
typescript
export default defineConfig({
server: {
proxy: {
// 字符串简写
'/foo': 'http://localhost:4567',
// 带选项
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
// 正则表达式
'^/fallback/.*': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fallback/, '')
},
// WebSocket
'/socket.io': {
target: 'ws://localhost:3001',
ws: true
},
// 自定义配置函数
'/custom': {
target: 'http://localhost:8080',
configure: (proxy, options) => {
proxy.on('error', (err) => {
console.log('proxy error', err)
})
}
}
}
}
})2.4 构建配置
typescript
export default defineConfig({
build: {
// 输出目录
outDir: 'dist',
// 静态资源目录
assetsDir: 'assets',
// 静态资源内联限制 (bytes)
assetsInlineLimit: 4096,
// CSS 代码分割
cssCodeSplit: true,
// CSS 目标浏览器
cssTarget: 'chrome61',
// 生成 source map
sourcemap: false, // true | 'inline' | 'hidden'
// Rollup 配置
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html')
},
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom']
}
}
},
// 自定义底层 Rollup 打包配置
commonjsOptions: {
include: [/node_modules/],
transformMixedEsModules: true
},
// 构建目标
target: 'modules', // 'esnext' | 'es2020' | 'es2015' 等
// 压缩方式
minify: 'esbuild', // 'terser' | 'esbuild' | false
// Terser 选项
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
}
},
// 禁用 gzip 压缩大小报告
reportCompressedSize: true,
// chunk 大小警告的限制
chunkSizeWarningLimit: 500,
// 监听模式
watch: null,
// 清空输出目录
emptyOutDir: true,
// SSR 构建
ssr: false,
// 库模式
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLib',
fileName: 'my-lib',
formats: ['es', 'cjs']
}
}
})构建优化配置:
typescript
export default defineConfig({
build: {
rollupOptions: {
output: {
// 分包策略
manualChunks(id) {
if (id.includes('node_modules')) {
// 第三方库单独打包
if (id.includes('react') || id.includes('react-dom')) {
return 'react'
}
if (id.includes('antd')) {
return 'antd'
}
if (id.includes('lodash')) {
return 'lodash'
}
return 'vendor'
}
},
// 自定义文件命名
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.')
let extType = info[info.length - 1]
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = 'images'
} else if (/woff2?|ttf|otf|eot/i.test(extType)) {
extType = 'fonts'
}
return `${extType}/[name]-[hash][extname]`
}
}
}
}
})2.5 预览配置
typescript
export default defineConfig({
preview: {
// 端口
port: 4173,
// 端口被占用时是否直接退出
strictPort: false,
// 服务器主机名
host: true,
// 自动打开浏览器
open: true,
// HTTPS
https: false,
// 代理配置
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
},
// CORS
cors: true,
// 请求头
headers: {
'Cache-Control': 'public, max-age=31536000'
}
}
})2.6 优化配置
typescript
export default defineConfig({
optimizeDeps: {
// 强制预构建的依赖项
include: [
'react',
'react-dom',
'react-router-dom',
'axios',
'lodash-es'
],
// 排除预构建的依赖项
exclude: ['your-esm-package'],
// 预构建入口
entries: ['./src/main.tsx'],
// esbuild 选项
esbuildOptions: {
define: {
global: 'globalThis'
},
plugins: [
// esbuild 插件
]
},
// 强制进行依赖预构建
force: false,
// 禁用依赖项发现
disabled: false
}
})2.7 SSR 配置
typescript
export default defineConfig({
ssr: {
// SSR 外部化的依赖
external: ['some-dep'],
// SSR 不外部化的依赖
noExternal: ['another-dep'],
// SSR 构建目标
target: 'node', // 'node' | 'webworker'
// SSR 格式
format: 'cjs' // 'cjs' | 'esm'
}
})2.8 Worker 配置
typescript
export default defineConfig({
worker: {
// Worker bundle 格式
format: 'es', // 'es' | 'iife'
// Worker 插件
plugins: [],
// Rollup 选项
rollupOptions: {
output: {
format: 'es'
}
}
}
})第三部分:环境变量与模式
3.1 环境变量文件
Vite 使用 dotenv 加载环境变量:
bash
.env # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略优先级:
.env.[mode].local > .env.[mode] > .env.local > .env3.2 定义环境变量
bash
# .env
VITE_APP_TITLE=My App
VITE_API_BASE_URL=https://api.example.com
# .env.development
VITE_API_BASE_URL=http://localhost:3001
VITE_DEBUG=true
# .env.production
VITE_API_BASE_URL=https://api.production.com
VITE_DEBUG=false注意事项:
- 只有以
VITE_开头的变量才会暴露给客户端 - 不要在环境变量中存储敏感信息
3.3 使用环境变量
typescript
// 在代码中使用
const apiUrl = import.meta.env.VITE_API_BASE_URL
const isDev = import.meta.env.DEV
const isProd = import.meta.env.PROD
const mode = import.meta.env.MODE
console.log('API URL:', apiUrl)
console.log('Is Development:', isDev)
console.log('Current Mode:', mode)内置环境变量:
typescript
import.meta.env.MODE // 应用运行的模式
import.meta.env.BASE_URL // 部署应用时的基本 URL
import.meta.env.PROD // 是否运行在生产环境
import.meta.env.DEV // 是否运行在开发环境
import.meta.env.SSR // 是否运行在 server 上3.4 TypeScript 类型定义
typescript
// src/vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
readonly VITE_API_BASE_URL: string
readonly VITE_DEBUG: string
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}3.5 动态环境变量
typescript
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd(), '')
return {
define: {
// 注入环境变量
__APP_ENV__: JSON.stringify(env.APP_ENV),
},
server: {
port: parseInt(env.VITE_PORT) || 3000
}
}
})3.6 自定义模式
bash
# 开发模式
npm run dev # mode = development
# 生产模式
npm run build # mode = production
# 自定义模式
npm run dev -- --mode staging
npm run build -- --mode stagingjson
// package.json
{
"scripts": {
"dev": "vite",
"dev:staging": "vite --mode staging",
"build": "vite build",
"build:staging": "vite build --mode staging"
}
}第四部分:Vite 插件系统
4.1 插件基础
Vite 插件扩展了 Rollup 插件接口:
typescript
// plugins/myPlugin.ts
import type { Plugin } from 'vite'
export function myPlugin(): Plugin {
return {
name: 'my-plugin', // 必需,插件名称
// 钩子
config(config, env) {
// 修改配置
},
configResolved(config) {
// 配置解析完成
},
configureServer(server) {
// 配置开发服务器
},
transformIndexHtml(html) {
// 转换 index.html
},
transform(code, id) {
// 转换代码
}
}
}4.2 插件执行顺序
typescript
export default defineConfig({
plugins: [
{
name: 'plugin-pre',
enforce: 'pre', // 最先执行
},
{
name: 'plugin-normal',
// 默认执行顺序
},
{
name: 'plugin-post',
enforce: 'post', // 最后执行
}
]
})执行顺序:
1. Alias
2. enforce: 'pre' 的用户插件
3. Vite 核心插件
4. 没有 enforce 的用户插件
5. Vite 构建插件
6. enforce: 'post' 的用户插件
7. Vite 构建后置插件4.3 条件应用插件
typescript
export default defineConfig({
plugins: [
{
name: 'dev-only-plugin',
apply: 'serve' // 只在开发环境应用
},
{
name: 'build-only-plugin',
apply: 'build' // 只在构建时应用
},
{
name: 'custom-apply-plugin',
apply(config, { command }) {
// 自定义应用条件
return command === 'build' && config.mode === 'production'
}
}
]
})第五部分:常用插件详解
5.1 React 插件
@vitejs/plugin-react-swc
bash
npm install -D @vitejs/plugin-react-swctypescript
import react from '@vitejs/plugin-react-swc'
export default defineConfig({
plugins: [
react({
// Fast Refresh 选项
fastRefresh: true,
// SWC 选项
jsxImportSource: '@emotion/react',
// 开发装饰器
devTarget: 'es2022'
})
]
})@vitejs/plugin-react
bash
npm install -D @vitejs/plugin-reacttypescript
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
// Babel 配置
babel: {
plugins: [
['@babel/plugin-proposal-decorators', { legacy: true }]
]
},
// Fast Refresh 选项
fastRefresh: true,
// JSX Runtime
jsxRuntime: 'automatic', // 'automatic' | 'classic'
// JSX Import Source
jsxImportSource: '@emotion/react'
})
]
})5.2 自动导入插件
bash
npm install -D unplugin-auto-importtypescript
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
// 预设
imports: [
'react',
'react-router-dom',
{
'ahooks': ['useRequest', 'useLocalStorageState']
}
],
// 自动导入的目录
dirs: [
'./src/hooks',
'./src/utils'
],
// 类型声明文件
dts: './src/auto-imports.d.ts',
// ESLint 配置
eslintrc: {
enabled: true,
filepath: './.eslintrc-auto-import.json'
}
})
]
})使用效果:
typescript
// 之前
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
// 之后 (自动导入)
function App() {
const [count, setCount] = useState(0)
const navigate = useNavigate()
}5.3 组件自动导入
bash
npm install -D unplugin-vue-componentstypescript
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
Components({
// 组件目录
dirs: ['src/components'],
// 组件扩展名
extensions: ['tsx', 'ts'],
// 自动导入的解析器
resolvers: [
AntDesignVueResolver()
],
// 类型声明文件
dts: 'src/components.d.ts',
// 深度搜索
deep: true,
// 包含的文件
include: [/\.tsx$/, /\.ts$/]
})
]
})5.4 SVG 处理插件
bash
npm install -D vite-plugin-svgrtypescript
import svgr from 'vite-plugin-svgr'
export default defineConfig({
plugins: [
svgr({
// SVGR 选项
svgrOptions: {
icon: true,
dimensions: false,
typescript: true
},
// 包含的文件
include: '**/*.svg',
// 排除的文件
exclude: ''
})
]
})使用方式:
typescript
// 作为 URL
import logoUrl from './logo.svg'
// 作为 React 组件
import { ReactComponent as Logo } from './logo.svg?react'
function App() {
return (
<div>
<img src={logoUrl} alt="Logo" />
<Logo />
</div>
)
}5.5 PWA 插件
bash
npm install -D vite-plugin-pwatypescript
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
VitePWA({
// 注册类型
registerType: 'autoUpdate',
// Workbox 选项
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 7 // 1 week
}
}
}
]
},
// Manifest
manifest: {
name: 'My App',
short_name: 'App',
description: 'My awesome app',
theme_color: '#ffffff',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
},
// 开发选项
devOptions: {
enabled: true
}
})
]
})5.6 图片优化插件
bash
npm install -D vite-plugin-imagemintypescript
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig({
plugins: [
viteImagemin({
// 图片压缩
gifsicle: {
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 80
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
})
]
})5.7 压缩插件
bash
npm install -D vite-plugin-compressiontypescript
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
// Gzip 压缩
viteCompression({
algorithm: 'gzip',
ext: '.gz',
threshold: 10240, // 10KB 以上才压缩
deleteOriginFile: false
}),
// Brotli 压缩
viteCompression({
algorithm: 'brotliCompress',
ext: '.br',
threshold: 10240
})
]
})5.8 可视化分析插件
bash
npm install -D rollup-plugin-visualizertypescript
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
// 输出文件
filename: 'dist/stats.html',
// 自动打开
open: true,
// Gzip 大小
gzipSize: true,
// Brotli 大小
brotliSize: true,
// 模板类型
template: 'treemap' // 'sunburst' | 'treemap' | 'network'
})
]
})5.9 环境变量插件
bash
npm install -D vite-plugin-env-compatibletypescript
import envCompatible from 'vite-plugin-env-compatible'
export default defineConfig({
plugins: [
envCompatible({
// 兼容 CRA 的环境变量前缀
prefix: 'REACT_APP_'
})
]
})5.10 HTML 插件
bash
npm install -D vite-plugin-htmltypescript
import { createHtmlPlugin } from 'vite-plugin-html'
export default defineConfig({
plugins: [
createHtmlPlugin({
// 压缩
minify: true,
// 注入数据
inject: {
data: {
title: 'My App',
injectScript: '<script src="./inject.js"></script>'
}
},
// 标签
tags: [
{
injectTo: 'head',
tag: 'meta',
attrs: {
name: 'description',
content: 'My awesome app'
}
}
]
})
]
})html
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<%= injectScript %>
</head>
<body>
<div id="root"></div>
</body>
</html>第六部分:自定义插件开发
6.1 插件基本结构
typescript
import type { Plugin, ResolvedConfig } from 'vite'
export function myCustomPlugin(options = {}): Plugin {
let config: ResolvedConfig
return {
name: 'my-custom-plugin',
// 配置钩子
config(config, env) {
// 返回部分配置(会与现有配置合并)
return {
define: {
__PLUGIN_VERSION__: JSON.stringify('1.0.0')
}
}
},
// 配置解析完成
configResolved(resolvedConfig) {
config = resolvedConfig
},
// 配置开发服务器
configureServer(server) {
server.middlewares.use((req, res, next) => {
// 自定义中间件
next()
})
},
// 转换 HTML
transformIndexHtml(html) {
return html.replace(
'</head>',
'<script>console.log("Plugin loaded")</script></head>'
)
},
// 解析模块 ID
resolveId(source) {
if (source === 'virtual-module') {
return source
}
},
// 加载模块
load(id) {
if (id === 'virtual-module') {
return 'export default "Virtual module content"'
}
},
// 转换代码
transform(code, id) {
if (id.endsWith('.custom')) {
// 转换代码
return {
code: transformedCode,
map: null
}
}
},
// 构建开始
buildStart() {
console.log('Build started')
},
// 构建结束
buildEnd() {
console.log('Build ended')
},
// 关闭
closeBundle() {
console.log('Bundle closed')
}
}
}6.2 实战:自动导入样式插件
typescript
// plugins/autoImportStyles.ts
import type { Plugin } from 'vite'
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import fs from 'fs'
import path from 'path'
interface Options {
extensions?: string[]
styleDir?: string
}
export function autoImportStyles(options: Options = {}): Plugin {
const {
extensions = ['.tsx', '.ts'],
styleDir = 'src/styles'
} = options
return {
name: 'auto-import-styles',
transform(code, id) {
// 只处理指定扩展名的文件
if (!extensions.some(ext => id.endsWith(ext))) {
return null
}
try {
// 解析代码为 AST
const ast = parse(code, {
sourceType: 'module',
plugins: ['typescript', 'jsx']
})
let componentName = ''
// 遍历 AST 寻找组件定义
traverse(ast, {
// 函数声明
FunctionDeclaration(path) {
componentName = path.node.id?.name || ''
},
// 箭头函数 const Component = () => {}
VariableDeclarator(path) {
if (path.node.id.type === 'Identifier') {
componentName = path.node.id.name
}
}
})
if (!componentName) {
return null
}
// 检查样式文件是否存在
const styleFile = path.join(
process.cwd(),
styleDir,
`${componentName}.css`
)
if (fs.existsSync(styleFile)) {
// 自动添加样式导入
const importStatement = `import '${styleDir}/${componentName}.css';\n`
return {
code: importStatement + code,
map: null
}
}
} catch (error) {
console.error('Parse error:', error)
}
return null
}
}
}使用插件:
typescript
// vite.config.ts
import { autoImportStyles } from './plugins/autoImportStyles'
export default defineConfig({
plugins: [
autoImportStyles({
styleDir: 'src/styles',
extensions: ['.tsx']
})
]
})6.3 实战:环境变量注入插件
typescript
// plugins/injectEnv.ts
import type { Plugin } from 'vite'
interface EnvConfig {
[key: string]: string | number | boolean
}
export function injectEnv(envConfig: EnvConfig): Plugin {
return {
name: 'inject-env',
config() {
// 将环境变量转换为可注入的格式
const define: Record<string, string> = {}
for (const [key, value] of Object.entries(envConfig)) {
define[`__ENV_${key}__`] = JSON.stringify(value)
}
return {
define
}
},
transformIndexHtml(html) {
// 在 HTML 中注入环境变量脚本
const envScript = `
<script>
window.__APP_ENV__ = ${JSON.stringify(envConfig)};
</script>
`
return html.replace('</head>', `${envScript}</head>`)
}
}
}使用:
typescript
// vite.config.ts
import { injectEnv } from './plugins/injectEnv'
export default defineConfig({
plugins: [
injectEnv({
API_URL: 'https://api.example.com',
APP_VERSION: '1.0.0',
FEATURE_FLAG: true
})
]
})typescript
// 在代码中使用
console.log(__ENV_API_URL__)
console.log(window.__APP_ENV__)6.4 实战:虚拟模块插件
typescript
// plugins/virtualModule.ts
import type { Plugin } from 'vite'
export function virtualModule(): Plugin {
const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'virtual-module',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `
export const version = '1.0.0'
export const buildTime = '${new Date().toISOString()}'
export const config = ${JSON.stringify({
env: process.env.NODE_ENV,
api: process.env.VITE_API_URL
})}
`
}
}
}
}使用虚拟模块:
typescript
// 声明虚拟模块类型
// vite-env.d.ts
declare module 'virtual:my-module' {
export const version: string
export const buildTime: string
export const config: {
env: string
api: string
}
}typescript
// 使用
import { version, buildTime, config } from 'virtual:my-module'
console.log('Version:', version)
console.log('Build Time:', buildTime)
console.log('Config:', config)6.5 实战:代码转换插件
typescript
// plugins/transformCode.ts
import type { Plugin } from 'vite'
import MagicString from 'magic-string'
export function transformCode(): Plugin {
return {
name: 'transform-code',
transform(code, id) {
if (!id.endsWith('.tsx')) {
return null
}
const s = new MagicString(code)
// 替换特定模式
const pattern = /console\.log\((.*?)\)/g
let match
while ((match = pattern.exec(code)) !== null) {
const start = match.index
const end = start + match[0].length
// 添加文件信息
s.overwrite(
start,
end,
`console.log('[${id}]', ${match[1]})`
)
}
if (!s.hasChanged()) {
return null
}
return {
code: s.toString(),
map: s.generateMap({ hires: true })
}
}
}
}第七部分:插件最佳实践
7.1 插件开发原则
1. 单一职责:
typescript
// 好 - 每个插件只做一件事
export function optimizeImages(): Plugin { }
export function compressAssets(): Plugin { }
// 不好 - 插件功能过多
export function doEverything(): Plugin { }2. 配置灵活:
typescript
interface PluginOptions {
enabled?: boolean
include?: string | string[]
exclude?: string | string[]
[key: string]: any
}
export function myPlugin(options: PluginOptions = {}): Plugin {
const {
enabled = true,
include = ['**/*.tsx'],
exclude = ['node_modules/**']
} = options
return {
name: 'my-plugin',
apply: enabled ? undefined : () => false,
// ...
}
}3. 错误处理:
typescript
export function safePlugin(): Plugin {
return {
name: 'safe-plugin',
transform(code, id) {
try {
// 转换逻辑
return transformCode(code)
} catch (error) {
// 记录错误但不中断构建
this.warn(`Transform failed for ${id}: ${error.message}`)
return null
}
}
}
}7.2 性能优化
1. 文件过滤:
typescript
export function optimizedPlugin(): Plugin {
return {
name: 'optimized-plugin',
transform(code, id) {
// 早期返回,避免不必要的处理
if (!id.includes('/src/')) {
return null
}
if (id.includes('node_modules')) {
return null
}
// 处理逻辑
}
}
}2. 缓存结果:
typescript
export function cachedPlugin(): Plugin {
const cache = new Map()
return {
name: 'cached-plugin',
transform(code, id) {
// 检查缓存
if (cache.has(id)) {
const cached = cache.get(id)
if (cached.code === code) {
return cached.result
}
}
// 处理并缓存
const result = processCode(code)
cache.set(id, { code, result })
return result
},
buildEnd() {
// 清理缓存
cache.clear()
}
}
}7.3 调试技巧
typescript
export function debugPlugin(debug = false): Plugin {
return {
name: 'debug-plugin',
config(config, env) {
if (debug) {
console.log('Config:', config)
console.log('Env:', env)
}
},
transform(code, id) {
if (debug && id.includes('target-file')) {
console.log('Transform:', id)
console.log('Code:', code)
}
const result = transform(code)
if (debug) {
console.log('Result:', result)
}
return result
}
}
}总结
本章深入探讨了 Vite 的配置系统和插件生态:
- 配置系统 - 全面了解 Vite 的各种配置选项
- 环境变量 - 掌握环境变量的使用和管理
- 插件系统 - 理解 Vite 插件的工作原理
- 常用插件 - 学习各种实用插件的使用
- 自定义开发 - 掌握自定义插件的开发方法
- 最佳实践 - 了解插件开发的最佳实践
掌握 Vite 配置和插件能够帮助您构建更强大、更高效的前端应用。