阅读(143) (0)

Vite 插件 API 独有钩子

2022-03-08 09:33:13 更新

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

config

  • 类型: ​(config: UserConfig, env: { mode: string, command: string }) => UserConfig | null | void
  • 种类: ​async​, ​sequential

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

示例:

// 返回部分配置(推荐)
const partialConfigPlugin = () => ({
  name: 'return-partial',
  config: () => ({
    alias: {
      foo: 'bar'
    }
  })
})

// 直接改变配置(应仅在合并不起作用时使用)
const mutateConfigPlugin = () => ({
  name: 'mutate-config',
  config(config, { command }) {
    if (command === 'build') {
      config.root = __dirname
    }
  }
})

注意:

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

configResolved

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

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

示例:

const exmaplePlugin = () => {
  let config

  return {
    name: 'read-config',

    configResolved(resolvedConfig) {
      // 存储最终解析的配置
      config = resolvedConfig
    },

    // 在其他钩子中使用存储的配置
    transform(code, id) {
      if (config.command === 'serve') {
        // dev: 由开发服务器调用的插件
      } else {
        // build: 由 Rollup 调用的插件
      }
    }
  }
}

注意,在开发环境下,command 的值为 serve(在 CLI 中,vite 和 vite dev 是 vite serve 的别名)。

configureServer

  • 类型:​(server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>
  • 种类: ​async​, ​sequential

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

const myPlugin = () => ({
  name: 'configure-server',
  configureServer(server) {
    server.middlewares.use((req, res, next) => {
      // 自定义请求处理...
    })
  }
})

注入后置中间件

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

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

存储服务器访问

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

const myPlugin = () => {
  let server
  return {
    name: 'configure-server',
    configureServer(_server) {
      server = _server
    },
    transform(code, id) {
      if (server) {
        // 使用 server...
      }
    }
  }
}

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

transformIndexHtml

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

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

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

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

基础示例:

const htmlPlugin = () => {
  return {
    name: 'html-transform',
    transformIndexHtml(html) {
      return html.replace(
        /<title>(.*?)<\/title>/,
        `<title>Title replaced!</title>`
      )
    }
  }
}

完整钩子签名:

type IndexHtmlTransformHook = (
  html: string,
  ctx: {
    path: string
    filename: string
    server?: ViteDevServer
    bundle?: import('rollup').OutputBundle
    chunk?: import('rollup').OutputChunk
  }
) =>
  | IndexHtmlTransformResult
  | void
  | Promise<IndexHtmlTransformResult | void>

type IndexHtmlTransformResult =
  | string
  | HtmlTagDescriptor[]
  | {
      html: string
      tags: HtmlTagDescriptor[]
    }

interface HtmlTagDescriptor {
  tag: string
  attrs?: Record<string, string>
  children?: string | HtmlTagDescriptor[]
  /**
   * 默认: 'head-prepend'
   */
  injectTo?: 'head' | 'body' | 'head-prepend' | 'body-prepend'
}

handleHotUpdate

类型: ​(ctx: HmrContext) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>

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

interface HmrContext {
  file: string
  timestamp: number
  modules: Array<ModuleNode>
  read: () => string | Promise<string>
  server: ViteDevServer
}
  • modules ​是受更改文件影响的模块数组。它是一个数组,因为单个文件可能映射到多个服务模块(例如 Vue 单文件组件)。
  • read ​这是一个异步读函数,它返回文件的内容。之所以这样做,是因为在某些系统上,文件更改的回调函数可能会在编辑器完成文件更新之前过快地触发,并 ​fs.readFile​ 直接会返回空内容。传入的 ​read ​函数规范了这种行为。

钩子可以选择:

  • 过滤和缩小受影响的模块列表,使 HMR 更准确。
  • 返回一个空数组,并通过向客户端发送自定义事件来执行完整的自定义 HMR 处理:
handleHotUpdate({ server }) {
  server.ws.send({
    type: 'custom',
    event: 'special-update',
    data: {}
  })
  return []
}

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

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