Система плагинов в Vite построена вокруг последовательной обработки модулей, HTML-файлов, CSS, виртуальных ресурсов и стадий сборки. Каждый плагин способен вмешиваться в жизненный цикл проекта, изменяя код, добавляя виртуальные модули, подменяя импорты, трансформируя ассеты или влияя на процесс сборки.
Поскольку одновременно могут использоваться десятки плагинов, порядок их выполнения становится критически важным. Неверная последовательность способна привести к:
Для управления последовательностью выполнения используется свойство
enforce.
Без дополнительных настроек плагины выполняются в порядке объявления
внутри массива plugins.
Пример:
import pluginA from './plugin-a'
import pluginB from './plugin-b'
import pluginC from './plugin-c'
export default {
plugins: [
pluginA(),
pluginB(),
pluginC()
]
}
Последовательность вызова hook-ов:
pluginApluginBpluginCЭто относится ко многим hook-ам:
resolveIdloadtransformhandleHotUpdateconfigureServerОднако часть hook-ов выполняется в обратном порядке. Особенно это касается post-processing стадий.
Свойство enforce позволяет сместить плагин в одну из
специальных фаз:
{
name: 'example-plugin',
enforce: 'pre'
}
Допустимые значения:
'enforce: "pre"'
'enforce: "post"'
Если enforce не указан — плагин относится к обычной фазе
(normal).
После обработки enforce все плагины делятся на три
блока:
prenormalpostИтоговый порядок:
pre → normal → post
Плагины с enforce: 'pre' выполняются раньше
остальных.
Пример:
{
name: 'pre-plugin',
enforce: 'pre'
}
Типичные задачи pre-плагинов:
plugins: [
pluginA(),
{
...pluginB(),
enforce: 'pre'
},
pluginC()
]
Фактический порядок:
pluginB → pluginA → pluginC
Несмотря на расположение в массиве, pluginB поднимается
вверх.
Плагины с enforce: 'post' выполняются после
остальных.
Пример:
{
name: 'post-plugin',
enforce: 'post'
}
Типичные задачи post-плагинов:
plugins: [
pluginA(),
{
...pluginB(),
enforce: 'post'
},
pluginC()
]
Итог:
pluginA → pluginC → pluginB
Внутри одной группы (pre, normal,
post) сохраняется порядок объявления.
Пример:
plugins: [
{
name: 'a',
enforce: 'pre'
},
{
name: 'b',
enforce: 'pre'
},
pluginC()
]
Порядок:
a → b → pluginC
Упрощённая схема:
1. Все pre
2. Все normal
3. Все post
После этого формируется единая цепочка hook-ов.
Система плагинов Vite построена поверх Rollup, поэтому большинство hook-ов совместимы с Rollup API.
Но Vite добавляет собственную фазовую модель:
pre / normal / post
В самом Rollup такого механизма нет.
Наиболее показательный hook — transform.
Пример:
transform(code, id) {
return code
}
При наличии нескольких плагинов цепочка может выглядеть так:
pre-transform
↓
normal-transform
↓
post-transform
Каждый следующий плагин получает результат предыдущего.
function pluginA() {
return {
name: 'plugin-a',
transform(code) {
return code.replace('__VALUE__', 'A')
}
}
}
function pluginB() {
return {
name: 'plugin-b',
enforce: 'post',
transform(code) {
return code.replace('A', 'B')
}
}
}
console.log('__VALUE__')
console.log('B')
Последовательность:
pluginA → pluginB
Hook resolveId особенно чувствителен к очередности.
Пример:
resolveId(source) {
if (source === 'virtual:data') {
return '\0virtual:data'
}
}
Если другой плагин изменит import раньше:
import 'virtual:data'
то текущий плагин может никогда не получить нужный
source.
Поэтому плагины виртуальных модулей часто используют:
enforce: 'pre'
Alias-резолвинг также зависит от очередности.
Пример:
resolveId(id) {
if (id.startsWith('@')) {
return id.replace('@', '/src')
}
}
Если другой плагин изменит путь раньше:
@/components/Button
↓
virtual:@/components/Button
alias уже не сработает.
Многие официальные плагины Vite используют enforce.
Например:
Это необходимо для строгого контроля цепочки трансформаций.
Частая проблема — несколько плагинов изменяют один и тот же код.
Пример:
Plugin A:
import.meta.env.API_URL
↓
process.env.API_URL
Plugin B:
import.meta.env.*
Если порядок неверный:
Plugin B не увидит import.meta.env
В подобных случаях один из плагинов должен быть pre.
Иногда один плагин выполняет ранний анализ, а другой — финальную обработку.
Пример:
pre-plugin:
анализирует imports
normal-plugin:
трансформирует код
post-plugin:
генерирует отчет
Такая архитектура встречается:
Некоторые hook-ы работают в прямом порядке:
A → B → C
Некоторые — в обратном:
C → B → A
Это зависит от типа hook-а.
Hook-ы типа transform выполняются последовательно:
plugin1
↓
plugin2
↓
plugin3
Каждый получает изменённый код.
Часть hook-ов может вызываться независимо:
buildStart
generateBundle
closeBundle
Но даже там порядок регистрации остаётся важным.
Vite поддерживает hook:
transformIndexHtml(html) {
return html
}
Он тоже учитывает enforce.
Пример:
{
enforce: 'pre',
transformIndexHtml(html) {
return html.replace('%TITLE%', 'App')
}
}
Post-фаза может затем дополнительно модифицировать HTML:
{
enforce: 'post',
transformIndexHtml(html) {
return html + '<script src="/debug.js"></script>'
}
}
CSS-пайплайн в Vite активно зависит от порядка.
Типичная цепочка:
pre:
Sass/Less/Stylus
normal:
CSS Modules
post:
autoprefixer/minification
Неверная последовательность приводит к:
Hook:
handleHotUpdate(ctx)
тоже зависит от порядка.
Ранние плагины могут:
Поздние плагины получают уже модифицированное состояние.
SSR-плагины часто используют pre.
Причина:
SSR transform должен происходить
до browser-specific transform
Иначе серверный рендеринг может получить браузерный код.
Во время pre-bundling через esbuild некоторые плагины должны выполняться раньше анализа зависимостей.
Пример:
virtual modules
alias plugins
framework resolvers
Именно поэтому многие framework plugins работают как
pre.
Для анализа цепочки удобно использовать логирование.
Пример:
function debugPlugin(name) {
return {
name,
transform(code, id) {
console.log(name, id)
return code
}
}
}
Можно вывести итоговую конфигурацию:
configResolved(config) {
console.log(config.plugins)
}
Это помогает увидеть:
Сам Vite регистрирует множество внутренних плагинов:
Они тоже имеют собственные фазы.
Поэтому пользовательские плагины могут неожиданно выполняться:
pre подходит для:
post подходит для:
Если плагин:
то достаточно обычной фазы normal.
Проблема:
Все плагины становятся pre
Результат:
Неверный подход:
"Поставим pre — и всё заработает"
Иногда проблема связана:
Плагин должен корректно работать независимо от количества проходов.
Плохой пример:
code += '__INJECTED__'
При повторной обработке:
__INJECTED____INJECTED__
Порядок плагинов может усилить проблему.
Для сложных экосистем обычно используется схема:
pre:
резолвинг и анализ
normal:
основные трансформации
post:
финальная обработка
Такой подход делает pipeline предсказуемым и совместимым с экосистемой Vite и Rollup.