跳到主要内容

钩子

2023年12月19日
柏拉文
越努力,越幸运

一、认识


其中 Vite 会调用一系列与 Rollup 兼容的钩子,这个钩子主要分为三个阶段:

  • 服务器启动阶段: optionsbuildStart钩子会在服务启动时被调用

  • 请求响应阶段: 当浏览器发起请求时,Vite 内部依次调用resolveIdloadtransform钩子

  • 服务器关闭阶段: Vite 会依次执行buildEndcloseBundle 钩子。

除了以上钩子,其他 Rollup 插件钩子(如moduleParsedrenderChunk)均不会在 Vite 开发阶段调用。而生产环境下,由于 Vite 直接使用 RollupVite 插件中所有 Rollup 的插件钩子都会生效。

1.1 执行顺序

Preview
  • 服务启动阶段: configVite 独有)、configResolvedVite 独有)、optionsconfigureServer(Vite 独有)、buildStart

  • 请求响应阶段: 如果是 html 文件,仅执行transformIndexHtml钩子;对于非 HTML 文件,则依次执行resolveIdloadtransform钩子

  • 热更新阶段: 执行handleHotUpdate钩子。

  • 服务关闭阶段: 依次执行buildEndcloseBundle钩子

1.2 应用场景

默认情况下 Vite 插件同时被用于开发环境和生产环境,你可以通过 apply 属性来决定应用场景:

{
// 'serve' 表示仅用于开发环境,'build'表示仅用于生产环境
apply: 'serve'
}

apply参数还可以配置成一个函数,进行更灵活的控制:

apply(config, { command }) {
// 只用于非 SSR 情况下的生产环境构建
return command === 'build' && !config.build.ssr
}

1.3 应用顺序

可以通过 enforce 属性来指定插件的执行顺序:

{
// 默认为`normal`,可取值还有`pre`和`post`
enforce: 'pre'
}
Preview

二、options


首先经历 options 钩子进行配置的转换,得到处理后的配置对象。

三、buildStart


Rollup 会调用 buildStart 钩子,正式开始构建流程。

四、resolveId


Rollup 先进入到 resolveId 钩子中解析文件路径。(从 input 配置指定的入口文件开始)。resolveId 钩子一般用来解析模块路径,为Async + First类型即异步优先的钩子。

4.1 语法

function Plugin(options){
return {
name: "vite-plugin-xxx",
// 传入三个参数,当前模块路径、引用当前模块的模块路径、其余参数
resolveId(importee, importer, resolveOptions){

}
}
}
  • 参数:

    • importee: 当前模块路径

    • importer: 引用当前模块的模块路径

    • resolveOptions: 解析参数

  • 返回值: 返回值可以是 nullstring 或者一个对象

    • 返回值为 null 时,会默认交给下一个插件的 resolveId 钩子处理。

    • 返回值为 string 时,则停止后续插件的处理。这里为了让替换后的路径能被其他插件处理,特意调用了 this.resolve 来交给其它插件处理,否则将不会进入到其它插件的处理。

    • 返回值为一个对象,也会停止后续插件的处理,不过这个对象就可以包含更多的信息了,包括解析后的路径、是否被 enternal、是否需要 tree-shaking 等等,不过大部分情况下返回一个 string 就够用了。

五、load


Rollup 通过调用 load 钩子加载模块内容。loadAsync + First 类型,即异步优先的钩子,和 resolveId 类似。它的作用是通过 resolveId 解析后的路径来加载模块内容。

5.1 语法

function Plugin(options){
return {
name: "vite-plugin-xxx",
load(id){

}
}
}
  • 参数:

    • id:
  • 返回值: 返回值一般是 nullstring 或者一个对象

    • 如果返回值为 null,则交给下一个插件处理

    • 如果返回值为 string 或者对象,则终止后续插件的处理

    • 如果是对象可以包含 SourceMapAST

六、transform


