Vite 插件架构概览

Vite 的插件系统建立在 Rollup 插件接口之上,同时扩展了一些 Vite 特有的钩子。这意味着大部分 Rollup 插件可以直接在 Vite 中使用,而 Vite 插件则通过额外的钩子来处理开发服务器和 HMR 等场景。理解这套架构是开发高质量插件的基础。

一个 Vite 插件本质上是一个对象,它包含一个 name 属性和各种钩子函数。最简单的插件结构如下:

// vite.config.js
import { defineConfig } from 'vite'

function myPlugin() {
  return {
    name: 'my-plugin', // 必填,插件名称
    // 各种钩子...
  }
}

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

插件生命周期与钩子

Vite 插件的钩子可以分为两类:Rollup 兼容钩子和 Vite 独有钩子。下面按执行顺序梳理核心钩子。

Rollup 兼容钩子

Vite 独有钩子

实战:编写一个文件头注释插件

下面我们编写一个实用的插件,它会在每个 JS/TS 文件构建产物的顶部自动添加版权注释头。

// plugins/vite-plugin-banner.js
export default function vitePluginBanner(options = {}) {
  const {
    banner = '// Built with Vite',
    include = /\.(js|ts|jsx|tsx)$/,
  } = options

  return {
    name: 'vite-plugin-banner',

    // 构建模式下生效
    apply: 'build',

    // 在 transform 阶段注入注释
    transform(code, id) {
      if (!include.test(id)) return null

      // 排除 node_modules
      if (id.includes('node_modules')) return null

      return {
        code: banner + '\n' + code,
        map: null, // 不生成 sourcemap
      }
    },

    buildEnd() {
      console.log('[banner] 版权注释注入完成')
    }
  }
}

在配置中使用它:

// vite.config.js
import banner from './plugins/vite-plugin-banner'

export default defineConfig({
  plugins: [
    banner({
      banner: '// Copyright 2026 MyCompany. All rights reserved.',
    })
  ]
})

实战:Markdown 转 HTML 插件

一个更复杂的例子是将 Markdown 文件作为 Vue 组件导入。这个插件演示了 resolveIdloadtransform 的配合使用。

// plugins/vite-plugin-md.js
import { marked } from 'marked'
import fs from 'node:fs'

export default function vitePluginMarkdown() {
  const virtualPrefix = '\0virtual:md:'

  return {
    name: 'vite-plugin-markdown',

    // 拦截 .md 文件的解析
    resolveId(id) {
      if (id.endsWith('.md')) {
        return virtualPrefix + id
      }
    },

    // 加载 Markdown 内容并转换为 HTML
    load(id) {
      if (!id.startsWith(virtualPrefix)) return null

      const filePath = id.slice(virtualPrefix.length)
      const raw = fs.readFileSync(filePath, 'utf-8')
      const html = marked(raw)

      // 导出为 Vue 函数组件
      return `export default {
  render() {
    return {
      template: ${JSON.stringify(`
${html}
`)} } } }` }, // 支持 HMR handleHotUpdate({ file, server }) { if (file.endsWith('.md')) { server.ws.send({ type: 'full-reload', path: '*', }) } } } }

HMR 热更新处理

handleHotUpdate 钩子是 Vite 插件的特色之一。它允许插件自定义热更新行为。其上下文对象包含以下关键信息:

handleHotUpdate({ file, server, modules, read, timestamp }) {
  // file — 变更的文件路径
  // server — Vite 开发服务器实例
  // modules — 受影响的模块列表
  // read — 读取文件内容的异步函数
  // timestamp — 更新时间戳

  // 返回空数组可以阻止默认的 HMR 行为
  if (file.endsWith('.custom')) {
    return []
  }

  // 返回模块数组可以自定义受影响的模块范围
  return modules
}

插件执行顺序

插件顺序至关重要。Vite 提供了 enforce 属性来控制执行顺序:

export default defineConfig({
  plugins: [
    { name: 'a', enforce: 'pre' },   // 第 1 执行
    { name: 'b' },                    // 第 3 执行(默认)
    { name: 'c', enforce: 'post' },   // 第 5 执行
    { name: 'd', enforce: 'pre' },    // 第 2 执行
    { name: 'e' },                    // 第 4 执行
  ]
})

另外,apply 属性可以控制插件仅在特定模式下生效:apply: 'serve' 仅在开发服务器中生效,apply: 'build' 仅在构建时生效。

小结

Vite 的插件系统提供了强大的扩展能力。从简单的代码转换到完整的文件类型支持,都可以通过插件实现。开发插件时需要注意:始终提供唯一的 name;正确处理 sourcemap 以保证调试体验;合理使用 enforceapply 控制执行时机;对于开发体验,务必实现 handleHotUpdate 钩子。掌握这些要点,你就能开发出高质量的 Vite 插件。