钩子
一、认识
其中 Vite
会调用一系列与 Rollup
兼容的钩子,这个钩子主要分为三个阶段:
-
服务器启动阶段:
options
和buildStart
钩子会在服务启动时被调用 -
请求响应阶段: 当浏览器发起请求时,
Vite
内部依次调用resolveId
、load
和transform
钩子 -
服务器关闭阶段:
Vite
会依次执行buildEnd
和closeBundle
钩子。
除了以上钩子,其他 Rollup
插件钩子(如moduleParsed
、renderChunk
)均不会在 Vite
开发阶段调用。而生产环境下,由于 Vite
直接使用 Rollup
,Vite
插件中所有 Rollup
的插件钩子都会生效。
1.1 执行顺序
-
服务启动阶段:
config
(Vite
独有)、configResolved
(Vite
独有)、options
、configureServer
(Vite
独有)、buildStart
-
请求响应阶段: 如果是
html
文件,仅执行transformIndexHtml
钩子;对于非HTML
文件,则依次执行resolveId
、load
和transform
钩子 -
热更新阶段: 执行
handleHotUpdate
钩子。 -
服务关闭阶段: 依次执行
buildEnd
和closeBundle
钩子
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'
}
二、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
: 解析参数
-
-
返回值: 返回值可以是
null
、string
或者一个对象-
返回值为
null
时,会默认交给下一个插件的resolveId
钩子处理。 -
返回值为
string
时,则停止后续插件的处理。这里为了让替换后的路径能被其他插件处理,特意调用了this.resolve
来交给其它插件处理,否则将不会进入到其它插件的处理。 -
返回值为一个对象,也会停止后续插件的处理,不过这个对象就可以包含更多的信息了,包括解析后的路径、是否被
enternal
、是否需要tree-shaking
等等,不过大部分情况下返回一个string
就够用了。
-
五、load
Rollup
通过调用 load
钩子加载模块内容。load
为 Async + First
类型,即异步优先的钩子,和 resolveId
类似。它的作用是通过 resolveId
解析后的路径来加载模块内容。
5.1 语法
function Plugin(options){
return {
name: "vite-plugin-xxx",
load(id){
}
}
}
-
参数:
id
:
-
返回值: 返回值一般是
null
、string
或者一个对象-
如果返回值为
null
,则交给下一个插件处理 -
如果返回值为
string
或者对象,则终止后续插件的处理 -
如果是对象可以包含
SourceMap
、AST
等
-
六、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
这个钩子仅在开发阶段会被调用,用于扩展 Vite
的 Dev 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();
})
}