插件开发
Vite 插件兼容 Rollup 插件,并扩展 config、configureServer、transformIndexHtml 等钩子。原理见 工作原理。
一、Vite 特有钩子
Vite 在 Rollup 插件生命周期之上新增了以下钩子(仅开发环境或同时用于构建):
1. config
作用:修改 Vite 配置(在解析配置前调用)。可以返回一个对象(深度合并)或直接修改 config 参数。
// 修改 alias 或 base
function myPlugin() {
return {
name: "my-plugin",
config(config, env) {
// env: { mode, command }
config.resolve.alias = { "@": "/src" };
return config; // 或返回部分配置
},
};
}
2. configResolved
作用:在 Vite 配置最终确定后调用,可以获取完整的配置信息。
configResolved(resolvedConfig) {
console.log('最终配置:', resolvedConfig);
// 可以保存配置供其他钩子使用
this.config = resolvedConfig;
}
3. configureServer
作用:配置开发服务器的自定义中间件。通常用于添加 Mock API、代理、静态文件等。
configureServer(server) {
// server 是 ViteDevServer 实例
server.middlewares.use('/api/mock', (req, res) => {
res.end(JSON.stringify({ data: 'mock' }));
});
}
高级用法:返回一个函数,可以在内部中间件之后执行:
configureServer(server) {
return () => {
server.middlewares.use((req, res, next) => {
// 在所有 Vite 内置中间件之后执行
next();
});
};
}
4. transformIndexHtml
作用:转换入口 HTML 文件(index.html)。可添加标签、修改内容等。
transformIndexHtml(html, ctx) {
// ctx: { path, filename, originalUrl }
return html.replace('<!-- inject -->', '<script>console.log("injected")</script>');
}
也可以返回对象数组,用于插入特定标签:
transformIndexHtml() {
return [
{ tag: 'meta', attrs: { name: 'theme-color', content: '#fff' } },
];
}
5. handleHotUpdate
作用:自定义 HMR 更新逻辑,可以过滤或扩展热更新的模块。
handleHotUpdate({ file, modules, server }) {
// 如果文件是某个特定组件,可以只重新加载该组件
if (file.endsWith('Component.vue')) {
return modules.filter(m => m.id === file);
}
// 默认返回所有受影响的模块
return modules;
}
二、开发环境与生产环境钩子差异
Vite 插件钩子在不同环境下有选择性执行:
| 钩子 | 开发环境 | 生产环境 | 说明 |
|---|---|---|---|
config | ✅ | ✅ | 都执行 |
configResolved | ✅ | ✅ | 都执行 |
configureServer | ✅ | ❌ | 仅开发服务器 |
transformIndexHtml | ✅ | ✅ | 构建时也会转换 HTML |
handleHotUpdate | ✅ | ❌ | 仅开发 HMR |
resolveId | ✅ | ✅ | Rollup 钩子,都执行 |
load | ✅ | ✅ | 都执行 |
transform | ✅ | ✅ | 都执行 |
buildStart | ❌ | ✅ | 构建开始时 |
renderStart | ❌ | ✅ | 构建渲染开始时 |
generateBundle | ❌ | ✅ | 生成 bundle 时 |
writeBundle | ❌ | ✅ | 写入文件后 |
开发环境实际上是无打包的,因此不会触发 Rollup 的 generateBundle 等钩子。生产环境使用 Rollup 打包,这些钩子都会执行。
三、中间件模式
Vite 开发服务器基于 connect 中间件栈。通过 configureServer 可以注入自定义中间件,常用于:
- Mock API:拦截特定请求返回 JSON。
- 代理重写:修改代理规则或响应。
- 静态文件托管:提供额外的静态资源目录。
示例:Mock API 中间件
export default function mockPlugin() {
return {
name: "mock-api",
configureServer(server) {
server.middlewares.use("/api/users", (req, res) => {
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify([{ id: 1, name: "Alice" }]));
});
},
};
}
注意中间件顺序:默认情况下,自定义中间件会添加到内部中间件之前。如果需要在 Vite 内部中间件(如静态文件、HMR)之后执行,可以返回一个函数:
configureServer(server) {
return () => {
server.middlewares.use('/after', (req, res) => {
// 在 Vite 内部中间件之后执行
});
};
}
四、虚拟模块
虚拟模块是指不实际存在于磁盘上的模块,通过插件动态生成内容。使用 resolveId 和 load 钩子实现。
基本模式:
export default function virtualModulePlugin() {
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 msg = "Hello from virtual module";`;
}
},
};
}
然后在代码中导入:
import { msg } from "virtual:my-module";
console.log(msg); // "Hello from virtual module"
典型应用:
- 注入环境变量:将
.env文件变量生成虚拟模块。 - 生成路由表:根据文件系统自动生成路由配置。
- 注入组件库注册信息:自动导入组件。
五、插件示例
1. 全局样式自动导入
自动在入口文件或每个模块中注入全局 CSS(如重置样式、主题变量)。
function globalStylePlugin() {
return {
name: "global-style",
transformIndexHtml(html) {
// 在 HTML 中直接插入 link 标签
return html.replace(
"</head>",
'<link rel="stylesheet" href="/src/styles/global.css"></head>',
);
},
// 或者使用 transform 钩子在 JS 模块中注入 import
transform(code, id) {
if (id.endsWith("main.js")) {
return `import '/src/styles/global.css';\n${code}`;
}
},
};
}
2. 组件库按需加载(类似 unplugin-vue-components)
自动解析模板中使用的组件并注册,无需手动导入。
import fs from "fs";
import path from "path";
function autoComponentsPlugin() {
const componentsDir = path.resolve(__dirname, "src/components");
const componentList = fs.readdirSync(componentsDir).map((file) => path.basename(file, ".vue"));
return {
name: "auto-components",
transform(code, id) {
if (!id.endsWith(".vue")) return;
// 查找模板中使用了哪些组件名(简单正则匹配)
const used = [];
const regex = /<(?:[\w-]+)?\s+([^>]*?)(?:>|$)/g;
let match;
while ((match = regex.exec(code))) {
const attrs = match[1];
const tagMatch = attrs.match(/^(\w+)/);
if (tagMatch && componentList.includes(tagMatch[1])) {
used.push(tagMatch[1]);
}
}
if (used.length) {
const imports = used
.map((name) => `import ${name} from '@/components/${name}.vue'`)
.join("\n");
return `${imports}\n<script>\nexport default { components: { ${used.join(",")} } }\n</script>${code}`;
}
return code;
},
};
}
3. 自动生成 API 类型文件
根据后端接口定义(如 OpenAPI 或自定义 JSON)生成 TypeScript 类型定义并作为虚拟模块提供。
import { generateTypes } from "./api-generator";
function apiTypesPlugin() {
const virtualId = "virtual:api-types";
const resolvedId = "\0" + virtualId;
let typesContent = "";
return {
name: "api-types",
buildStart() {
typesContent = generateTypes(); // 生成类型字符串
},
resolveId(id) {
if (id === virtualId) return resolvedId;
},
load(id) {
if (id === resolvedId) return typesContent;
},
};
}
然后在代码中:
import type { User, Post } from "virtual:api-types";
总结
| 钩子 | 作用 | 典型应用 |
|---|---|---|
config | 修改配置 | 动态添加 alias、proxy |
configureServer | 添加自定义中间件 | Mock API、代理 |
transformIndexHtml | 转换 HTML | 注入 script、meta |
handleHotUpdate | 自定义 HMR | 特定模块只更新自身 |
| 虚拟模块 | 动态生成模块内容 | 类型、路由、常量 |
Vite 插件开发充分利用这些钩子,可以极大扩展 Vite 的能力,满足各种工程化需求。开发时注意区分开发/生产环境,合理使用 Rollup 兼容钩子。
参考文献
以下链接在编写时均可正常访问:
相关文章
部署实践
Vite 项目的 CI/CD、多环境、CDN、Legacy 与体积预算等工程化要点。环境变量见 env;构建见 生产构建。
进阶配置
Vite 进阶:server.proxy、resolve.alias、SSR、多页面、库模式、vite preview 等。环境变量见 env。
生产构建
vite build 使用 Rollup 打包并应用内置优化。开发阶段原理见 工作原理。
create-vite
pnpm create vite 实际执行的是 npm 包 create-vite(与 vite 本体不同)。工程化背景见 工程化概览、工作原理。
工作原理
Vite 是面向现代浏览器的前端构建工具:开发阶段利用原生 ES 模块与按需编译;生产构建由 Rollup 完成。对比见 Webpack 与 Vite、工程化概览。
其他配置
除 ESLint、Prettier 等规范文件外,仓库中常见的工具链与包管理配置如下。见 工程化概览。