插件系统
插件概述
Vite 插件扩展了构建流程的功能。Vite 的插件系统基于 Rollup 插件接口,并添加了 Vite 特定的配置钩子。
插件类型
- 官方插件:由 Vite 团队维护
- 社区插件:由社区开发者贡献
- 自定义插件:根据项目需求自行开发
官方插件列表
| 插件 | 用途 | 安装命令 |
|---|---|---|
@vitejs/plugin-vue | Vue 3 单文件组件支持 | npm i -D @vitejs/plugin-vue |
@vitejs/plugin-vue-jsx | Vue 3 JSX 支持 | npm i -D @vitejs/plugin-vue-jsx |
@vitejs/plugin-react | React Fast Refresh | npm i -D @vitejs/plugin-react |
@vitejs/plugin-react-swc | React SWC 编译 | npm i -D @vitejs/plugin-react-swc |
@vitejs/plugin-legacy | 旧浏览器支持 | npm i -D @vitejs/plugin-legacy |
@vitejs/plugin-basic-ssl | 开发服务器 HTTPS | npm i -D @vitejs/plugin-basic-ssl |
使用插件
基本用法
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
],
})
插件配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
// 使用默认配置
vue(),
// 使用自定义配置
legacy({
targets: ['defaults', 'not IE 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
}),
],
})
条件使用插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig(({ mode }) => ({
plugins: [
vue(),
// 只在生产构建时启用分析插件
mode === 'production' && visualizer({
open: true,
gzipSize: true,
}),
].filter(Boolean), // 过滤掉 false 值
}))
常用社区插件
路径别名解析
npm install -D vite-tsconfig-paths
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths()],
})
自动导入
npm install -D unplugin-auto-import
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router'],
dts: true, // 生成类型声明文件
}),
],
})
组件自动导入
npm install -D unplugin-vue-components
import { defineConfig } from 'vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
SVG 处理
npm install -D vite-plugin-svgr
import { defineConfig } from 'vite'
import svgr from 'vite-plugin-svgr'
export default defineConfig({
plugins: [
svgr({
// 将 SVG 作为 React 组件导入
exportAsDefault: true,
}),
],
})
使用:
import { ReactComponent as Logo } from './logo.svg'
图片压缩
npm install -D vite-plugin-imagemin
import { defineConfig } from 'vite'
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] },
svgo: {
plugins: [
{ name: 'removeViewBox' },
{ name: 'removeEmptyAttrs', active: false },
],
},
}),
],
})
PWA 支持
npm install -D vite-plugin-pwa
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
VitePWA({
registerType: 'autoUpdate',
manifest: {
name: 'My App',
short_name: 'MyApp',
theme_color: '#ffffff',
icons: [
{
src: '/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
],
},
}),
],
})
检查器(TypeScript/ESLint)
npm install -D vite-plugin-checker
import { defineConfig } from 'vite'
import checker from 'vite-plugin-checker'
export default defineConfig({
plugins: [
checker({
typescript: true,
eslint: {
lintCommand: 'eslint "./src/**/*.{ts,tsx}"',
},
}),
],
})
创建自定义插件
插件基本结构
// my-plugin.js
export default function myPlugin(options = {}) {
return {
// 插件名称(用于警告和错误信息)
name: 'my-plugin',
// 应用顺序('pre' | 'post' | undefined)
enforce: 'pre',
// 仅在构建时应用
apply: 'build',
// 配置钩子...
}
}
常用钩子
解析钩子
export default function myPlugin() {
return {
name: 'my-plugin',
// 解析 ID 时调用
resolveId(source, importer) {
if (source === 'virtual-module') {
return source // 返回虚拟模块 ID
}
},
// 加载模块时调用
load(id) {
if (id === 'virtual-module') {
return 'export default "Hello from virtual module!"'
}
},
}
}
转换钩子
export default function myPlugin() {
return {
name: 'my-plugin',
// 转换代码时调用
transform(code, id) {
// 只处理 .js 文件
if (!id.endsWith('.js')) return null
// 替换代码中的特定内容
const transformed = code.replace(
/__VERSION__/g,
JSON.stringify(process.env.npm_package_version)
)
return {
code: transformed,
map: null, // 可选:source map
}
},
}
}
构建钩子
export default function myPlugin() {
return {
name: 'my-plugin',
// 构建开始时调用
buildStart() {
console.log('构建开始!')
},
// 构建结束时调用
buildEnd() {
console.log('构建结束!')
},
// 输出文件生成时调用
generateBundle(options, bundle) {
// 遍历所有生成的文件
for (const [fileName, chunk] of Object.entries(bundle)) {
console.log(`生成文件: ${fileName}`)
}
},
// 写入文件后调用
writeBundle(options, bundle) {
console.log('文件写入完成!')
},
}
}
Vite 特定钩子
export default function myPlugin() {
return {
name: 'my-plugin',
// 配置 Vite 服务器
configureServer(server) {
// 添加自定义中间件
server.middlewares.use('/api', (req, res, next) => {
// 处理 API 请求
})
},
// 转换 HTML
transformIndexHtml(html) {
return html.replace(
/<title>(.*?)<\/title>/,
`<title>My App - $1</title>`
)
},
// 处理热更新
handleHotUpdate({ file, server, modules }) {
console.log(`文件更新: ${file}`)
// 可以返回自定义的模块列表
return modules
},
}
}
完整插件示例
虚拟模块插件
// virtual-module-plugin.js
export default function virtualModulePlugin() {
const virtualModuleId = 'virtual:config'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'virtual-module-plugin',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `
export const appName = 'My App'
export const version = '1.0.0'
export const apiUrl = '${process.env.VITE_API_URL}'
`
}
},
}
}
使用:
import { appName, version } from 'virtual:config'
console.log(appName, version)
环境变量注入插件
// env-replace-plugin.js
export default function envReplacePlugin(options = {}) {
const { prefix = 'VITE_' } = options
return {
name: 'env-replace-plugin',
transform(code, id) {
// 只处理 JS/TS 文件
if (!/\.(js|ts|jsx|tsx)$/.test(id)) return null
// 查找所有 __ENV_*__ 模式
const envRegex = /__ENV_(\w+)__/g
let match
let transformed = code
while ((match = envRegex.exec(code)) !== null) {
const varName = match[1]
const envValue = process.env[`${prefix}${varName}`]
if (envValue !== undefined) {
transformed = transformed.replace(
match[0],
JSON.stringify(envValue)
)
}
}
return { code: transformed }
},
}
}
文件复制插件
// copy-files-plugin.js
import fs from 'fs'
import path from 'path'
export default function copyFilesPlugin(options = {}) {
const { from, to } = options
return {
name: 'copy-files-plugin',
writeBundle() {
const srcDir = path.resolve(from)
const destDir = path.resolve(to)
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true })
}
const files = fs.readdirSync(srcDir)
for (const file of files) {
const srcFile = path.join(srcDir, file)
const destFile = path.join(destDir, file)
fs.copyFileSync(srcFile, destFile)
console.log(`Copied: ${file}`)
}
},
}
}
使用:
// vite.config.js
import copyFilesPlugin from './copy-files-plugin'
export default defineConfig({
plugins: [
copyFilesPlugin({
from: './public/locales',
to: './dist/locales',
}),
],
})
插件排序
enforce 选项
控制插件应用顺序:
export default defineConfig({
plugins: [
{
name: 'pre-plugin',
enforce: 'pre', // 在其他插件之前运行
transform(code) {
// ...
},
},
{
name: 'normal-plugin',
// 默认顺序
transform(code) {
// ...
},
},
{
name: 'post-plugin',
enforce: 'post', // 在其他插件之后运行
transform(code) {
// ...
},
},
],
})
apply 选项
控制插件在何时应用:
export default defineConfig({
plugins: [
{
name: 'build-only',
apply: 'build', // 仅在构建时应用
},
{
name: 'serve-only',
apply: 'serve', // 仅在开发服务器时应用
},
{
name: 'conditional',
apply(config, { command }) {
// 自定义条件
return command === 'build' && config.build?.minify
},
},
],
})
插件最佳实践
1. 命名规范
插件名称应该清晰描述其功能:
// ✅ 好的命名
{ name: 'vite-plugin-svg-icons' }
{ name: 'vite-plugin-compression' }
// ❌ 不好的命名
{ name: 'my-plugin' }
{ name: 'plugin' }
2. 错误处理
在插件中妥善处理错误:
export default function myPlugin() {
return {
name: 'my-plugin',
transform(code, id) {
try {
// 转换逻辑
return { code: transformed }
} catch (error) {
this.error(`转换失败: ${id}`, error)
}
},
}
}
3. 性能优化
避免不必要的处理:
export default function myPlugin() {
return {
name: 'my-plugin',
transform(code, id) {
// 快速过滤不相关的文件
if (!id.endsWith('.special')) return null
// 检查是否需要转换
if (!code.includes('SPECIAL_TAG')) return null
// 执行转换
return { code: transform(code) }
},
}
}
4. Source Map 支持
如果转换代码,提供 source map:
import { createSourceMap } from 'some-sourcemap-lib'
export default function myPlugin() {
return {
name: 'my-plugin',
transform(code, id) {
const result = transformWithSourceMap(code, id)
return {
code: result.code,
map: result.map, // 提供 source map
}
},
}
}
小结
本章我们学习了 Vite 的插件系统:
- 官方插件:Vue、React、Legacy 等官方维护的插件
- 社区插件:路径别名、自动导入、PWA 等常用插件
- 自定义插件:
- 插件基本结构和钩子
- 解析、加载、转换钩子
- Vite 特定的服务器和 HTML 钩子
- 插件排序:使用
enforce和apply控制插件行为 - 最佳实践:命名、错误处理、性能优化
插件是扩展 Vite 功能的核心机制,掌握插件开发可以让你根据项目需求定制构建流程。