Vite插件机制
Vite的插件机制是基于Rollup来设计的,同时它也扩展了Rollup的插件接口,新增了一些特定于Vite的选项。所以,只需编写一次Vite插件,就可以让它同时适用于开发和构建。
通用hook
Vite在开发阶段
会模拟Rollup的行为,Vite Dev Server会创建一个插件容器,以Rollup相同的方式调用Rollup Build Hooks,这些调用主要分为三个阶段:
服务器启动阶段
: 在此阶段,options和buildStart Hook会被调用。请求响应阶段
: 当浏览器发起请求时,Vite 内部依次调用resolveId、load和transform Hook。服务器关闭阶段
: 在此阶段,Vite会依次调用buildEnd和closeBundle Hook。
TIP
- 为了避免完整的AST解析以获得更好的性能,Vite在dev开发阶段没有调用
moduleParsed
Hook。 - 在Vite dev开发阶段,除了
closeBundle
Hook,Rollup的Output Generation Hooks都没有被调用,我们可以认为Vite dev server只调用了rollup.rollup()
,并没有调用bundle.generate()
Vite独有的Hook
这些Hook只会在Vite内部调用
config
Vite在读取完配置文件(vite.config.ts/vite.config.js
和CLI选项的合并)之后,会拿到用户导出的配置对象,然后会执行config
Hook。在这个Hook里面,我们可以对配置文件导出的对象进行自定义的操作。
该Hook可以返回部分配置对象,返回的对象将会和已存在的配置进行深度合并,也可以直接修改配置对象
如下两种情况:第一种返回部分配置对象,第二种直接对配置的某些属性进行修改
// return partial config (recommended)
const partialConfigPlugin = () => ({
name: 'return-partial',
config: () => ({
resolve: {
alias: {
foo: 'bar',
},
},
}),
})
// mutate the config directly (use only when merging doesn't work)
const mutateConfigPlugin = () => ({
name: 'mutate-config',
config(config, { command }) {
if (command === 'build') {
config.root = 'foo'
}
},
})
WARNING
用户定义的插件在config Hook执行前已经被解析了,所以在config Hook中再注入其他插件是不会有效果的
configResolved
Vite在解析完配置之后会调用configResolved钩子,这个钩子一般用来记录最终的配置信息,而不建议再修改配置。
当插件需要根据正在运行的命令执行不同的操作时,可以使用该Hook
const examplePlugin = () => {
let config
return {
name: 'read-config',
configResolved(resolvedConfig) {
// store the resolved config
config = resolvedConfig
},
// use stored config in other hooks
transform(code, id) {
if (config.command === 'serve') {
// dev: plugin invoked by dev server
} else {
// build: plugin invoked by Rollup
}
},
}
}
configureServer
查看更多ViteDevServer
WARNING
configureServer
Hook只有在dev环境调用,不会在生产构建时调用
- 该Hook用来配置dev Server,最常见的使用场景是添加自定义中间件
const myPlugin = () => ({
name: 'configure-server',
configureServer(server) {
server.middlewares.use((req, res, next) => {
// custom handle request...
})
},
})
- 注入后置中间件
configureServer Hook在内部其他中间件安装之前调用,所以自定义的中间件默认是在其他内部中间件之前执行,如果想让自定义中间件在内部中间件之后执行,可以返回一个函数。
const myPlugin = () => ({
name: 'configure-server',
configureServer(server) {
// return a post hook that is called after internal middlewares are
// installed
return () => {
server.middlewares.use((req, res, next) => {
// custom handle request...
})
}
},
})
- 在某些情况下,其他Hook可能需要访问dev服务器实例(例如
访问websocket服务器
、文件系统监视器
或模块图
)。这个钩子还可以用来存储服务器实例
,以便在其他钩子中访问:
const myPlugin = () => {
let server
return {
name: 'configure-server',
configureServer(_server) {
server = _server
},
transform(code, id) {
if (server) {
// use server...
}
},
}
}
configurePreviewServer
该Hook是用来启动预览服务,和configureServer
相似,该Hook中添加的中间件调用顺序也是在内部中间件之前的,如果想在内部中间件之后调用,也是需要返回一个函数
const myPlugin = () => ({
name: 'configure-preview-server',
configurePreviewServer(server) {
// return a post hook that is called after other middlewares are
// installed
return () => {
server.middlewares.use((req, res, next) => {
// custom handle request...
})
}
},
})
transformIndexHtml
该Hook用来获取HTML的内容,并且可以对HTML内容进行修改。
该Hook可返回以下选项中的一项:
返回转换过后的HTML字符串内容
返回一个数组,该数组包含标签描述对象(
{ tag, attrs, children, injectTo }
),其中的内容会被注入到已有的HTML中,还可在对象中指定标签要插入的位置,默认插入到<head>
中返回一个同时包含HTML和tag的对象(
{ html, tag }
)
基础示例:
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 | boolean>
children?: string | HtmlTagDescriptor[]
/**
* default: 'head-prepend'
*/
injectTo?: 'head' | 'body' | 'head-prepend' | 'body-prepend'
}
handleHotUpdate
该Hook会在Vite服务端处理热更新时被调用,我们可以在这个Hook中拿到热更新相关的上下文信息,进行热更模块的过滤,或者进行自定义热更处理。
插件执行顺序
与webpack loader相似,可以通过enforce
属性来指定插件执行的顺序,enforce
的值可以是pre
或者post
。
解析后的插件将以以下顺序执行:
- Alias (路径别名)相关的插件
- 配置
enforce: pre
的用户插件 - Vite核心插件
- 未配置
enforce
属性的用户插件 - Vite 生产环境构建用的插件
- 配置
enforce: post
的用户插件 - Vite 后置构建插件(如压缩插件)
插件Hook执行顺序
服务启动阶段依次执行
config
、configResolved
、options
、configureServer
、buildStart
Hook请求响应阶段:
如果是
HTML
文件,仅执行transformIndexHtml
Hook对于
非HTML
文件,则依次执行resolveId
、load
和transform
Hook
热更新阶段,执行
handleHotUpdate
Hook服务关闭阶段,依次执行
buildEnd
和closeBundle
Hook
插件应用条件
默认情况下,Vite插件会同时被应用在开发环境
和生产环境
,有时需要只在开发环境或者只在生产环境应用,可以使用apply
属性设置在哪个环境应用。
apply
属性可以是一个字符串
function myPlugin() {
return {
name: 'build-only',
apply: 'build', // or 'serve' 'serve' 表示仅用于开发环境,'build'表示仅用于生产环境
}
}
apply
属性还可以是一个函数
apply(config, { command }) {
// apply only on build but not for SSR
return command === 'build' && !config.build.ssr
}