插件 API
Vite 插件扩展了 Rollup 良好设计的插件接口,并添加了一些额外的 Vite 特定选项。因此,您可以编写一个 Vite 插件,使其同时适用于开发和构建。
建议您在阅读以下部分之前,先阅读Rollup 的插件文档。
编写插件
Vite 努力在开箱即用时提供既定的模式,因此在创建新插件之前,请确保您查看功能指南,以查看您的需求是否已得到满足。还可以查看可用的社区插件,包括兼容的 Rollup 插件和Vite 特定插件。
创建插件时,可以在您的 vite.config.js
中内联它。无需为此创建一个新包。一旦您发现某个插件在您的项目中很有用,请考虑分享它以帮助其他人在生态系统中。
提示
在学习、调试或编写插件时,我们建议在您的项目中包含vite-plugin-inspect。它允许您检查 Vite 插件的中间状态。安装后,您可以访问 localhost:5173/__inspect/
以检查项目的模块和转换堆栈。查看vite-plugin-inspect 文档中的安装说明。
约定
如果插件不使用 Vite 特定的钩子,并且可以作为兼容的 Rollup 插件实现,那么建议使用Rollup 插件命名约定。
- Rollup 插件应该有一个清晰的名称,并以
rollup-plugin-
为前缀。 - 在 package.json 中包含
rollup-plugin
和vite-plugin
关键字。
这使插件也可以在纯 Rollup 或基于 WMR 的项目中使用
仅适用于 Vite 的插件
- Vite 插件应该有一个清晰的名称,并以
vite-plugin-
为前缀。 - 在 package.json 中包含
vite-plugin
关键字。 - 在插件文档中包含一个部分,详细说明为什么它是 Vite 独有的插件(例如,它使用 Vite 特定的插件钩子)。
如果您的插件仅适用于特定框架,则其名称应作为前缀的一部分。
- Vue 插件使用
vite-plugin-vue-
前缀 - React 插件使用
vite-plugin-react-
前缀 - Svelte 插件使用
vite-plugin-svelte-
前缀
另请参阅虚拟模块约定。
插件配置
用户会将插件添加到项目的 devDependencies
中,并使用 plugins
数组选项配置它们。
import vitePlugin from 'vite-plugin-feature'
import rollupPlugin from 'rollup-plugin-feature'
export default defineConfig({
plugins: [vitePlugin(), rollupPlugin()],
})
虚假插件将被忽略,这可以用于轻松激活或停用插件。
plugins
还接受预设,包括多个插件作为一个元素。这对于使用多个插件实现的复杂功能(如框架集成)很有用。数组将在内部被扁平化。
// framework-plugin
import frameworkRefresh from 'vite-plugin-framework-refresh'
import frameworkDevtools from 'vite-plugin-framework-devtools'
export default function framework(config) {
return [frameworkRefresh(config), frameworkDevTools(config)]
}
import { defineConfig } from 'vite'
import framework from 'vite-plugin-framework'
export default defineConfig({
plugins: [framework()],
})
简单示例
提示
通常的做法是将 Vite/Rollup 插件编写为工厂函数,该函数返回实际的插件对象。该函数可以接受选项,允许用户自定义插件的行为。
转换自定义文件类型
const fileRegex = /\.(my-file-ext)$/
export default function myPlugin() {
return {
name: 'transform-file',
transform(src, id) {
if (fileRegex.test(id)) {
return {
code: compileFileToJS(src),
map: null, // provide source map if available
}
}
},
}
}
导入虚拟文件
请参阅下一节中的示例。
虚拟模块约定
虚拟模块是一种有用的方案,允许您使用正常的 ESM 导入语法将构建时信息传递到源文件。
export default function myPlugin() {
const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'my-plugin', // required, will show up in warnings and errors
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export const msg = "from virtual module"`
}
},
}
}
允许在 JavaScript 中导入模块
import { msg } from 'virtual:my-module'
console.log(msg)
按照约定,Vite(和 Rollup)中的虚拟模块以 virtual:
为前缀作为用户可见路径。如果可能,插件名称应用作命名空间以避免与生态系统中其他插件发生冲突。例如,vite-plugin-posts
可以要求用户导入 virtual:posts
或 virtual:posts/helpers
虚拟模块以获取构建时信息。在内部,使用虚拟模块的插件在解析 id 时应以 \0
为前缀模块 ID,这是 Rollup 生态系统中的约定。这可以防止其他插件尝试处理 id(如节点解析),并且核心功能(如源映射)可以使用此信息来区分虚拟模块和常规文件。\0
不是导入 URL 中允许的字符,因此我们必须在导入分析期间替换它们。\0{id}
虚拟 id 最终在浏览器开发过程中被编码为 /@id/__x00__{id}
。id 将在进入插件管道之前被解码,因此插件钩子代码看不到它。
请注意,直接从真实文件派生的模块(例如,单文件组件中的脚本模块(如 .vue 或 .svelte SFC))不需要遵循此约定。SFC 在处理时通常会生成一组子模块,但这些模块中的代码可以映射回文件系统。对这些子模块使用 \0
将阻止源映射正常工作。
通用钩子
在开发过程中,Vite 开发服务器创建一个插件容器,该容器以与 Rollup 相同的方式调用Rollup 构建钩子。
以下钩子在服务器启动时调用一次
以下钩子在每个传入的模块请求时调用
这些钩子还具有扩展的 options
参数,其中包含其他 Vite 特定属性。您可以在SSR 文档中阅读更多内容。
某些 resolveId
调用的 importer
值可能是根目录下通用 index.html
的绝对路径,因为由于 Vite 的非打包开发服务器模式,无法始终推导出实际的导入程序。对于在 Vite 的解析管道中处理的导入,可以在导入分析阶段跟踪导入程序,从而提供正确的 importer
值。
以下钩子在服务器关闭时调用
请注意,在开发过程中不会调用moduleParsed
钩子,因为 Vite 避免了完整的 AST 解析以提高性能。
输出生成钩子(除了 closeBundle
)在开发过程中不会被调用。您可以将 Vite 的开发服务器视为仅调用 rollup.rollup()
而无需调用 bundle.generate()
。
Vite 特定钩子
Vite 插件还可以提供用于服务于 Vite 特定目的的钩子。Rollup 会忽略这些钩子。
config
类型:
(config: UserConfig, env: { mode: string, command: string }) => UserConfig | null | void
种类:
异步
,顺序
在解析 Vite 配置之前修改它。该钩子接收原始用户配置(CLI 选项与配置文件合并)和当前配置环境,该环境公开正在使用的
mode
和command
。它可以返回一个部分配置对象,该对象将被深度合并到现有配置中,或者直接修改配置(如果默认合并无法实现所需的结果)。示例
js// 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' } }, })
注意
用户插件在运行此钩子之前被解析,因此在
config
钩子内注入其他插件将无效。
configResolved
类型:
(config: ResolvedConfig) => void | Promise<void>
种类:
异步
,并行
在解析 Vite 配置后调用。使用此钩子读取和存储最终解析的配置。当插件需要根据正在运行的命令执行不同的操作时,它也很有用。
示例
jsconst 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 } }, } }
请注意,在开发过程中,
command
值为serve
(在 cli 中,vite
、vite dev
和vite serve
是别名)。
configureServer
类型:
(server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>
种类:
异步
,顺序
另请参阅:ViteDevServer
用于配置开发服务器的钩子。最常见的用例是将自定义中间件添加到内部connect 应用中
jsconst myPlugin = () => ({ name: 'configure-server', configureServer(server) { server.middlewares.use((req, res, next) => { // custom handle request... }) }, })
注入后中间件
configureServer
钩子在安装内部中间件之前被调用,因此默认情况下自定义中间件将在内部中间件之前运行。如果要**在**内部中间件之后注入中间件,可以从configureServer
返回一个函数,该函数将在安装内部中间件后被调用。jsconst 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... }) } }, })
存储服务器访问
在某些情况下,其他插件钩子可能需要访问开发服务器实例(例如访问 WebSocket 服务器、文件系统监视器或模块图)。此钩子也可用于存储服务器实例,以便在其他钩子中访问。
jsconst myPlugin = () => { let server return { name: 'configure-server', configureServer(_server) { server = _server }, transform(code, id) { if (server) { // use server... } }, } }
注意,在运行生产构建时不会调用
configureServer
,因此您的其他钩子需要防止其不存在。
configurePreviewServer
类型:
(server: PreviewServer) => (() => void) | void | Promise<(() => void) | void>
种类:
异步
,顺序
另请参阅: PreviewServer
与
configureServer
相同,但用于预览服务器。与configureServer
类似,configurePreviewServer
钩子在安装其他中间件之前被调用。如果要**在**其他中间件之后注入中间件,可以从configurePreviewServer
返回一个函数,该函数将在安装内部中间件后被调用。jsconst 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
类型:
IndexHtmlTransformHook | { order?: 'pre' | 'post', handler: IndexHtmlTransformHook }
种类:
异步
,顺序
专用于转换 HTML 入口文件(例如
index.html
)的钩子。该钩子接收当前的 HTML 字符串和一个转换上下文。该上下文在开发期间公开ViteDevServer
实例,并在构建期间公开 Rollup 输出包。该钩子可以是异步的,并且可以返回以下内容之一:
- 转换后的 HTML 字符串
- 要注入到现有 HTML 中的标签描述符对象数组(
{ tag, attrs, children }
)。每个标签还可以指定应将其注入到哪里(默认为添加到<head>
的开头)。 - 包含两者作为
{ html, tags }
的对象。
默认情况下,
order
为undefined
,此钩子在 HTML 转换后应用。为了注入一个应该通过 Vite 插件管道传递的脚本,order: 'pre'
将在处理 HTML 之前应用该钩子。order: 'post'
在所有order
为 undefined 的钩子应用后应用该钩子。基本示例
jsconst htmlPlugin = () => { return { name: 'html-transform', transformIndexHtml(html) { return html.replace( /<title>(.*?)<\/title>/, `<title>Title replaced!</title>`, ) }, } }
完整钩子签名
tstype 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' }
注意
如果您使用具有自定义入口文件处理的框架(例如 SvelteKit),则不会调用此钩子。
handleHotUpdate
类型:
(ctx: HmrContext) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>
另请参阅: HMR API
执行自定义 HMR 更新处理。该钩子接收一个具有以下签名的上下文对象:
tsinterface HmrContext { file: string timestamp: number modules: Array<ModuleNode> read: () => string | Promise<string> server: ViteDevServer }
modules
是受更改文件影响的模块数组。它是一个数组,因为单个文件可能映射到多个已服务的模块(例如 Vue SFC)。read
是一个异步读取函数,它返回文件的内容。提供此功能是因为在某些系统上,文件更改回调可能会在编辑器完成更新文件之前过快地触发,并且直接fs.readFile
将返回空内容。传入的读取函数标准化了此行为。
该钩子可以选择:
筛选并缩小受影响的模块列表,使 HMR 更准确。
返回空数组并执行完全重新加载。
jshandleHotUpdate({ server, modules, timestamp }) { // Invalidate modules manually const invalidatedModules = new Set() for (const mod of modules) { server.moduleGraph.invalidateModule( mod, invalidatedModules, timestamp, true ) } server.ws.send({ type: 'full-reload' }) return [] }
返回空数组并通过向客户端发送自定义事件来执行完全自定义的 HMR 处理。
jshandleHotUpdate({ server }) { server.ws.send({ type: 'custom', event: 'special-update', data: {} }) return [] }
客户端代码应使用 HMR API 注册相应的处理程序(这可以由同一插件的
transform
钩子注入)。jsif (import.meta.hot) { import.meta.hot.on('special-update', (data) => { // perform custom update }) }
插件排序
Vite 插件还可以指定一个 enforce
属性(类似于 Webpack 加载器)来调整其应用顺序。enforce
的值可以是 "pre"
或 "post"
。已解析的插件将按以下顺序排列:
- 别名
- 具有
enforce: 'pre'
的用户插件 - Vite 核心插件
- 没有 enforce 值的用户插件
- Vite 构建插件
- 具有
enforce: 'post'
的用户插件 - Vite 构建后插件(压缩、清单、报告)
请注意,这与钩子排序是分开的,它们仍然分别受其 order
属性的约束 与 Rollup 钩子一样。
条件应用
默认情况下,插件会同时用于服务和构建。在插件需要有条件地仅在服务或构建期间应用的情况下,请使用 apply
属性仅在 'build'
或 'serve'
期间调用它们。
function myPlugin() {
return {
name: 'build-only',
apply: 'build', // or 'serve'
}
}
还可以使用函数进行更精确的控制。
apply(config, { command }) {
// apply only on build but not for SSR
return command === 'build' && !config.build.ssr
}
Rollup 插件兼容性
相当多的 Rollup 插件可以直接用作 Vite 插件(例如 @rollup/plugin-alias
或 @rollup/plugin-json
),但并非所有插件都如此,因为某些插件钩子在非捆绑式开发服务器上下文中没有意义。
通常,只要 Rollup 插件符合以下条件,它就应该可以直接用作 Vite 插件:
- 它不使用
moduleParsed
钩子。 - 它在捆绑阶段钩子和输出阶段钩子之间没有强耦合。
如果 Rollup 插件仅对构建阶段有意义,则可以将其指定在 build.rollupOptions.plugins
下。它的工作方式与具有 enforce: 'post'
和 apply: 'build'
的 Vite 插件相同。
您还可以使用 Vite 专属属性增强现有的 Rollup 插件。
import example from 'rollup-plugin-example'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
{
...example(),
enforce: 'post',
apply: 'build',
},
],
})
路径规范化
Vite 在解析 id 时会规范化路径以使用 POSIX 分隔符 ( / ),同时保留 Windows 中的卷。另一方面,Rollup 默认情况下会保持解析后的路径不变,因此解析后的 id 在 Windows 中具有 win32 分隔符 ( \ )。但是,Rollup 插件在内部使用来自 @rollup/pluginutils
的 normalizePath
实用程序函数,该函数在执行比较之前将分隔符转换为 POSIX。这意味着当这些插件在 Vite 中使用时,include
和 exclude
配置模式以及其他类似路径与解析后的 id 比较将正常工作。
因此,对于 Vite 插件,在将路径与解析后的 id 进行比较时,务必先将路径规范化为使用 POSIX 分隔符。vite
模块导出了等效的 normalizePath
实用程序函数。
import { normalizePath } from 'vite'
normalizePath('foo\\bar') // 'foo/bar'
normalizePath('foo/bar') // 'foo/bar'
筛选、include/exclude 模式
Vite 公开了 @rollup/pluginutils
的 createFilter
函数,以鼓励 Vite 特定的插件和集成使用标准的 include/exclude 筛选模式,Vite 核心本身也使用了该模式。
客户端-服务器通信
从 Vite 2.9 开始,我们为插件提供了一些实用程序,以帮助处理与客户端的通信。
服务器到客户端
在插件端,我们可以使用 server.ws.send
将事件广播到客户端。
export default defineConfig({
plugins: [
{
// ...
configureServer(server) {
server.ws.on('connection', () => {
server.ws.send('my:greetings', { msg: 'hello' })
})
},
},
],
})
注意
我们建议**始终为**您的事件名称添加前缀,以避免与其他插件发生冲突。
在客户端,使用 hot.on
监听事件。
// client side
if (import.meta.hot) {
import.meta.hot.on('my:greetings', (data) => {
console.log(data.msg) // hello
})
}
客户端到服务器
要从客户端向服务器发送事件,我们可以使用 hot.send
// client side
if (import.meta.hot) {
import.meta.hot.send('my:from-client', { msg: 'Hey!' })
}
然后使用 server.ws.on
并监听服务器端的事件。
export default defineConfig({
plugins: [
{
// ...
configureServer(server) {
server.ws.on('my:from-client', (data, client) => {
console.log('Message from client:', data.msg) // Hey!
// reply only to the client (if needed)
client.send('my:ack', { msg: 'Hi! I got your message!' })
})
},
},
],
})
自定义事件的 TypeScript
在内部,vite 从 CustomEventMap
接口推断有效负载的类型,可以通过扩展该接口来为自定义事件设置类型。
注意
指定 TypeScript 声明文件时,请确保包含 .d.ts
扩展名。否则,Typescript 可能不知道该模块尝试扩展哪个文件。
import 'vite/types/customEvent.d.ts'
declare module 'vite/types/customEvent.d.ts' {
interface CustomEventMap {
'custom:foo': { msg: string }
// 'event-key': payload
}
}
此接口扩展由 InferCustomEventPayload<T>
用于推断事件 T
的有效负载类型。有关如何使用此接口的更多信息,请参阅 HMR API 文档。
type CustomFooPayload = InferCustomEventPayload<'custom:foo'>
import.meta.hot?.on('custom:foo', (payload) => {
// The type of payload will be { msg: string }
})
import.meta.hot?.on('unknown:event', (payload) => {
// The type of payload will be any
})