插件机制:Rollup 插件接口 + Vite 专属钩子

本节目标

Vite 的插件系统直接复用 Rollup 的插件接口——一个插件就是「一个带 name 和若干钩子方法的对象」。这让你写的插件在「开发服务器」和「生产构建」里都能工作(构建阶段走 Rollup)。

// 一个最小 Vite 插件骨架
export default function myPlugin() {
  return {
    name: 'my-plugin',        // 插件名(必填,Rollup 要求唯一)
    // —— Rollup 通用钩子 ——
    transform(code, id) {
      // code: 文件源码;id: 文件绝对路径
      // 返回修改后的代码(或不返回表示不动)
      if (id.endsWith('.txt')) {
        return `export default ${JSON.stringify(code)}`
      }
    },
    // —— Vite 专属钩子(serve 阶段)——
    configureServer(server) {
      // 在开发服务器上加自定义中间件
      server.middlewares.use((req, res, next) => {
        if (req.url === '/ping') { res.end('pong'); return }
        next()
      })
    },
    // —— Vite 专属钩子:改 index.html ——
    transformIndexHtml(html) {
      return html.replace('<title>', '<title>[DEV] ')
    },
  }
}

关键心智点:

  1. serve 阶段(开发):Vite 用自己的中间件管线,插件可用 configureServer、transform、transformIndexHtml 等钩子干预请求与响应。
  2. build 阶段(生产):Vite 调用 Rollup,插件用 Rollup 标准钩子(resolveId、load、transform、generateBundle 等)工作。
  3. 同一个 transform 钩子,在两边都有效——这正是「一套插件通吃开发和生产」的原因。
// vite.config.js 里挂载插件
import { defineConfig } from 'vite'
import myPlugin from './my-plugin.js'

export default defineConfig({
  plugins: [myPlugin()],
})

名词解释

Rollup 插件接口:Vite 插件遵循 Rollup 的插件规范(一个含 name + 钩子的对象)。这意味着大量 Rollup 生态插件可直接用于 Vite。

transform 钩子:最常用的钩子,接收「文件源码 + 路径」,返回「修改后的代码」。可用于改写、注入、转译任意模块。

configureServer:Vite 专属钩子,只在开发服务器里执行,用来挂自定义中间件(如 mock 接口、特殊路由)。

transformIndexHtml:Vite 专属钩子,用来在开发/构建时改写 index.html(注入脚本、改 title 等)。

课后练习

练习 1:为什么说「Vite 插件能复用 Rollup 生态」?

答案:因为 Vite 插件接口直接兼容 Rollup 插件规范。生产构建时 Vite 底层就是 Rollup,所以那些为 Rollup 写的插件(只要不涉及 Vite 专属钩子之外的冲突)大多能直接在 Vite 里用。

练习 2:configureServer 钩子会在生产构建时执行吗?

答案:不会。它是 Vite 专属的「开发服务器」钩子,只在 vite dev(serve 阶段)触发;生产构建走 Rollup,不会启动 dev server,所以该钩子不执行。

总结

Vite 插件的灵魂是**「一个对象 = Rollup 插件 + Vite 专属钩子」**,serve 阶段用 configureServer/transformIndexHtml 干预服务器,build 阶段用 Rollup 钩子(transform/load…)参与打包,而 transform 在两阶段通用。理解了这套「双阶段钩子」模型,写插件就只是「选对钩子」。下一节我们亲手写一个:虚拟模块 + 注入环境变量。