Plugin API
Плагины Vite расширяют интерфейс плагинов Rolldown несколькими дополнительными специфичными для Vite опциями. В результате вы можете написать плагин Vite один раз, и он будет работать как для разработки, так и для сборки.
Рекомендуется сначала ознакомиться с документацией по плагинам Rolldown, прежде чем читать разделы ниже.
Создание плагина
Vite стремится предлагать устоявшиеся паттерны из коробки, поэтому перед созданием нового плагина убедитесь, что вы вы ознакомились с разделом Возможности, чтобы понять, покрывают ли они ваши потребности. Также ознакомьтесь с доступными плагинами сообщества, как в виде совместимых плагинов Rollup, так и специфичных для Vite плагинов.
При создании плагина вы можете встроить его в ваш vite.config.js. Нет необходимости создавать для него новый пакет. Как только вы увидите, что плагин был полезен в ваших проектах, подумайте о том, чтобы поделиться им для помощи другим в экосистеме.
СОВЕТ
При обучении, отладке или написании плагинов мы рекомендуем включить vite-plugin-inspect в ваш проект. Он позволяет вам просматривать промежуточное состояние плагинов Vite. После установки вы можете посетить localhost:5173/__inspect/, чтобы просмотреть модули и стек трансформаций вашего проекта. Ознакомьтесь с инструкциями по установке в документации vite-plugin-inspect. 
Конвенции
Если плагин не использует специфичные для Vite хуки и может быть реализован как совместимый плагин Rolldown, то рекомендуется использовать соглашения об именовании плагинов Rolldown.
- Плагины Rolldown должны иметь чёткое название с префиксом
rolldown-plugin-. - Включите ключевые слова
rolldown-pluginиvite-pluginв полеkeywordspackage.json.
Это позволяет использовать плагин также в чистых проектах на базе Rolldown или Rollup.
Что касается плагинов, предназначенных только для Vite:
- Плагины Vite должны иметь понятное имя с префиксом
vite-plugin-. - Включите ключевое слово
vite-pluginв полеkeywordspackage.json. - Включите раздел в документации плагина, объясняющий, почему это плагин только для Vite (например, он использует специфические для Vite хуки).
Если ваш плагин будет работать только для определённого фреймворка, его имя должно быть включено в префикс:
- Префикс
vite-plugin-vue-для плагинов Vue - Префикс
vite-plugin-react-для плагинов React - Префикс
vite-plugin-svelte-для плагинов 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/Rolldown/Rollup в виде фабричной функции, которая возвращает фактический объект плагина. Функция может принимать параметры, что позволяет пользователям настраивать поведение плагина.
Преобразование пользовательских типов файлов
const fileRegex = /\.(my-file-ext)$/
export default function myPlugin() {
return {
name: 'transform-file',
transform: {
filter: {
id: fileRegex,
},
handler(src, id) {
return {
code: compileFileToJS(src),
map: null, // предоставьте карту источников, если она доступна
}
},
},
}
}Импорт виртуального файла
Виртуальные модули позволяют передавать информацию, полученную во время сборки, в исходные файлы с помощью обычного синтаксиса импорта ESM. Полное описание соглашения об именовании см. в разделе Соглашение для виртуальных модулей.
import { exactRegex } from '@rolldown/pluginutils'
export default function myPlugin() {
const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'my-plugin', // обязательно, будет отображаться в предупреждениях и ошибках
resolveId: {
filter: { id: exactRegex(virtualModuleId) },
handler() {
return resolvedVirtualModuleId
},
},
load: {
filter: { id: exactRegex(resolvedVirtualModuleId) },
handler() {
return `export const msg = "from virtual module"`
},
},
}
}Что позволяет импортировать модуль в JavaScript:
import { msg } from 'virtual:my-module'
console.log(msg)В Vite, поскольку \0 не является допустимым символом в URL импорта, виртуальный идентификатор \0{id} в режиме разработки в браузере преобразуется в /@id/__x00__{id}. Перед передачей в конвейер плагинов идентификатор декодируется обратно, поэтому в коде хуков плагинов это преобразование не видно.
Хуки Rolldown
Во время разработки dev-сервер Vite создает контейнер плагинов, который вызывает хуки сборки Rolldown точно так же, как это делает Rolldown.
Все хуки Rolldown являются хуками уровня окружения.
Следующие хуки вызываются один раз при запуске сервера:
Следующие хуки вызываются при каждом входящем запросе модуля:
Эти хуки также имеют расширенный параметр options с дополнительными свойствами, специфичными для Vite. Вы можете прочитать больше в документации по SSR.
Некоторые вызовы resolveId могут иметь значение importer в виде абсолютного пути к общему index.html в корне, так как не всегда возможно определить фактический импортёр из-за паттерна разработки без сборки пакета в сервере Vite. Для импортов, обрабатываемых в рамках разрешающего конвейера Vite, импортёр может отслеживаться на этапе анализа импорта, обеспечивая правильное значение importer.
Следующие хуки вызываются, когда сервер закрывается:
Обратите внимание, что хук moduleParsed не вызывается во время разработки, поскольку Vite избегает полного разбора AST для повышения производительности.
Хуки генерации вывода (за исключением closeBundle) не вызываются во время разработки.
Специфические хуки Vite
Плагины Vite также могут предоставлять хуки, которые служат специфическим для Vite целям. Эти хуки игнорируются Rollup.
config
Тип:
(config: UserConfig, env: { mode: 'build' | 'serve', command: string, isSsrBuild?: boolean, isPreview?: boolean }) => 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
Тип:
(config: ResolvedConfig) => void | Promise<void>Режим работы:
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-сервера (например, для доступа к WebSocket-серверу, наблюдателю за файловой системой или графу модулей). Этот хук также можно использовать для хранения экземпляра сервера для доступа в других хуках:
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('rolldown').OutputBundle chunk?: import('rolldown').OutputChunk originalUrl?: string }, ) => 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>Режим работы:
async,sequentialСмотрите также: 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 предоставляет дополнительные свойства в this.meta:
this.meta.viteVersion: Строка с текущей версией Vite (например,"8.0.0").
Определение Vite на базе Rolldown
this.meta.rolldownVersion доступно только для Vite, работающего на Rolldown (т. е. Vite 8+). Вы можете использовать это свойство, чтобы определить, работает ли текущий экземпляр Vite на базе Rolldown:
function versionCheckPlugin(): Plugin {
return {
name: 'version-check',
buildStart() {
if (this.meta.rolldownVersion) {
// Действуем только в случае, если Vite работает на Rolldown
} else {
// В противном случае (Vite на Rollup) делаем что-то другое
}
},
}
}Метаданные выходного бандла
Во время сборки Vite дополняет объекты вывода сборки Rolldown специфическим для Vite полем viteMetadata.
Оно доступно через:
RenderedChunk(например, в хукахrenderChunkиaugmentChunkHash)OutputChunkиOutputAsset(например, в хукахgenerateBundleиwriteBundle)
viteMetadata предоставляет:
viteMetadata.importedCss: Set<string>viteMetadata.importedAssets: Set<string>
Это полезно при написании плагинов, которым нужно проверить сгенерированные CSS-файлы и статические ресурсы, не полагаясь на build.manifest.
Пример:
function outputMetadataPlugin(): Plugin {
return {
name: 'output-metadata-plugin',
enforce: 'post',
generateBundle(_, bundle) {
for (const output of Object.values(bundle)) {
const css = output.viteMetadata?.importedCss
const assets = output.viteMetadata?.importedAssets
if (!css?.size && !assets?.size) continue
console.log(output.fileName, {
css: css ? [...css] : [],
assets: assets ? [...assets] : [],
})
}
},
}
}Порядок плагинов
Плагин Vite может дополнительно указать свойство enforce (аналогично загрузчикам webpack), чтобы настроить порядок его применения. Значение enforce может быть либо "pre", либо "post". Разрешённые плагины будут выполняться в следующем порядке:
- Алиасы
- Пользовательские плагины с
enforce: 'pre' - Ядро плагинов Vite
- Пользовательские плагины без значения enforce
- Плагины сборки Vite
- Пользовательские плагины с
enforce: 'post' - Постсборочные плагины Vite (минификация, манифест, отчетность)
Обратите внимание, что это работает отдельно от порядка хуков — они по-прежнему подчиняются своему атрибуту order, как обычно для хуков Rolldown.
Применение по условию
По умолчанию плагины вызываются как для сервера, так и для сборки. В случаях, когда плагин необходимо применять условно только во время сервера или сборки, используйте свойство apply, чтобы вызывать их только во время 'build' или 'serve':
function myPlugin() {
return {
name: 'build-only',
apply: 'build', // или 'serve'
}
}Функция также может быть использована для более точного контроля:
apply(config, { command }) {
// применяем только при сборке, но не для SSR
return command === 'build' && !config.build.ssr
}Совместимость плагинов Rolldown
Достаточное количество плагинов Rolldown / Rollup будет работать напрямую как плагины Vite (например, @rollup/plugin-alias или @rollup/plugin-json), но не все из них, так как некоторые хуки плагинов не имеют смысла в контексте dev-сервера без сборки пакета.
В общем, если плагин Rolldown / Rollup соответствует следующим критериям, он должен работать как плагин Vite:
- Не использует хук
moduleParsed. - Не зависит от специфичных для Rolldown опций, таких как
transform.inject. - У него нет сильной связи между хуками фазы сборки и хуками фазы вывода.
Если плагин Rolldown / Rollup имеет смысл только для фазы сборки, его можно указать в build.rolldownOptions.plugins. Он будет работать так же, как плагин Vite с enforce: 'post' и apply: 'build'.
Вы также можете дополнить существующий плагин Rolldown / Rollup свойствами, специфичными для Vite:
// vite.config.js
import example from 'rolldown-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.
Фильтры хуков
Rolldown ввёл функцию фильтрации хуков, чтобы уменьшить накладные расходы на взаимодействие между средами выполнения Rust и JavaScript. Эта функция позволяет плагинам указывать шаблоны, определяющие, когда должны вызываться хуки, улучшая производительность за счёт избежания ненужных вызовов.
Это также поддерживается в Rollup 4.38.0+ и Vite 6.3.0+. Чтобы сделать ваш плагин обратно совместимым со старыми версиями, убедитесь, что вы также запускаете фильтр внутри обработчиков хуков.
export default function myPlugin() {
const jsFileRegex = /\.js$/
return {
name: 'my-plugin',
// Пример: вызывать transform только для .js файлов
transform: {
filter: {
id: jsFileRegex,
},
handler(code, id) {
// Дополнительная проверка для обратной совместимости
if (!jsFileRegex.test(id)) return null
return {
code: transformCode(code),
map: null,
}
},
},
}
}СОВЕТ
@rolldown/pluginutils экспортирует некоторые утилиты для фильтров хуков, такие как exactRegex и prefixRegex. Они также переэкспортируются из rolldown/filter для удобства.
Сведения о карте импортов чанков
Экспериментальная функция
Эта функция является экспериментальной и в будущем может измениться.
Когда включён параметр build.chunkImportMap, инструкции импорта в сгенерированных чанках будут использовать уникальный идентификатор каждого чанка вместо пути к файлу.
Чтобы получить соответствие между идентификатором чанка и путём к файлу, можно обратиться к карте импортов, добавленной в сборку, в хуке generateBundle или writeBundle. Карта импортов получает имя, указанное в параметре build.rolldownOptions.experimental.chunkImportMap.fileName (по умолчанию — importmap.json).
function accessImportMap() {
let config: ResolvedConfig
return {
name: 'access-import-map',
configResolved(resolvedConfig) {
config = resolvedConfig
},
generateBundle(options, bundle) {
const chunkImportMap =
config.build.rolldownOptions.experimental?.chunkImportMap
if (chunkImportMap) {
const importMapFilename =
typeof chunkImportMap === 'object' && chunkImportMap.fileName
? chunkImportMap.fileName
: 'importmap.json'
const importMap = bundle[importMapFilename]! as OutputAsset
const mapping = JSON.parse(importMap.source).imports
console.log(mapping)
// { "./entry.hash1.js": "./entry.hash2.js" }
}
},
}
}Связь клиент-сервер
Начиная с 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, чтобы слушать события:
// сторона клиента
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
}
}