紧接着 Rollup 执行所有的 transform 钩子来对模块内容进行进行自定义的转换,比如 babel 转译。transform 钩子也是非常常见的一个钩子函数,为Async + Sequential类型,也就是异步串行钩子,作用是对加载后的模块内容进行自定义的转换。

6.1 语法

function Plugin(options){
return {
name: "vite-plugin-xxx",
transform(code, id){

}
}
}
  • 参数:

    • code: 模块代码

    • id: 模块 ID

  • 返回值: 返回一个包含 code(代码内容) 和 map(SourceMap 内容) 属性的对象,当然也可以返回 null 来跳过当前插件的 transform 处理。需要注意的是,当前插件返回的代码会作为下一个插件 transform 钩子的第一个入参,实现类似于瀑布流的处理。

七、独有 config


Vite 在读取完配置文件(即vite.config.ts)之后,会拿到用户导出的配置对象,然后执行 config 钩子。在这个钩子里面,你可以对配置文件导出的对象进行自定义的操作,如下代码所示:

// 返回部分配置(推荐)
const editConfigPlugin = () => ({
name: 'vite-plugin-modify-config',
config: () => ({
alias: {
react: require.resolve('react')
}
})
})

官方推荐的姿势是在 config 钩子中返回一个配置对象,这个配置对象会和 Vite 已有的配置进行深度的合并。

八、独有 configResolved


Vite 在解析完配置之后会调用 configResolved 钩子,这个钩子一般用来记录最终的配置信息,而不建议再修改配置,用法如下图所示:

const exmaplePlugin = () => {
let config

return {
name: 'read-config',

configResolved(resolvedConfig) {
// 记录最终配置
config = resolvedConfig
},

// 在其他钩子中可以访问到配置
transform(code, id) {
console.log(config)
}
}
}

九、独有 configureServer


这个钩子仅在开发阶段会被调用,用于扩展 ViteDev Server,一般用于增加自定义 server 中间件,如下代码所示:

const myPlugin = () => ({
name: 'configure-server',
configureServer(server) {
// 姿势 1: 在 Vite 内置中间件之前执行
server.middlewares.use((req, res, next) => {
// 自定义请求处理逻辑
})
// 姿势 2: 在 Vite 内置中间件之后执行
return () => {
server.middlewares.use((req, res, next) => {
// 自定义请求处理逻辑
})
}
}
})

十、独有 transformIndexHtml


这个钩子用来灵活控制 HTML 的内容,你可以拿到原始的 html 内容后进行任意的转换:

const htmlPlugin = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
return html.replace(
/<title>(.*?)</title>/,
`<title>换了个标题</title>`
)
}
}
}
// 也可以返回如下的对象结构,一般用于添加某些标签
const htmlPlugin = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
return {
html,
// 注入标签
tags: [
{
// 放到 body 末尾,可取值还有`head`|`head-prepend`|`body-prepend`,顾名思义
injectTo: 'body',
// 标签属性定义
attrs: { type: 'module', src: './index.ts' },
// 标签名
tag: 'script',
},
],
}
}
}
}

十一、独有 handleHotUpdate


这个钩子会在 Vite 服务端处理热更新时被调用,你可以在这个钩子中拿到热更新相关的上下文信息,进行热更模块的过滤,或者进行自定义的热更处理。下面是一个简单的例子:

const handleHmrPlugin = () => {
return {
async handleHotUpdate(ctx) {
// 需要热更的文件
console.log(ctx.file)
// 需要热更的模块,如一个 Vue 单文件会涉及多个模块
console.log(ctx.modules)
// 时间戳
console.log(ctx.timestamp)
// Vite Dev Server 实例
console.log(ctx.server)
// 读取最新的文件内容
console.log(await read())
// 自行处理 HMR 事件
ctx.server.ws.send({
type: 'custom',
event: 'special-update',
data: { a: 1 }
})
return []
}
}
}

// 前端代码中加入
if (import.meta.hot) {
import.meta.hot.on('special-update', (data) => {
// 执行自定义更新
// { a: 1 }
console.log(data)
window.location.reload();
})
}