插件 API

    推荐在阅读下面的章节之前,首先阅读下 Rollup 插件文档

    如果插件不使用 Vite 特有的钩子,可以实现为 ,推荐使用 Rollup 插件名称约定

    • Rollup 插件应该有一个带 rollup-plugin- 前缀、语义清晰的名称。
    • 在 package.json 中包含 rollup-pluginvite-plugin 关键字。

    这样,插件也可以用于纯 Rollup 或基于 WMR 的项目。

    对于 Vite 专属的插件:

    • Vite 插件应该有一个带 vite-plugin- 前缀、语义清晰的名称。
    • 在 package.json 中包含 vite-plugin 关键字。
    • 在插件文档增加一部分关于为什么本插件是一个 Vite 专属插件的详细说明(如,本插件使用了 Vite 特有的插件钩子)。

    如果你的插件只适用于特定的框架,它的名字应该遵循以下前缀格式:

    • vite-plugin-vue- 前缀作为 Vue 插件
    • vite-plugin-react- 前缀作为 React 插件
    • vite-plugin-svelte- 前缀作为 Svelte 插件

    插件配置

    用户会将插件添加到项目的 devDependencies 中并使用数组形式的 plugins 选项配置它们。

    假值的插件将被忽略,可以用来轻松地启用或停用插件。

    plugins 也可以接受将多个插件作为单个元素的预设。这对于使用多个插件实现的复杂特性(如框架集成)很有用。该数组将在内部被扁平化(flatten)。

    1. // 框架插件
    2. import frameworkRefresh from 'vite-plugin-framework-refresh'
    3. import frameworkDevtools from 'vite-plugin-framework-devtools'
    4. export default function framework(config) {
    5. return [frameworkRefresh(config), frameworkDevTools(config)]
    6. }
    1. // vite.config.js
    2. import { defineConfig } from 'vite'
    3. import framework from 'vite-plugin-framework'
    4. export default defineConfig({
    5. plugins: [framework()]
    6. })

    简单示例

    :::tip 通常的惯例是创建一个 Vite/Rollup 插件作为一个返回实际插件对象的工厂函数。该函数可以接受允许用户自定义插件行为的选项。 :::

    1. export default function myPlugin() {
    2. const virtualFileId = '@my-virtual-file'
    3. return {
    4. name: 'my-plugin', // 必须的,将会在 warning 和 error 中显示
    5. resolveId(id) {
    6. if (id === virtualFileId) {
    7. return virtualFileId
    8. }
    9. },
    10. load(id) {
    11. if (id === virtualFileId) {
    12. return `export const msg = "from virtual file"`
    13. }
    14. }
    15. }
    16. }

    这使得可以在 JavaScript 中引入这些文件:

    1. import { msg } from '@my-virtual-file'
    2. console.log(msg)

    转换自定义文件类型

    1. const fileRegex = /\.(my-file-ext)$/
    2. export default function myPlugin() {
    3. return {
    4. name: 'transform-file',
    5. transform(src, id) {
    6. if (fileRegex.test(id)) {
    7. return {
    8. code: compileFileToJS(src),
    9. map: null // 如果可行将提供 source map
    10. }
    11. }
    12. }
    13. }

    在开发中,Vite 开发服务器会创建一个插件容器来调用 Rollup 构建钩子,与 Rollup 如出一辙。

    以下钩子在服务器启动时被调用:

    以下钩子会在每个传入模块请求时被调用:

    以下钩子在服务器关闭时被调用:

    请注意 moduleParsed 钩子在开发中是 不会 被调用的,因为 Vite 为了性能会避免完整的 AST 解析。

    (除了 closeBundle) 在开发中是 不会 被调用的。你可以认为 Vite 的开发服务器只调用了 rollup.rollup() 而没有调用 bundle.generate()

    Vite 独有钩子

    Vite 插件也可以提供钩子来服务于特定的 Vite 目标。这些钩子会被 Rollup 忽略。

    • 类型:
    • 在解析 Vite 配置前调用。钩子接收原始用户配置(命令行选项指定的会与配置文件合并)和一个描述配置环境的变量,包含正在使用的 modecommand。它可以返回一个将被深度合并到现有配置中的部分配置对象,或者直接改变配置(如果默认的合并不能达到预期的结果)。

      示例:

      ::: warning 注意 用户插件在运行这个钩子之前会被解析,因此在 config 钩子中注入其他插件不会有任何效果。 :::

    configResolved

    • 类型: (config: ResolvedConfig) => void | Promise<void>
    • 种类: async, parallel

      在解析 Vite 配置后调用。使用这个钩子读取和存储最终解析的配置。当插件需要根据运行的命令做一些不同的事情时,它也很有用。

      示例:

      1. const exmaplePlugin = () => {
      2. let config
      3. return {
      4. name: 'read-config',
      5. configResolved(resolvedConfig) {
      6. // 存储最终解析的配置
      7. config = resolvedConfig
      8. },
      9. // 在其他钩子中使用存储的配置
      10. transform(code, id) {
      11. if (config.command === 'serve') {
      12. // serve: 由开发服务器调用的插件
      13. } else {
      14. // build: 由 Rollup 调用的插件
      15. }
      16. }
      17. }
      18. }
    • 类型: (server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>
    • 种类: async, sequential
    • 此外请看 ViteDevServer

      是用于配置开发服务器的钩子。最常见的用例是在内部 应用程序中添加自定义中间件:

      1. const myPlugin = () => ({
      2. name: 'configure-server',
      3. configureServer(server) {
      4. server.middlewares.use((req, res, next) => {
      5. // 自定义请求处理...
      6. })
      7. }
      8. })

      注入后置中间件

      configureServer 钩子将在内部中间件被安装前调用,所以自定义的中间件将会默认会比内部中间件早运行。如果你想注入一个在内部中间件 之后 运行的中间件,你可以从 configureServer 返回一个函数,将会在内部中间件安装后被调用:

      1. const myPlugin = () => ({
      2. name: 'configure-server',
      3. configureServer(server) {
      4. // 返回一个在内部中间件安装后
      5. // 被调用的后置钩子
      6. return () => {
      7. server.middlewares.use((req, res, next) => {
      8. // 自定义请求处理...
      9. })
      10. }
      11. }
      12. })

      存储服务器访问

      在某些情况下,其他插件钩子可能需要访问开发服务器实例(例如访问 websocket 服务器、文件系统监视程序或模块图)。这个钩子也可以用来存储服务器实例以供其他钩子访问:

      1. const myPlugin = () => {
      2. let server
      3. return {
      4. name: 'configure-server',
      5. configureServer(_server) {
      6. server = _server
      7. },
      8. transform(code, id) {
      9. if (server) {
      10. // 使用 server...
      11. }
      12. }
      13. }

      注意 configureServer 在运行生产版本时不会被调用,所以其他钩子需要防范它缺失。

    transformIndexHtml

    • 类型: IndexHtmlTransformHook | { enforce?: 'pre' | 'post' transform: IndexHtmlTransformHook }
    • 种类: async, sequential

      转换 index.html 的专用钩子。钩子接收当前的 HTML 字符串和转换上下文。上下文在开发期间暴露实例,在构建期间暴露 Rollup 输出的包。

      这个钩子可以是异步的,并且可以返回以下其中之一:

      • 经过转换的 HTML 字符串
      • 注入到现有 HTML 中的标签描述符对象数组()。每个标签也可以指定它应该被注入到哪里(默认是在 <head> 之前)
      • 一个包含 { html, tags } 的对象

      基础示例:

      1. const htmlPlugin = () => {
      2. return {
      3. name: 'html-transform',
      4. transformIndexHtml(html) {
      5. return html.replace(
      6. /<title>(.*?)<\/title>/,
      7. `<title>Title replaced!</title>`
      8. )
      9. }
      10. }
      11. }
    • 类型: (ctx: HmrContext) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>

      执行自定义 HMR 更新处理。钩子接收一个带有以下签名的上下文对象:

      1. interface HmrContext {
      2. file: string
      3. timestamp: number
      4. modules: Array<ModuleNode>
      5. read: () => string | Promise<string>
      6. server: ViteDevServer
      7. }
      • modules 是受更改文件影响的模块数组。它是一个数组,因为单个文件可能映射到多个服务模块(例如 Vue 单文件组件)。

      • read 这是一个异步读函数,它返回文件的内容。之所以这样做,是因为在某些系统上,文件更改的回调函数可能会在编辑器完成文件更新之前过快地触发,并 fs.readFile 直接会返回空内容。传入的 read 函数规范了这种行为。

      钩子可以选择:

      • 过滤和缩小受影响的模块列表,使 HMR 更准确。

      • 返回一个空数组,并通过向客户端发送自定义事件来执行完整的自定义 HMR 处理:

        1. handleHotUpdate({ server }) {
        2. server.ws.send({
        3. type: 'custom',
        4. event: 'special-update',
        5. data: {}
        6. })
        7. return []
        8. }

        客户端代码应该使用 HMR API 注册相应的处理器(这应该被相同插件的 transform 钩子注入):

        1. if (import.meta.hot) {
        2. import.meta.hot.on('special-update', (data) => {
        3. // 执行自定义更新
        4. })
        5. }

    插件顺序

    一个 Vite 插件可以额外指定一个 enforce 属性(类似于 webpack 加载器)来调整它的应用顺序。enforce 的值可以是prepost。解析后的插件将按照以下顺序排列:

    • Alias
    • 带有 enforce: 'pre' 的用户插件
    • Vite 核心插件
    • 没有 enforce 值的用户插件
    • Vite 构建用的插件
    • 带有 enforce: 'post' 的用户插件
    • Vite 后置构建插件(最小化,manifest,报告)

    默认情况下插件在开发(serve)和构建(build)模式中都会调用。如果插件只需要在预览或构建期间有条件地应用,请使用 apply 属性指明它们仅在 'build''serve' 模式时调用:

    1. function myPlugin() {
    2. return {
    3. name: 'build-only',
    4. apply: 'build' // 或 'serve'
    5. }
    6. }

    Rollup 插件兼容性

    相当数量的 Rollup 插件将直接作为 Vite 插件工作(例如:@rollup/plugin-alias@rollup/plugin-json),但并不是所有的,因为有些插件钩子在非构建式的开发服务器上下文中没有意义。

    一般来说,只要 Rollup 插件符合以下标准,它就应该像 Vite 插件一样工作:

    • 没有使用 钩子。
    • 它在打包钩子和输出钩子之间没有很强的耦合。

    如果一个 Rollup 插件只在构建阶段有意义,则在 build.rollupOptions.plugins 下指定即可。

    你也可以用 Vite 独有的属性来扩展现有的 Rollup 插件:

    1. // vite.config.js
    2. import example from 'rollup-plugin-example'
    3. import { defineConfig } from 'vite'
    4. export default defineConfig({
    5. plugins: [
    6. {
    7. ...example(),
    8. enforce: 'post',
    9. apply: 'build'
    10. }
    11. ]
    12. })

    查看 Vite Rollup 插件 获取兼容的官方 Rollup 插件列表及其使用指南。

    路径规范化

    Vite 对路径进行了规范化处理,在解析路径时使用 POSIX 分隔符( / ),同时保留了 Windows 中的卷名。而另一方面,Rollup 在默认情况下保持解析的路径不变,因此解析的路径在 Windows 中会使用 win32 分隔符( \ )。然而,Rollup 插件会使用 @rollup/pluginutils 内部的 normalizePath 工具函数,它在执行比较之前将分隔符转换为 POSIX。所以意味着当这些插件在 Vite 中使用时,includeexclude 两个配置模式,以及与已解析路径比较相似的路径会正常工作。

    所以对于 Vite 插件来说,在将路径与已解析的路径进行比较时,首先规范化路径以使用 POSIX 分隔符是很重要的。从 vite 模块中也导出了一个等效的 工具函数。