Vite использует собственный dev-сервер и механизм трансформации модулей во время разработки, однако на этапе production-сборки полностью опирается на Rollup. Именно поэтому большинство плагинов Vite строятся вокруг Rollup API и поддерживают значительную часть Rollup-хуков.
Плагин Vite представляет собой расширение Rollup-плагина. Многие хуки вызываются одинаково как в Rollup, так и в Vite, однако часть из них работает только во время сборки, а часть — и в dev-режиме.
Типичная структура плагина:
export default function myPlugin() {
return {
name: 'my-plugin',
buildStart() {
console.log('Сборка началась')
}
}
}
Подключение:
import { defineConfig } from 'vite'
import myPlugin from './plugins/my-plugin.js'
export default defineConfig({
plugins: [myPlugin()]
})
Vite поддерживает несколько групп Rollup-хуков:
| Категория | Назначение |
|---|---|
| Input hooks | Работа со входными файлами |
| Resolve hooks | Разрешение модулей |
| Load hooks | Загрузка содержимого |
| Transform hooks | Трансформация кода |
| Build hooks | Управление процессом сборки |
| Output hooks | Генерация выходных файлов |
| Close hooks | Завершение работы |
Не все Rollup-хуки поддерживаются одинаково. Некоторые работают только в production build, некоторые — только частично.
Хук options вызывается самым первым. Позволяет
модифицировать Rollup-конфигурацию до старта сборки.
export default function plugin() {
return {
name: 'options-plugin',
options(options) {
console.log(options.input)
return {
...options,
treeshake: false
}
}
}
}
Во время dev-сервера options практически не играет роли,
поскольку Vite не запускает полноценный Rollup bundling.
На production build хук работает полноценно.
Вызывается перед началом обработки графа модулей.
buildStart() {
console.log('Начало сборки')
}
buildStart() {
this.cache.set('startTime', Date.now())
}
buildStart() {
this.emitFile({
type: 'asset',
fileName: 'meta.json',
source: '{"version":"1.0"}'
})
}
buildStart() {
if (!process.env.API_URL) {
this.error('API_URL не определён')
}
}
Один из важнейших хуков Vite и Rollup.
Отвечает за определение пути модуля.
resolveId(source, importer) {
console.log(source)
console.log(importer)
}
Когда встречается импорт:
import foo from './foo.js'
Vite вызывает цепочку resolveId, пока какой-либо плагин
не вернёт результат.
resolveId(source) {
if (source === 'virtual:config') {
return '\0virtual:config'
}
}
Префикс \0 указывает Rollup, что модуль виртуальный.
resolveId(source) {
if (source.startsWith('@images')) {
return source.replace('@images', '/src/assets/images')
}
}
resolveId(source) {
if (source.includes('fs')) {
this.error('fs запрещён')
}
}
Хук активно используется как в dev, так и в build.
Именно через resolveId реализуются:
Позволяет самостоятельно загрузить содержимое модуля.
load(id) {
console.log(id)
}
Один из самых распространённых сценариев.
resolveId(source) {
if (source === 'virtual:data') {
return '\0virtual:data'
}
},
load(id) {
if (id === '\0virtual:data') {
return `
export const message = 'hello'
`
}
}
Использование:
import { message } from 'virtual:data'
console.log(message)
load(id) {
if (id.endsWith('.generated.js')) {
return `
export default {
timestamp: ${Date.now()}
}
`
}
}
import fs from 'node:fs'
load(id) {
if (id.endsWith('.txt')) {
return `
export default ${JSON.stringify(
fs.readFileSync(id, 'utf-8')
)}
`
}
}
Главный хук трансформации кода.
transform(code, id) {
return code
}
transform(code, id) {
if (id.endsWith('.js')) {
return code.replace('__DEV__', 'true')
}
}
transform(code) {
return {
code: code.replace('foo', 'bar'),
map: null
}
}
transform(code) {
return {
code,
map: {
mappings: ''
}
}
}
Практически всегда требуется ограничение области работы.
transform(code, id) {
if (!id.endsWith('.vue')) {
return
}
return code
}
Часто применяется библиотека MagicString.
import MagicString from 'magic-string'
transform(code) {
const s = new MagicString(code)
s.prepend('const injected = true;\n')
return {
code: s.toString(),
map: s.generateMap()
}
}
transform(code) {
const ast = this.parse(code)
console.log(ast)
return code
}
Хук используется чрезвычайно активно:
| Режим | Поддержка |
|---|---|
| Dev server | Да |
| Production build | Да |
| SSR | Да |
Через transform работают:
Вызывается после парсинга модуля.
moduleParsed(info) {
console.log(info.id)
}
{
id,
importedIds,
dynamicallyImportedIds,
ast
}
moduleParsed(info) {
console.log(info.importedIds)
}
В dev-режиме хук может работать ограниченно, поскольку Vite избегает полного AST-анализа ради производительности.
Во время production build поддержка полноценная.
Вызывается после завершения сборки.
buildEnd(error) {
if (error) {
console.error(error)
}
}
buildEnd() {
clearInterval(this.timer)
}
buildEnd() {
console.log('Сборка завершена')
}
Output hooks работают только при production build.
Во время dev-server они не вызываются.
Позволяет изменить output-конфигурацию.
outputOptions(options) {
return {
...options,
sourcemap: true
}
}
Вызывается перед генерацией output.
renderStart() {
console.log('Рендер output')
}
Добавляют код в bundle.
banner() {
return '/* build banner */'
}
Результат:
/* build banner */
console.log('app')
footer() {
return '/* footer */'
}
Добавляет код внутрь bundle в начало.
intro() {
return 'const BUILD = true;'
}
Добавляет код в конец bundle.
outro() {
return 'console.log("finish")'
}
Позволяет изменить итоговый chunk.
renderChunk(code, chunk) {
return code
}
renderChunk(code) {
return code.replace(/\s+/g, ' ')
}
renderChunk(code) {
return `
const VERSION = '1.0';
${code}
`
}
renderChunk(code, chunk) {
console.log(chunk.fileName)
return code
}
Один из самых мощных output-хуков.
Позволяет управлять всем bundle.
generateBundle(options, bundle) {
console.log(bundle)
}
{
'index.js': ChunkInfo,
'style.css': AssetInfo
}
generateBundle(options, bundle) {
delete bundle['debug.js']
}
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'stats.json',
source: '{"ok":true}'
})
}
generateBundle(options, bundle) {
for (const file in bundle) {
const item = bundle[file]
if (item.type === 'asset') {
item.source = String(item.source).toUpperCase()
}
}
}
Вызывается после записи файлов на диск.
writeBundle() {
console.log('Файлы записаны')
}
import fs from 'node:fs'
writeBundle() {
fs.copyFileSync(
'./extra.txt',
'./dist/extra.txt'
)
}
writeBundle() {
console.log('deploy started')
}
Последний lifecycle-хук.
closeBundle() {
console.log('bundler closed')
}
| buildEnd | closeBundle |
|---|---|
| После сборки | После полного завершения |
| Может вызываться при ошибке | Финальный этап |
| Нет гарантии записи файлов | Файлы уже готовы |
Наиболее типичная схема работы Vite-плагинов:
export default function plugin() {
return {
name: 'virtual-module',
resolveId(source) {
if (source === 'virtual:env') {
return '\0virtual:env'
}
},
load(id) {
if (id === '\0virtual:env') {
return `
export const mode = 'development'
`
}
},
transform(code) {
return code
}
}
}
Цепочка выглядит так:
import → resolveId → load → transform
Во всех Rollup-хуках доступен специальный контекст
this.
Создание файлов:
this.emitFile({
type: 'asset',
fileName: 'meta.json',
source: '{}'
})
Генерация ошибки:
this.error('Build failed')
Предупреждение:
this.warn('Deprecated API')
Парсинг AST:
const ast = this.parse(code)
Добавление файлов в watcher:
this.addWatchFile('./config.json')
Типичная последовательность production build:
options
buildStart
resolveId
load
transform
moduleParsed
buildEnd
outputOptions
renderStart
renderChunk
generateBundle
writeBundle
closeBundle
Во время dev-server цепочка значительно сокращается:
resolveId
load
transform
Vite использует:
Поэтому многие Rollup output hooks отсутствуют.
Во время production build запускается полноценный Rollup pipeline.
Доступны:
Большинство Rollup-плагинов совместимы с Vite:
import legacy from '@rollup/plugin-legacy'
export default {
plugins: [legacy()]
}
Однако возможны ограничения:
| Возможность | Dev |
|---|---|
| resolveId | Да |
| load | Да |
| transform | Да |
| renderChunk | Нет |
| generateBundle | Нет |
| writeBundle | Нет |
Vite расширяет Rollup-плагины собственным механизмом
enforce.
{
name: 'pre-plugin',
enforce: 'pre'
}
Запускается раньше стандартных плагинов.
{
name: 'post-plugin',
enforce: 'post'
}
Запускается после остальных.
pre → normal → post
Это критически важно для:
Vite позволяет ограничивать выполнение плагинов.
{
apply: 'build'
}
{
apply: 'serve'
}
{
apply(config, env) {
return env.mode === 'production'
}
}
| Хук | Популярность |
|---|---|
| resolveId | Очень высокая |
| load | Очень высокая |
| transform | Максимальная |
| configureServer | Очень высокая |
| generateBundle | Высокая |
| renderChunk | Высокая |
Помимо Rollup API, Vite предоставляет собственные хуки:
| Хук | Назначение |
|---|---|
| config | Изменение конфигурации |
| configResolved | Доступ к финальному config |
| configureServer | Настройка dev server |
| handleHotUpdate | Управление HMR |
| transformIndexHtml | Изменение HTML |
Они дополняют Rollup lifecycle, формируя гибридную архитектуру Vite-плагинов.