Система плагинов — центральный элемент архитектуры Vite. Именно через плагины реализуются:
Архитектура Vite строится вокруг идеи «development server + production bundler», поэтому система плагинов должна одинаково эффективно работать:
Главная особенность заключается в том, что Vite не создаёт собственную полностью отдельную экосистему плагинов. Вместо этого он использует совместимость с плагинами Rollup, расширяя их дополнительными хуками и dev-server API.
Архитектура Vite-плагинов состоит из нескольких уровней:
Каждый плагин представляет собой объект с набором хуков.
Простейшая структура:
export default function myPlugin() {
return {
name: 'my-plugin'
}
}
Плагин регистрируется через plugins в
vite.config.js:
import { defineConfig } from 'vite'
import myPlugin from './plugins/myPlugin.js'
export default defineConfig({
plugins: [myPlugin()]
})
Плагин проходит через несколько фаз:
configХук config позволяет изменять пользовательскую
конфигурацию Vite до её окончательной нормализации.
export default function configPlugin() {
return {
name: 'config-plugin',
config(config, env) {
return {
define: {
__DEVTOOLS__: true
}
}
}
}
}
Параметры:
| Параметр | Описание |
|---|---|
config |
Исходная конфигурация |
env |
Информация о режиме |
Объект env:
{
command: 'serve',
mode: 'development'
}
configResolvedВызывается после полной нормализации конфигурации.
export default function resolvedPlugin() {
let resolvedConfig
return {
name: 'resolved-plugin',
configResolved(config) {
resolvedConfig = config
}
}
}
Этот хук особенно важен для:
Во время запуска vite dev создаётся внутренний
HTTP-сервер.
Плагины могут вмешиваться в его работу через специальные hooks.
configureServerПозволяет получить доступ к объекту dev server.
export default function serverPlugin() {
return {
name: 'server-plugin',
configureServer(server) {
server.middlewares.use((req, res, next) => {
console.log(req.url)
next()
})
}
}
}
Объект server содержит:
| Поле | Назначение |
|---|---|
middlewares |
Connect middleware |
moduleGraph |
Граф модулей |
watcher |
Chokidar watcher |
ws |
WebSocket HMR |
pluginContainer |
Контейнер плагинов |
Внутри Vite используется стек middleware на основе Connect.
Схема обработки запроса:
HTTP Request
↓
Connect Middleware
↓
Vite Plugin Pipeline
↓
Transform Pipeline
↓
Response
Плагины могут:
Пример API endpoint:
export default function apiPlugin() {
return {
name: 'api-plugin',
configureServer(server) {
server.middlewares.use('/api/hello', (req, res) => {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({
message: 'hello'
}))
})
}
}
}
Внутри Vite создаётся Plugin Container.
Он отвечает за:
Упрощённая схема:
Plugin Container
├── resolveId
├── load
├── transform
├── handleHotUpdate
└── generateBundle
resolveIdОтвечает за определение пути модуля.
export default function resolvePlugin() {
return {
name: 'resolve-plugin',
resolveId(source) {
if (source === 'virtual:config') {
return '\0virtual:config'
}
}
}
}
source — импортируемый путь:
import config from 'virtual:config'
Одно из важнейших архитектурных решений Vite.
Виртуальный модуль:
resolveId + load.export default function virtualPlugin() {
const virtualId = 'virtual:env'
const resolvedId = '\0' + virtualId
return {
name: 'virtual-plugin',
resolveId(id) {
if (id === virtualId) {
return resolvedId
}
},
load(id) {
if (id === resolvedId) {
return `
export const MODE = "development"
`
}
}
}
}
Использование:
import { MODE } from 'virtual:env'
loadИспользуется для загрузки содержимого модуля.
load(id) {
if (id.endsWith('.txt')) {
return 'export default "text file"'
}
}
Хук может:
Transform pipeline — ядро обработки модулей.
Каждый модуль проходит через последовательность transform hooks.
Схема:
Source File
↓
resolveId
↓
load
↓
transform #1
↓
transform #2
↓
transform #3
↓
Browser
transformГлавный hook Vite-плагинов.
transform(code, id) {
if (id.endsWith('.js')) {
return {
code: code.replace('__VERSION__', '1.0.0'),
map: null
}
}
}
Hook может возвращать:
return code
или:
return {
code,
map
}
Source maps критически важны для:
Плагины выполняются по порядку.
plugins: [
pluginA(),
pluginB(),
pluginC()
]
Pipeline:
module.js
↓
pluginA.transform()
↓
pluginB.transform()
↓
pluginC.transform()
Результат каждого transform становится входом следующего.
Порядок можно контролировать через enforce.
{
name: 'pre-plugin',
enforce: 'pre'
}
Варианты:
| Значение | Назначение |
|---|---|
pre |
Выполнить раньше |
post |
Выполнить позже |
Pipeline:
pre plugins
↓
normal plugins
↓
post plugins
Почти все плагины используют фильтрацию файлов.
if (!id.endsWith('.vue')) {
return
}
Часто используется createFilter из:
import { createFilter } from '@rollup/pluginutils'
const filter = createFilter(
['**/*.js'],
['node_modules/**']
)
Во время dev-режима Vite строит граф зависимостей модулей.
App.vue
├── main.js
├── styles.css
└── api.js
Module Graph используется для:
При изменении файла:
File Changed
↓
Watcher Event
↓
Module Graph Invalidate
↓
Affected Modules
↓
HMR Update
Hot Module Replacement — один из ключевых компонентов Vite.
Плагин может управлять HMR через:
handleHotUpdate(ctx) {
}
handleHotUpdateexport default function hmrPlugin() {
return {
name: 'hmr-plugin',
handleHotUpdate(ctx) {
console.log(ctx.file)
}
}
}
Объект ctx:
| Поле | Назначение |
|---|---|
file |
Изменённый файл |
modules |
Затронутые модули |
server |
Dev server |
timestamp |
Время обновления |
Vite использует WebSocket-соединение между браузером и dev server.
Схема:
File Change
↓
Watcher
↓
Plugin Hook
↓
Module Graph
↓
WebSocket Message
↓
Browser Update
configureServer(server) {
server.ws.send({
type: 'custom',
event: 'my:event',
data: {
updated: true
}
})
}
На клиенте:
if (import.meta.hot) {
import.meta.hot.on('my:event', data => {
console.log(data)
})
}
Vite поддерживает SSR через ту же систему плагинов.
Плагин может определять режим:
transform(code, id, options) {
if (options?.ssr) {
// SSR transform
}
}
Архитектурно Vite разделён на две части:
| Режим | Основа |
|---|---|
| Dev | Native ESM + Vite |
| Build | Rollup |
Во время production build Vite использует Rollup pipeline.
Это позволяет:
Поддерживаются почти все основные Rollup hooks:
| Hook | Назначение |
|---|---|
resolveId |
Разрешение модулей |
load |
Загрузка кода |
transform |
Трансформация |
buildStart |
Начало сборки |
buildEnd |
Конец сборки |
generateBundle |
Генерация bundle |
writeBundle |
Запись файлов |
buildStartbuildStart() {
console.log('build started')
}
generateBundleПозволяет изменять bundle перед записью.
generateBundle(options, bundle) {
for (const file in bundle) {
console.log(file)
}
}
writeBundleВызывается после записи файлов.
writeBundle() {
console.log('bundle written')
}
Vite использует отдельный этап dependency optimization.
Для этого применяется esbuild.
Схема:
Dependencies
↓
esbuild pre-bundle
↓
Optimized cache
↓
Dev Server
Это ускоряет:
Vite анализирует imports после transform pipeline.
Пример:
import { ref } from 'vue'
После анализа:
import { ref } from '/node_modules/.vite/vue.js'
Официальный плагин:
реализует:
App.vue
↓
parse SFC
↓
template transform
↓
script transform
↓
style transform
↓
HMR integration
Официальный React-плагин:
добавляет:
React Fast Refresh работает через:
Внутри hooks доступен специальный context.
transform(code, id) {
this.warn('warning')
}
Основные методы:
| Метод | Назначение |
|---|---|
this.warn() |
Предупреждение |
this.error() |
Ошибка |
this.emitFile() |
Генерация файла |
this.resolve() |
Resolve module |
this.emitFile({
type: 'asset',
fileName: 'info.txt',
source: 'generated'
})
Vite сам использует множество встроенных плагинов:
| Плагин | Назначение |
|---|---|
| Alias plugin | alias resolution |
| CSS plugin | CSS transforms |
| Asset plugin | static assets |
| HTML plugin | index.html transforms |
| Import analysis | ESM analysis |
Vite умеет трансформировать index.html.
transformIndexHtml(html) {
return html.replace(
'</head>',
'<script src="/debug.js"></script></head>'
)
}
CSS обрабатывается отдельным pipeline.
Поддерживаются:
Pipeline:
CSS File
↓
Preprocessor
↓
PostCSS
↓
CSS Modules
↓
HMR
↓
Browser
Плагин может работать только в нужном режиме.
apply: 'serve'
или:
apply: 'build'
Также поддерживается функция:
apply(config, env) {
return env.mode === 'development'
}
Плагины могут комбинироваться.
export default function composedPlugin() {
return [
pluginA(),
pluginB()
]
}
Большинство hooks поддерживают async.
async transform(code, id) {
const result = await compile(code)
return {
code: result.code,
map: result.map
}
}
Плагин может выбрасывать ошибки:
this.error('Compilation failed')
Vite отображает:
Наиболее дорогие операции:
| Операция | Стоимость |
|---|---|
| AST parsing | Высокая |
| Babel transform | Высокая |
| Source map merge | Высокая |
| File system access | Средняя |
| Regex replace | Низкая |
Многие плагины используют собственный cache layer.
const cache = new Map()
Типичные стратегии:
Сложные плагины работают через AST.
Популярные инструменты:
| Инструмент | Назначение |
|---|---|
| Babel | JS transform |
| Acorn | Parsing |
| ESTree | AST format |
| MagicString | Code mutation |
Широко используется внутри Vite-плагинов.
import MagicString from 'magic-string'
const s = new MagicString(code)
s.prepend('const DEV = true')
return {
code: s.toString(),
map: s.generateMap()
}
Source maps критически важны в архитектуре Vite.
Без них ломаются:
Один и тот же плагин работает:
Vite получает доступ к тысячам существующих плагинов.
Каждый плагин отвечает за отдельный pipeline stage:
Новые возможности добавляются без изменения ядра Vite.
Архитектура минимизирует:
plugin/
├── index.js
├── transform.js
├── runtime.js
├── utils.js
├── cache.js
├── hmr.js
└── types.d.ts
Несмотря на гибкость, система плагинов имеет ограничения:
| Ограничение | Причина |
|---|---|
| Разный pipeline dev/build | Native ESM vs Rollup |
| HMR complexity | Module graph invalidation |
| Source map overhead | merge operations |
| Plugin ordering issues | transform dependencies |
| SSR edge cases | server/client divergence |
Архитектура плагинов постепенно развивается в направлениях: