插件系统
插件概述
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-rsc | React Server Components | npm i -D @vitejs/plugin-rsc |
@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 请求
})
},
// 配置预览服务器
configurePreviewServer(server) {
// 与 configureServer 类似,但用于预览模式
// 在内部中间件安装后添加中间件
return () => {
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' }
命名约定:
- Vite 专用插件:
vite-plugin-* - Rollup 兼容插件:
rollup-plugin-* - 框架专用插件:
{framework}-vite-plugin-*或@{scope}/vite-plugin-{name}
2. 错误处理
在插件中妥善处理错误,提供有意义的错误信息:
export default function myPlugin() {
return {
name: 'my-plugin',
transform(code, id) {
try {
// 转换逻辑
return { code: transformed }
} catch (error) {
// 使用 this.error 提供上下文信息
this.error(`转换失败: ${id}\n原因: ${error.message}`, error)
}
},
}
}
错误处理 API:
// 错误信息(中断构建)
this.error(message, error)
// 警告信息(继续构建)
this.warn(message, position)
// 信息日志
this.info(message)
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) }
},
}
}
性能优化技巧:
export default function myPlugin(options = {}) {
// 预编译正则表达式
const includePattern = options.include || /\.vue$/
const excludePattern = options.exclude || /node_modules/
// 缓存计算结果
const cache = new Map()
return {
name: 'my-plugin',
transform(code, id) {
// 使用缓存的过滤结果
if (cache.has(id)) {
return cache.get(id)
}
// 快速过滤
if (excludePattern.test(id)) {
cache.set(id, null)
return null
}
if (!includePattern.test(id)) {
cache.set(id, null)
return null
}
// 执行转换并缓存
const result = doTransform(code)
cache.set(id, result)
return result
},
}
}
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
}
},
}
}
5. 开发和生产环境区分
根据环境应用不同的行为:
export default function myPlugin() {
return {
name: 'my-plugin',
apply: 'build', // 只在构建时应用
// 或
apply: 'serve', // 只在开发时应用
// 或使用函数动态判断
apply(config, { command }) {
// command: 'serve' | 'build'
return command === 'build' && config.build.minify
}
}
}
6. TypeScript 类型支持
为插件提供类型定义:
// types/index.d.ts
import type { Plugin } from 'vite'
export interface MyPluginOptions {
include?: string | string[]
exclude?: string | string[]
strict?: boolean
}
export default function myPlugin(options?: MyPluginOptions): Plugin
7. 配置验证
验证用户提供的配置选项:
export default function myPlugin(options = {}) {
const defaultOptions = {
include: '**/*.js',
exclude: 'node_modules/**',
strict: false,
}
const config = { ...defaultOptions, ...options }
// 验证必需选项
if (!config.someRequired) {
throw new Error('[vite-plugin-my] someRequired option is required')
}
// 验证类型
if (typeof config.strict !== 'boolean') {
throw new Error('[vite-plugin-my] strict must be a boolean')
}
return {
name: 'vite-plugin-my',
// ...
}
}
客户端-服务器通信
Vite 提供了 WebSocket 通信机制,允许插件在服务端和客户端之间发送消息。
服务端向客户端发送消息
在插件中使用 server.ws.send 广播事件:
export default defineConfig({
plugins: [
{
name: 'greetings-plugin',
configureServer(server) {
server.ws.on('connection', () => {
// 向客户端发送消息
server.ws.send('my:greetings', { msg: 'hello' })
})
},
},
],
})
客户端使用 hot.on 监听事件:
// 客户端代码
if (import.meta.hot) {
import.meta.hot.on('my:greetings', (data) => {
console.log(data.msg) // 'hello'
})
}
客户端向服务端发送消息
客户端使用 hot.send 发送事件:
// 客户端代码
if (import.meta.hot) {
import.meta.hot.send('my:from-client', { msg: 'Hey!' })
}
服务端使用 server.ws.on 监听:
export default defineConfig({
plugins: [
{
name: 'client-message-plugin',
configureServer(server) {
server.ws.on('my:from-client', (data, client) => {
console.log('来自客户端的消息:', data.msg) // 'Hey!'
// 可选:仅回复该客户端
client.send('my:ack', { msg: '收到你的消息!' })
})
},
},
],
})
重要提示:建议始终为事件名称添加前缀,避免与其他插件冲突。
TypeScript 类型支持
为自定义事件添加类型定义:
// vite-env.d.ts
import 'vite/types/customEvent.d.ts'
declare module 'vite/types/customEvent.d.ts' {
interface CustomEventMap {
'my:greetings': { msg: string }
'my:from-client': { msg: string }
}
}
工具函数
Vite 提供了一些实用工具函数,帮助插件开发。
路径规范化
Vite 使用 POSIX 分隔符(/)规范化路径。比较路径时需要先规范化:
import { normalizePath } from 'vite'
normalizePath('foo\\bar') // 'foo/bar'
normalizePath('foo/bar') // 'foo/bar'
这在 Windows 系统上尤其重要,确保路径比较的一致性。
文件过滤
使用 createFilter 创建 include/exclude 过滤器:
import { createFilter } from 'vite'
export default function myPlugin(options = {}) {
// 创建过滤器
const filter = createFilter(
options.include || ['**/*.js', '**/*.ts'],
options.exclude || ['node_modules/**']
)
return {
name: 'my-plugin',
transform(code, id) {
// 使用过滤器
if (!filter(id)) return null
// 执行转换
return { code: transform(code) }
},
}
}
调试插件
使用 vite-plugin-inspect 检查插件的中间状态:
npm install -D vite-plugin-inspect
import Inspect from 'vite-plugin-inspect'
export default defineConfig({
plugins: [Inspect()],
})
访问 http://localhost:5173/__inspect/ 可以查看:
- 每个文件经过各插件转换后的内容
- 转换前后的代码差异
- 每个插件的转换耗时
写在最后
插件扩展了 Vite 的能力边界。本章介绍了官方插件和社区插件的使用,以及如何开发自定义插件。
当你需要集成新框架、添加构建优化或实现特殊功能时,插件是最灵活的方式。开发插件时注意遵循命名规范,妥善处理错误,并考虑性能影响。遇到问题时,可以使用 vite-plugin-inspect 查看插件的转换过程。