Plugin 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-
. - Включите ключевые слова
rollup-plugin
иvite-plugin
в package.json.
Это позволяет использовать плагин также в чистых проектах Rollup или на основе WMR.
Для плагинов только для Vite:
- Плагины Vite должны иметь понятное имя с префиксом
vite-plugin-
. - Включите ключевое слово
vite-plugin
в package.json. - Включите раздел в документации плагина, объясняющий, почему это плагин только для Vite (например, он использует специфические для Vite хуки плагинов).
Если ваш плагин будет работать только для определённого фреймворка, его имя должно быть включено в префикс:
- Префикс
vite-plugin-vue-
для плагинов Vue - Префикс
vite-plugin-react-
для плагинов React - Префикс
vite-plugin-svelte-
для плагинов Svelte
Смотрите также Конвенция виртуальных модулей.
Конфигурация плагинов
Пользователи добавят плагины в devDependencies
проекта и настроят их с помощью опции массива plugins
:
// vite.config.js
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)]
}
// vite.config.js
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, // предоставьте карту источников, если она доступна
}
}
},
}
}
Импорт виртуального файла
Смотрите пример в следующем разделе.
Конвенция виртуальных модулей
Виртуальные модули — это полезная схема, которая позволяет передавать информацию о времени сборки в исходные файлы, используя обычный синтаксис импорта ESM.
export default function myPlugin() {
const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'my-plugin', // обязательно, будет отображаться в предупреждениях и ошибках
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
, чтобы получить информацию о времени сборки. Внутри плагины, использующие виртуальные модули, должны префиксировать идентификатор модуля символом \0
при разрешении идентификатора, что является соглашением из экосистемы Rollup. Это предотвращает попытки других плагинов обрабатывать идентификатор (например, разрешение узлов), и основные функции, такие как карты источников, могут использовать эту информацию для различения виртуальных модулей и обычных файлов. Символ \0
не является допустимым символом в URL-адресах импорта, поэтому мы должны заменить его во время анализа импорта. Виртуальный идентификатор \0{id}
в конечном итоге кодируется как /@id/__x00__{id}
во время разработки в браузере. Идентификатор будет декодирован обратно перед входом в конвейер плагинов, поэтому это не видно коду хуков плагинов.
Обратите внимание, что модули, непосредственно производные от реального файла, как в случае скриптового модуля в компоненте единого файла (например, однофайловые компоненты .vue или .svelte), не обязаны следовать этому соглашению. SFC обычно генерируют набор подмодулей при обработке, но код в этих подмодулях может быть сопоставлен с файловой системой. Использование \0
для этих подмодулей предотвратит корректную работу карт источников.
Универсальные хуки
Во время разработки сервер Vite создает контейнер плагинов, который вызывает хуки сборки Rollup так же, как это делает Rollup.
Следующие хуки вызываются один раз при запуске сервера:
Следующие хуки вызываются при каждом входящем запросе модуля:
Эти хуки также имеют расширенный параметр options
с дополнительными свойствами, специфичными для Vite. Вы можете прочитать больше в документации по SSR.
Некоторые вызовы resolveId
могут иметь значение importer
в виде абсолютного пути к общему index.html
в корне, так как не всегда возможно определить фактический импортёр из-за паттерна разработки без сборки пакета в сервере Vite. Для импортов, обрабатываемых в рамках разрешающего конвейера Vite, импортёр может отслеживаться на этапе анализа импорта, обеспечивая правильное значение importer
.
Следующие хуки вызываются, когда сервер закрывается:
Обратите внимание, что хук moduleParsed
не вызывается во время разработки, поскольку Vite избегает полного разбора AST для повышения производительности.
Хуки генерации вывода (за исключением closeBundle
) не вызываются во время разработки. Вы можете рассматривать dev-сервер Vite как вызывающий только rollup.rollup()
, не вызывая bundle.generate()
.
Специфические хуки Vite
Плагины Vite также могут предоставлять хуки, которые служат специфическим для Vite целям. Эти хуки игнорируются Rollup.
config
Тип:
(config: UserConfig, env: { mode: string, command: string }) => UserConfig | null | void
Вид:
async
,sequential
Измените конфигурацию Vite перед её разрешением. Хук получает необработанную пользовательскую конфигурацию (опции CLI, объединённые с файлом конфигурации) и текущую среду конфигурации, которая предоставляет используемые
mode
иcommand
. Он может вернуть частичный объект конфигурации, который будет глубоко объединён с существующей конфигурацией, или напрямую изменить конфигурацию (если стандартное объединение не может достичь желаемого результата).Пример:
js// возврат частичной конфигурации (рекомендуется) const partialConfigPlugin = () => ({ name: 'return-partial', config: () => ({ resolve: { alias: { foo: 'bar', }, }, }), }) // прямое изменение конфигурации (используйте только когда объединение не работает) const mutateConfigPlugin = () => ({ name: 'mutate-config', config(config, { command }) { if (command === 'build') { config.root = 'foo' } }, })
ПРЕДУПРЕЖДЕНИЕ
Пользовательские плагины разрешаются перед выполнением этого хука, поэтому внедрение других плагинов внутри хука
config
не будет иметь эффекта.
configResolved
Type:
(config: ResolvedConfig) => void | Promise<void>
Kind:
async
,parallel
Вызывается после разрешения конфигурации Vite. Используйте этот хук, чтобы прочитать и сохранить окончательную разрешённую конфигурацию. Он также полезен, когда плагин должен выполнять что-то другое в зависимости от выполняемой команды.
Пример:
jsconst examplePlugin = () => { let config return { name: 'read-config', configResolved(resolvedConfig) { // сохраняем разрешённую конфигурацию config = resolvedConfig }, // используем сохранённую конфигурацию в других хуках transform(code, id) { if (config.command === 'serve') { // dev: плагин вызывается dev-сервером } else { // build: плагин вызывается Rollup } }, } }
Обратите внимание, что значение
command
равноserve
в режиме разработки (в CLIvite
,vite dev
иvite serve
являются псевдонимами).
configureServer
Тип:
(server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>
Вид:
async
,sequential
Смотрите также: ViteDevServer
Хук для настройки dev-сервера. Наиболее распространенный случай использования — добавление пользовательских промежуточных слоев к внутреннему connect приложению:
jsconst myPlugin = () => ({ name: 'configure-server', configureServer(server) { server.middlewares.use((req, res, next) => { // пользовательская обработка запроса... }) }, })
Внедрение пост-промежуточного слоя
Хук
configureServer
вызывается до установки внутренних промежуточных слоев, поэтому пользовательские промежуточные слои будут выполняться до внутренних промежуточных слоев по умолчанию. Если вы хотите внедрить промежуточный слой после внутренних промежуточных слоев, вы можете вернуть функцию изconfigureServer
, которая будет вызвана после установки внутренних промежуточных слоев:jsconst myPlugin = () => ({ name: 'configure-server', configureServer(server) { // возвращаем пост-хук, который вызывается после установки внутренних промежуточных слоев return () => { server.middlewares.use((req, res, next) => { // пользовательская обработка запроса... }) } }, })
Хранение доступа к серверу
В некоторых случаях другие хуки плагинов могут нуждаться в доступе к экземпляру dev-сервера (например, для доступа к серверу веб-сокетов, наблюдателю за файловой системой или графу модулей). Этот хук также можно использовать для хранения экземпляра сервера для доступа в других хуках:
jsconst myPlugin = () => { let server return { name: 'configure-server', configureServer(_server) { server = _server }, transform(code, id) { if (server) { // используем сервер... } }, } }
Обратите внимание, что
configureServer
не вызывается при создании продакшен-сборки, поэтому ваши другие хуки должны учитывать его отсутствие.
configurePreviewServer
Тип:
(server: PreviewServer) => (() => void) | void | Promise<(() => void) | void>
Вид:
async
,sequential
Смотрите также: PreviewServer
То же самое, что и
configureServer
, но для сервера предварительного просмотра. АналогичноconfigureServer
, хукconfigurePreviewServer
вызывается до установки других промежуточных слоев. Если вы хотите внедрить промежуточный слой после других промежуточных слоев, вы можете вернуть функцию изconfigurePreviewServer
, которая будет вызвана после установки внутренних промежуточных слоев:jsconst myPlugin = () => ({ name: 'configure-preview-server', configurePreviewServer(server) { // возвращаем пост-хук, который вызывается после установки других промежуточных слоев return () => { server.middlewares.use((req, res, next) => { // пользовательская обработка запроса... }) } }, })
transformIndexHtml
Тип:
IndexHtmlTransformHook | { order?: 'pre' | 'post', handler: IndexHtmlTransformHook }
Вид:
async
,sequential
Специальный хук для преобразования файлов HTML-точек входа, таких как
index.html
. Хук получает текущую строку HTML и контекст преобразования. Контекст предоставляет экземплярViteDevServer
во время разработки и выводной пакет Rollup во время сборки.Хук может быть асинхронным и может возвращать одно из следующих значений:
- Преобразованную строку HTML
- Массив объектов-дескрипторов тегов (
{ tag, attrs, children }
), которые нужно вставить в существующий HTML. Каждый тег также может указывать, куда он должен быть вставлен (по умолчанию — добавление в<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[] /** * по умолчанию: '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 }) { // Ручное аннулирование модулей 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) => { // выполняем пользовательское обновление }) }
Порядок плагинов
Плагин 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', // или 'serve'
}
}
Функция также может быть использована для более точного контроля:
apply(config, { command }) {
// применяем только при сборке, но не для SSR
return command === 'build' && !config.build.ssr
}
Совместимость плагинов Rollup
Достаточное количество плагинов Rollup будет работать напрямую как плагины Vite (например, @rollup/plugin-alias
или @rollup/plugin-json
), но не все из них, так как некоторые хуки плагинов не имеют смысла в контексте dev-сервера без сборки пакета.
В общем, если плагин Rollup соответствует следующим критериям, он должен работать как плагин Vite:
- Он не использует хук
moduleParsed
. - У него нет сильной связи между хуками фазы сборки и хуками фазы вывода.
Если плагин Rollup имеет смысл только для фазы сборки, его можно указать в build.rollupOptions.plugins
. Он будет работать так же, как плагин Vite с enforce: 'post'
и apply: 'build'
.
Вы также можете дополнить существующий плагин Rollup свойствами, специфичными для Vite:
// vite.config.js
import example from 'rollup-plugin-example'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
{
...example(),
enforce: 'post',
apply: 'build',
},
],
})
Нормализация путей
Vite нормализует пути при разрешении идентификаторов, используя разделители POSIX ( / ), сохраняя при этом объём в Windows. С другой стороны, Rollup по умолчанию оставляет разрешённые пути нетронутыми, поэтому разрешённые идентификаторы имеют разделители win32 ( \ ) в Windows. Однако плагины Rollup используют внутреннюю функцию утилиты normalizePath
из @rollup/pluginutils
, которая преобразует разделители в POSIX перед выполнением сравнений. Это означает, что когда эти плагины используются в Vite, шаблоны конфигурации include
и exclude
, а также другие аналогичные пути для сравнений разрешённых идентификаторов работают корректно.
Таким образом, для плагинов Vite, при сравнении путей с разрешёнными идентификаторами важно сначала нормализовать пути, чтобы использовать разделители POSIX. Эквивалентная функция утилиты normalizePath
экспортируется из модуля vite
.
import { normalizePath } from 'vite'
normalizePath('foo\\bar') // 'foo/bar'
normalizePath('foo/bar') // 'foo/bar'
Фильтрация, шаблон include/exclude
Vite предоставляет функцию createFilter
из @rollup/pluginutils
, чтобы побудить специфичные для Vite плагины и интеграции использовать стандартный шаблон фильтрации include/exclude, который также используется в Vite Core.
Связь клиент-сервер
Начиная с Vite 2.9, мы предоставляем некоторые утилиты для плагинов, чтобы помочь в обработке связи с клиентами.
От сервера к клиенту
На стороне плагина мы можем использовать server.ws.send
, чтобы транслировать события клиенту:
// vite.config.js
export default defineConfig({
plugins: [
{
// ...
configureServer(server) {
server.ws.on('connection', () => {
server.ws.send('my:greetings', { msg: 'привет' })
})
},
},
],
})
ПРИМЕЧАНИЕ
Мы рекомендуем всегда добавлять префикс к именам ваших событий, чтобы избежать конфликтов с другими плагинами.
На стороне клиента используйте hot.on
, чтобы слушать события:
import 'vite/client'
// ---cut---
// сторона клиента
if (import.meta.hot) {
import.meta.hot.on('my:greetings', (data) => {
console.log(data.msg) // привет
})
}
От клиента к серверу
Чтобы отправить события от клиента к серверу, мы можем использовать hot.send
:
// сторона клиента
if (import.meta.hot) {
import.meta.hot.send('my:from-client', { msg: 'Привет!' })
}
Затем используйте server.ws.on
и слушайте события на стороне сервера:
// vite.config.js
export default defineConfig({
plugins: [
{
// ...
configureServer(server) {
server.ws.on('my:from-client', (data, client) => {
console.log('Сообщение от клиента:', data.msg) // Привет!
// отвечаем только клиенту (если необходимо)
client.send('my:ack', { msg: 'Привет! Я получил ваше сообщение!' })
})
},
},
],
})
TypeScript для пользовательских событий
Возможно типизировать пользовательские события, расширяя интерфейс CustomEventMap
:
// events.d.ts
import 'vite/types/customEvent'
declare module 'vite/types/customEvent' {
interface CustomEventMap {
'custom:foo': { msg: string }
// 'event-key': payload
}
}