一、Vite 插件执行时机:核心钩子
Vite 插件本质是一个带钩子(hooks)的对象,在构建/服务生命周期的特定阶段被调用。与 Webpack 的"事件订阅"模型不同,Vite 插件是类 Rollup 的钩子函数模型——你提供函数,Vite 在对应时机调用。
1.1 核心钩子一览
| 钩子 | 时机 | 用途 |
|---|---|---|
config | 解析配置文件时 | 修改/补充配置 |
configResolved | 配置最终确定后 | 读取最终配置(如 mode) |
configureServer | dev-server 创建时 | 拦截请求、注入中间件(仅开发) |
resolveId | 解析模块路径时 | 拦截 import 路径,返回自定义 id |
load | 加载模块内容时 | 返回该 id 对应的模块代码 |
transform | 转换模块内容时 | 修改代码(如移除 console、语法转换) |
transformIndexHtml | 处理 index.html 时 | 往 HTML 注入标签/脚本 |
buildStart | 构建开始时 | 初始化(仅构建) |
generateBundle | 产物生成前 | 修改/增删产物文件(仅构建) |
closeBundle | 构建结束时 | 清理工作(仅构建) |
1.2 钩子执行顺序(一次请求/构建)
flowchart LR
A[config] --> B[configResolved]
B --> C[resolveId 解析路径]
C --> D[load 加载内容]
D --> E[transform 转换代码]
E --> F[浏览器执行 / 写入产物]
F -.仅构建.-> G[generateBundle]
G -.仅构建.-> H[closeBundle]
1.3 与 Webpack 插件差异
| 对比项 | Webpack 插件 | Vite 插件 |
|---|---|---|
| 模型 | 订阅 compiler 事件(tap 钩子) | 导出钩子函数对象 |
| 处理粒度 | 以 chunk/资源为单位 | 以单个模块为单位(resolveId→load→transform) |
| 开发/生产 | 大部分通用 | 部分钩子仅 serve / 仅 build |
| 编写成本 | 较重(需理解 compiler 生命周期) | 轻量,几个函数即可 |
二、官方核心插件
2.1 @vitejs/plugin-vue:Vue 单文件支持
npm install -D @vitejs/plugin-vue
// vite.config.ts
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
});
作用:解析 .vue 单文件(模板、脚本、样式拆分 + 编译),并支持 SFC 的 HMR。Vue 3 项目必须安装,否则 .vue 文件无法识别。
Vue 2 项目改用
@vitejs/plugin-vue2;React 项目用@vitejs/plugin-react(含 Fast Refresh 热更新)。
2.2 @vitejs/plugin-legacy:传统浏览器降级
import legacy from "@vitejs/plugin-legacy";
export default defineConfig({
plugins: [
legacy({
targets: ["chrome >= 64", "safari >= 12"],
}),
],
});
原理:为现代构建产物额外生成 SystemJS 降级包 + polyfill,现代浏览器加载原生 ESM 产物,老浏览器加载降级包,互不影响。
三、unplugin 生态
unplugin 是一个跨构建工具(Vite / Rollup / Webpack / esbuild)的插件框架,一套代码多端复用。高频成员:
| 插件 | 作用 | 典型收益 |
|---|---|---|
unplugin-auto-import | 自动导入 API(ref、computed 等) | 源码无需 import { ref } from "vue" |
unplugin-vue-components | 按需自动注册组件 | 不用手动 import Button from ... |
unplugin-icons | 图标按需加载 | 图标以组件形式使用,零手动引入 |
unplugin-auto-import + 配置 | 自动导入函数、类型 | 减少样板代码 |
// vite.config.ts —— 高频组合示例
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default defineConfig({
plugins: [
AutoImport({
imports: ["vue", "vue-router", "pinia"], // 自动引入这些库的 API
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()], // 组件按需 + 自动注册
}),
],
});
<script setup lang="ts">
// 无需 import ref / ElButton,直接使用
const count = ref(0);
</script>
<template>
<el-button @click="count++">点击 {{ count }}</el-button>
</template>
四、手写一个极简插件:看懂插件运行流程
4.1 插件一:虚拟模块(演示 resolveId + load)
// plugins/virtual-module.ts
import type { Plugin } from "vite";
export function virtualModule(): Plugin {
const VIRTUAL_ID = "virtual:hello"; // 自定义模块 id
return {
name: "virtual-module", // 插件名(调试/报错时显示)
resolveId(id) {
if (id === VIRTUAL_ID) return VIRTUAL_ID; // 命中后接管该 id 的解析
},
load(id) {
if (id === VIRTUAL_ID) {
return `export const msg = "来自虚拟模块的问候";`; // 返回模块内容
}
},
};
}
// 使用
import { msg } from "virtual:hello"; // 不存在于磁盘,由插件在内存中提供
console.log(msg);
4.2 插件二:移除 console.log(演示 transform)
// plugins/remove-console.ts
import type { Plugin } from "vite";
export function removeConsole(): Plugin {
return {
name: "remove-console",
apply: "build", // 只在生产构建生效(开发保留日志)
enforce: "post", // 后置执行:等核心转换完成再处理
transform(code, id) {
// 只处理业务源码,跳过 node_modules
if (id.includes("node_modules")) return;
if (!/\.(js|ts|vue)$/.test(id)) return;
return code.replace(/console\.\w+\([^)]*\);?/g, "");
},
};
}
// vite.config.ts
import { removeConsole } from "./plugins/remove-console";
export default defineConfig({
plugins: [removeConsole()],
});
4.3 运行流程回顾(对照上面两个插件)
flowchart LR
A[import 'virtual:hello'] -->|resolveId 拦截| B[返回虚拟 id]
B -->|load 生成内容| C[得到模块代码]
C -->|transform 移除 console| D[最终产物]
五、插件开发常见注意点
| 注意点 | 说明 |
|---|---|
apply 限定环境 | apply: "serve"(仅开发)/ "build"(仅构建),默认全环境 |
enforce 控制顺序 | "pre"(最先)/ "post"(最后),默认在中间;用于与核心转换的前后关系 |
| 命名规范 | 插件名以 vite-plugin- 开头(如 vite-plugin-xxx),便于识别 |
| 处理范围 | 用 id.includes("node_modules") 等条件跳过依赖,避免重复处理、性能损耗 |
| 虚拟模块规范 | 约定用 virtual: 前缀 + \0 标记,防止与真实路径冲突 |
| 缓存友好 | transform 尽量只改必要的代码,保持确定性,利于 Vite 缓存 |
| 兼容性 | 想同时支持其他构建工具,用 unplugin 框架编写 |
六、总结
- 模型:Vite 插件 = 钩子函数对象,核心链路
resolveId → load → transform; - 钩子分类:
config系列全程、configureServer仅开发、generateBundle/closeBundle仅构建; - 官方插件:
plugin-vue支持 SFC、plugin-legacy兼容老浏览器; - unplugin 生态:auto-import、vue-components 让"自动导入/按需注册"零配置;
- 手写插件:虚拟模块(resolveId+load)与代码转换(transform)两个模式覆盖大部分需求;
- 规范:
apply/enforce/命名/跳过 node_modules 是插件正确性的关键。