7.1 Vite 插件 = Rollup 插件 + Vite 扩展
Vite 的插件接口 超集了 Rollup 的插件接口——意味着绝大多数 Rollup 插件能直接用,同时 Vite 又加了几个自己的专属钩子(主要为 dev 服务器设计)。
一个插件就是一个对象(或返回对象的函数),含 name、若干钩子、可选的 enforce/apply:
const myPlugin = {
name: 'my-plugin',
enforce: 'pre', // 'pre' | 'post' 控制顺序
apply: 'build', // 只在 build 阶段生效,或 'serve'
transform(code, id) { /* ... */ },
};
7.2 通用钩子(Rollup 兼容)
- resolveId(source, importer)
- 自定义模块路径解析。返回字符串表示"把这个路径当成解析结果"。
- load(id)
- 根据路径返回文件内容(字符串或
{ code, map })。常用于虚拟模块。 - transform(code, id)
- 拿到文件内容后的转换钩子——最常用。返回
{ code, map }或新字符串。 - buildStart / buildEnd
- 构建开始/结束时的回调。
- generateBundle / writeBundle
- Rollup 生成和写出 bundle 时的钩子(仅 build 阶段有意义)。
7.3 Vite 专属钩子
- config(config, { command, mode })
- 读到用户配置后、合并前修改配置。
- configResolved(resolvedConfig)
- 配置完全解析后——保存配置到插件局部变量,后续 transform 用。
- configureServer(server)
- 拿到 dev 服务器实例,能加中间件、监听 WebSocket、访问文件监听器。
- configurePreviewServer(server)
- 类似,但针对
vite preview。 - transformIndexHtml(html, ctx)
- 专门转换 index.html——返回新 HTML 字符串或标签对象数组。
- handleHotUpdate(ctx)
- 自定义 HMR:决定哪些模块需要热替换。
7.4 虚拟模块:加载不存在的文件
最经典的插件用法——让 import 'virtual:app-version' 能 import "根本不存在"的模块:
import type { Plugin } from 'vite';
export function appVersionPlugin(): Plugin {
const virtualId = 'virtual:app-version';
const resolvedId = '\0virtual:app-version';
return {
name: 'app-version',
resolveId(id) {
if (id === virtualId) return resolvedId;
},
load(id) {
if (id === resolvedId) {
return `export const version = "1.2.3"; export const buildTime = ${Date.now()};`;
}
},
};
}
// 业务代码
import { version, buildTime } from 'virtual:app-version';
为什么 resolvedId 前缀
\0?
Rollup 约定:虚拟模块的解析结果应该以 \0(null 字节)开头,防止其他插件/Node 误认为是真实文件路径。这是生态一致性约定。
7.5 transform 示例:把 Markdown 变成组件
import { marked } from 'marked';
export function mdPlugin(): Plugin {
return {
name: 'md-to-react',
transform(code, id) {
if (!id.endsWith('.md')) return;
const html = marked(code);
return {
code: `import React from 'react';
export default function() { return React.createElement('div', {
dangerouslySetInnerHTML: { __html: ${JSON.stringify(html)} }
}); }`,
map: null,
};
},
};
}
7.6 configureServer:自定义中间件
export function apiMockPlugin(): Plugin {
return {
name: 'api-mock',
configureServer(server) {
server.middlewares.use('/api/hello', (req, res) => {
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ hello: 'world' }));
});
},
};
}
7.7 transformIndexHtml:注入标签
export function injectEnvPlugin(): Plugin {
return {
name: 'inject-env',
transformIndexHtml: {
order: 'pre',
handler(html) {
return {
html,
tags: [{
tag: 'script',
children: `window.__ENV__ = ${JSON.stringify({ BUILD: Date.now() })};`,
injectTo: 'head-prepend',
}],
};
},
},
};
}
7.8 handleHotUpdate:自定义 HMR 决策
export function customHmrPlugin(): Plugin {
return {
name: 'custom-hmr',
handleHotUpdate({ file, server, modules }) {
if (file.endsWith('.locale.json')) {
// 语言文件变了:发自定义事件让客户端 reload i18n
server.ws.send({ type: 'custom', event: 'locale-update' });
return []; // 返回空数组 = 我已处理,Vite 不要再动
}
},
};
}
7.9 常用插件精选
| 插件 | 用途 |
|---|---|
| @vitejs/plugin-react / react-swc | React JSX + Fast Refresh |
| @vitejs/plugin-vue | Vue 3 SFC 支持 |
| @vitejs/plugin-legacy | 为旧浏览器生成 nomodule 产物 |
| vite-plugin-svgr | SVG 作为 React 组件 import |
| vite-tsconfig-paths | 自动把 tsconfig.paths 同步到 Vite alias |
| vite-plugin-pages | 文件路由(类 Next.js pages/) |
| unplugin-auto-import | 自动 import 常用 API |
| unplugin-icons | 按需导入 Iconify 图标 |
| vite-plugin-pwa | PWA / Service Worker 一键接入 |
| @tailwindcss/vite | Tailwind v4 官方插件 |
| vite-plugin-dts | 库模式下生成 .d.ts |
| vite-plugin-inspect | 打开 /__inspect/ 查看每个模块的 transform 链路,调试神器 |
unplugin 是什么?
unplugin 是一个构建工具无关的插件框架——同一份插件代码能在 Vite、Rollup、webpack、Rspack、esbuild 里跑。unplugin-auto-import、unplugin-icons 等就属于它。写公共插件时优先考虑 unplugin。
7.10 小结
- 插件 = Rollup 钩子 + Vite 专属钩子(config/configureServer/transformIndexHtml/handleHotUpdate)。
- 虚拟模块用
resolveId+load;转换文件用transform;自定义 dev 服务用configureServer。 - 大多数需求已有现成插件,先搜再写。
vite-plugin-inspect是调试插件链的必备工具。