Vite 插件机制 + 常用插件 + 手写简易插件

13 阅读4分钟

一、Vite 插件执行时机:核心钩子

Vite 插件本质是一个带钩子(hooks)的对象,在构建/服务生命周期的特定阶段被调用。与 Webpack 的"事件订阅"模型不同,Vite 插件是类 Rollup 的钩子函数模型——你提供函数,Vite 在对应时机调用。

1.1 核心钩子一览

钩子时机用途
config解析配置文件时修改/补充配置
configResolved配置最终确定后读取最终配置(如 mode)
configureServerdev-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 是插件正确性的关键。