Back

如何在 Vite 中构建和使用插件

如何在 Vite 中构建和使用插件

创建自定义 Vite 插件可以让你扩展构建过程,超越默认配置的限制——无论你需要转换自定义文件类型、注入构建时逻辑,还是添加开发服务器中间件。如果你已经触及标准 Vite 配置的极限,构建自己的插件就是自然而然的下一步。

本指南涵盖了核心概念:Vite 的 Plugin API 如何工作、你将使用的关键生命周期钩子,以及帮助你入门的实用示例。你将学会创建、测试和发布插件,以解决开发工作流中的实际问题。

核心要点

  • Vite 插件通过生命周期钩子扩展开发和生产构建过程
  • 大多数 Rollup 插件可以在 Vite 中使用,但开发服务器功能需要 Vite 特定的钩子
  • 在转向虚拟模块等高级模式之前,先从简单的转换开始
  • 发布插件时遵循命名约定和文档标准

理解 Vite 插件及其与 Rollup 的关系

Vite 插件是 JavaScript 对象,它们钩入开发和构建过程的不同阶段。它们基于 Rollup 的插件系统构建,并添加了 Vite 开发服务器特定的额外钩子。

Vite 在底层使用两种不同的构建工具:esbuild 为开发服务器提供原生 ES 模块的极速支持,而 Rollup 处理生产环境打包以获得最优输出。这种双重架构意味着你的插件可以使用 apply 选项针对特定环境:

function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    apply: 'serve', // 仅在开发环境运行
    // ... 钩子
  }
}

大多数 Rollup 插件无需修改即可在 Vite 中使用。但是,如果你需要开发服务器功能——比如添加中间件或在开发期间修改 HTML——你将需要 Vite 特定的钩子。

基本插件结构和配置

每个 Vite 插件必须有一个 name 属性和至少一个钩子函数:

import type { Plugin } from 'vite'

function myPlugin(options?: MyPluginOptions): Plugin {
  return {
    name: 'vite-plugin-example',
    enforce: 'pre', // 可选:控制插件执行顺序
    apply: 'build', // 可选:'serve' | 'build' | undefined
    
    // 钩子函数放在这里
    transform(code, id) {
      if (id.endsWith('.custom')) {
        return transformCustomFile(code)
      }
    }
  }
}

Plugin API 为所有钩子和选项提供了 TypeScript 类型定义。从 ‘vite’ 导入 Plugin 类型以获得完整的类型安全和自动补全。

Plugin API 中的核心生命周期钩子

不同的钩子在构建过程的不同阶段触发。以下是最常用的钩子:

配置钩子

  • config:在配置解析之前修改 Vite 配置
  • configResolved:访问最终解析的配置

转换钩子

  • transform:修改单个模块的源代码
  • load:为特定文件类型提供自定义加载逻辑
  • resolveId:自定义模块解析
{
  name: 'transform-plugin',
  transform(code, id) {
    if (!id.includes('node_modules') && id.endsWith('.js')) {
      return {
        code: addCustomHeader(code),
        map: null // Source map 支持
      }
    }
  }
}

服务器和构建钩子

  • configureServer:向开发服务器添加自定义中间件
  • transformIndexHtml:修改 HTML 文件
  • writeBundle:在打包文件写入磁盘后运行

每个钩子都有特定的返回类型和执行时机。transform 钩子会为每个模块运行,因此要保持快速。对特定文件的昂贵操作使用 load

构建你的第一个 Vite 插件:实用示例

让我们构建三个实用插件来演示常见模式:

示例 1:HTML 修改

function htmlPlugin(): Plugin {
  return {
    name: 'html-modifier',
    transformIndexHtml(html) {
      return html.replace(
        '</head>',
        '<script>console.log("Injected!")</script></head>'
      )
    }
  }
}

示例 2:开发服务器中间件

function analyticsPlugin(): Plugin {
  return {
    name: 'request-analytics',
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        console.log(`${req.method} ${req.url}`)
        next()
      })
    }
  }
}

示例 3:构建时文件生成

import path from 'path'
import fs from 'fs'
import type { Plugin, ResolvedConfig } from 'vite'

function generateManifest(): Plugin {
  let config: ResolvedConfig
  
  return {
    name: 'generate-manifest',
    configResolved(resolvedConfig) {
      config = resolvedConfig
    },
    writeBundle() {
      const manifestPath = path.join(config.build.outDir, 'manifest.json')
      fs.writeFileSync(manifestPath, JSON.stringify({
        timestamp: Date.now(),
        version: process.env.npm_package_version
      }))
    }
  }
}

对于调试,在钩子中使用 console.log 来跟踪执行。this.warn()this.error() 方法比抛出异常提供更好的错误报告。

高级插件模式和最佳实践

虚拟模块

创建磁盘上不存在的模块:

const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId

export function virtualPlugin(): Plugin {
  return {
    name: 'virtual-plugin',
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },
    load(id) {
      if (id === resolvedVirtualModuleId) {
        return `export const msg = "from virtual module"`
      }
    }
  }
}

性能优化

缓存昂贵的转换操作:

const cache = new Map<string, string>()

function cachedTransform(): Plugin {
  return {
    name: 'cached-transform',
    transform(code, id) {
      if (cache.has(id)) return cache.get(id)
      
      const result = expensiveOperation(code)
      cache.set(id, result)
      return result
    }
  }
}

使用 VitestcreateServer API 测试你的 Vite 插件,以独立验证钩子行为。

发布和分享你的插件

发布时遵循以下约定:

  1. 将包命名为 vite-plugin-[name]
  2. 在 package.json 的 keywords 中包含 "vite-plugin"
  3. 说明为什么它是 Vite 特定的(如果使用了 Vite 专有钩子)
  4. 如果是框架特定的,添加框架前缀:vite-plugin-vue-[name]

创建清晰的 README,包含安装说明、配置示例和 API 文档。将你的插件提交到 awesome-vite 以触达社区。

总结

构建自定义 Vite 插件让你能够完全控制构建过程。从使用 transformtransformIndexHtml 等钩子的简单转换开始,然后根据需要扩展到更复杂的模式。Plugin API 强大而易于上手——大多数插件只需要几个钩子就能解决特定问题。

要深入探索,请查看 Vite 官方插件文档 并研究生态系统中的流行插件。你的下一个构建优化或工作流改进可能只需一个插件就能实现。

常见问题

可以,大多数 Rollup 插件无需修改即可在 Vite 中使用。但是,依赖 moduleParsed 钩子或需要开发服务器功能的插件需要进行 Vite 特定的适配。请查看插件文档中的 Vite 兼容性说明。

在钩子中使用 console.log 语句来跟踪执行流程。this.warn() 和 this.error() 方法提供更好的错误报告。你还可以在 Vite 中使用 DEBUG 环境变量来查看详细日志。

enforce 选项控制插件的执行顺序。pre 插件在 Vite 核心插件之前运行,post 插件在之后运行。使用 pre 进行输入转换,使用 post 进行输出修改。如果不设置 enforce,插件按照它们出现的顺序运行。

如果你的插件解决了其他人可能面临的问题,即使是专业化的,也可以考虑发布。清楚地记录其特定用例。对于真正项目特定的插件,将它们保留在本地或私有仓库中。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay