Система плагинов в Vite является центральным механизмом расширения сборщика. Через плагины реализуются:
Архитектурно система плагинов Vite построена поверх плагинной модели Rollup, однако дополнительно включает собственные Vite-специфичные хуки и поведение dev-сервера.
Во время запуска Vite формирует внутренний конвейер обработки модулей.
Каждый модуль проходит через набор хуков:
resolveIdloadtransformgenerateBundleСхема обработки выглядит следующим образом:
Импорт модуля
↓
resolveId()
↓
load()
↓
transform()
↓
dev server / bundle
↓
generateBundle()
Каждый плагин может:
Плагины подключаются через массив plugins в конфигурации
Vite.
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue()
]
})
Плагин обычно представляет собой функцию, возвращающую объект конфигурации плагина.
Минимальный плагин выглядит так:
export default function myPlugin() {
return {
name: 'my-plugin'
}
}
Полноценный плагин включает хуки жизненного цикла:
export default function myPlugin() {
return {
name: 'my-plugin',
config(config) {
console.log(config)
},
resolveId(id) {
if (id === 'virtual:module') {
return id
}
},
load(id) {
if (id === 'virtual:module') {
return 'export const msg = "Hello"'
}
},
transform(code, id) {
if (id.endsWith('.js')) {
return code.replace('__DEV__', 'true')
}
}
}
}
Поле name является обязательным.
{
name: 'custom-plugin'
}
Имя используется:
Рекомендуется использовать уникальные префиксы:
name: 'vite-plugin-custom'
Работают во время dev-сервера.
configureServer(server) {
console.log(server)
}
Работают только во время production-сборки.
apply: 'build'
Работают только при vite dev.
apply: 'serve'
Работают и в dev, и в build.
{
name: 'universal-plugin'
}
Поле apply определяет режим выполнения.
{
apply: 'serve'
}
{
apply: 'build'
}
{
apply(config, env) {
return env.mode === 'production'
}
}
Позволяет управлять порядком выполнения.
Выполняется раньше остальных.
{
enforce: 'pre'
}
Выполняется после остальных.
{
enforce: 'post'
}
Общий порядок:
pre plugins
↓
normal plugins
↓
post plugins
Внутри каждой группы плагины выполняются последовательно.
Позволяет модифицировать конфигурацию Vite.
config(config, env) {
return {
define: {
__APP_VERSION__: '"1.0.0"'
}
}
}
Вызывается после финального объединения конфигурации.
configResolved(resolvedConfig) {
console.log(resolvedConfig.root)
}
Этот хук часто используется для:
Даёт доступ к dev-серверу.
configureServer(server) {
server.middlewares.use((req, res, next) => {
console.log(req.url)
next()
})
}
Vite использует middleware-подход, аналогичный Express.
Можно создавать собственные обработчики:
configureServer(server) {
server.middlewares.use('/api/test', (req, res) => {
res.end('Hello')
})
}
Позволяет изменять HTML перед отправкой браузеру.
transformIndexHtml(html) {
return html.replace(
'</head>',
'<script src="/test.js"></script></head>'
)
}
Можно возвращать массив тегов:
transformIndexHtml() {
return [
{
tag: 'script',
attrs: {
src: '/analytics.js'
},
injectTo: 'head'
}
]
}
Отвечает за резолв импортов.
resolveId(source) {
if (source === 'virtual:data') {
return '\0virtual:data'
}
}
Виртуальный модуль не существует физически на диске.
resolveId(id) {
if (id === 'virtual:env') {
return '\0virtual:env'
}
}
Загрузка содержимого:
load(id) {
if (id === '\0virtual:env') {
return `
export const MODE = 'development'
`
}
}
Использование:
import { MODE } from 'virtual:env'
Символ \0 сообщает Rollup и Vite, что модуль является
внутренним виртуальным модулем.
Без него модуль может участвовать в обычном файловом резолвинге.
Отвечает за загрузку содержимого модуля.
load(id) {
if (id.endsWith('.txt')) {
return 'export default "text file"'
}
}
Самый важный хук большинства плагинов.
Позволяет изменять исходный код.
transform(code, id) {
if (id.endsWith('.js')) {
return code.replace(/__VERSION__/g, '1.0.0')
}
}
Плагин может возвращать sourcemap.
transform(code) {
return {
code,
map: null
}
}
Все хуки могут быть асинхронными.
async transform(code) {
const result = await compile(code)
return {
code: result.code,
map: result.map
}
}
Частая практика — ограничивать обработку по расширению.
transform(code, id) {
if (!id.endsWith('.ts')) {
return
}
return compile(code)
}
const filter = /\.jsx?$/
transform(code, id) {
if (!filter.test(id)) {
return
}
return process(code)
}
Vite использует query-суффиксы:
Component.vue?vue&type=script
style.css?inline
Плагин должен учитывать это.
if (id.includes('?inline')) {
return
}
const [path, query] = id.split('?')
Используется для управления HMR.
handleHotUpdate(ctx) {
console.log(ctx.file)
}
Контекст содержит:
{
file,
server,
modules,
timestamp,
read
}
handleHotUpdate(ctx) {
ctx.server.ws.send({
type: 'full-reload'
})
}
handleHotUpdate() {
return []
}
Vite использует WebSocket для HMR.
Можно отправлять собственные события:
server.ws.send({
type: 'custom',
event: 'my:event',
data: {
message: 'upd ated'
}
})
Клиент:
if (import.meta.hot) {
import.meta.hot.on('my:event', (data) => {
console.log(data)
})
}
Вызывается перед началом сборки.
buildStart() {
console.log('build started')
}
buildEnd() {
console.log('build finished')
}
Позволяет изменять выходной bundle.
generateBundle(options, bundle) {
console.log(bundle)
}
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'meta.json',
source: JSON.stringify({
version: '1.0.0'
})
})
}
{
type: 'asset'
}
{
type: 'chunk'
}
generateBundle(options, bundle) {
delete bundle['old.js']
}
generateBundle(options, bundle) {
for (const file of Object.values(bundle)) {
if (file.type === 'chunk') {
file.code = file.code.replace(
/DEBUG/g,
'false'
)
}
}
}
Плагин может анализировать режим SSR.
transform(code, id, options) {
if (options?.ssr) {
return processSSR(code)
}
}
Плагины часто внедряют переменные окружения.
define: {
__API_URL__: JSON.stringify(process.env.API_URL)
}
import dotenv from 'dotenv'
dotenv.config()
import { transformAsync } from '@babel/core'
export default function babelPlugin() {
return {
name: 'babel-plugin',
async transform(code, id) {
if (!id.endsWith('.js')) {
return
}
const result = await transformAsync(code, {
presets: ['@babel/preset-env']
})
return {
code: result.code,
map: result.map
}
}
}
}
Vite активно использует esbuild для сверхбыстрой трансформации модулей.
Пример прямого использования:
import { transform } from 'esbuild'
async transform(code) {
const result = await transform(code, {
loader: 'ts'
})
return result
}
Плагины могут самостоятельно реализовывать кэш.
const cache = new Map()
transform(code, id) {
if (cache.has(id)) {
return cache.get(id)
}
const result = compile(code)
cache.se t(id, result)
return result
}
handleHotUpdate(ctx) {
cache.delete(ctx.file)
}
import fs from 'fs/promises'
async load(id) {
if (id.endsWith('.md')) {
return await fs.readFile(id, 'utf-8')
}
}
Простейший markdown-loader:
import fs from 'fs/promises'
import { marked } from 'marked'
export default function markdownPlugin() {
return {
name: 'markdown-plugin',
async load(id) {
if (!id.endsWith('.md')) {
return
}
const raw = await fs.readFile(id, 'utf-8')
const html = marked(raw)
return `
export default ${JSON.stringify(html)}
`
}
}
}
export default function apiPlugin() {
return {
name: 'api-plugin',
resolveId(id) {
if (id === 'virtual:api') {
return '\0virtual:api'
}
},
load(id) {
if (id === '\0virtual:api') {
return `
export async function getUsers() {
return fetch('/api/users')
}
`
}
}
}
}
В monorepo-проектах плагины часто:
Утилита из Rollup:
import { createFilter } from '@rollup/pluginutils'
const filter = createFilter(
['**/*.js'],
['node_modules/**']
)
transform(code, id) {
if (!filter(id)) {
return
}
return process(code)
}
Для корректной диагностики используется:
this.error('Compilation failed')
this.warn('Deprecated API')
configureServer(server) {
server.config.logger.info(
'Custom plugin enabled'
)
}
configureServer(server) {
server.watcher.on('change', (file) => {
console.log(file)
})
}
this.addWatchFile('config/custom.json')
config(config, env) {
console.log(env.mode)
}
config(config, env) {
console.log(env.command)
}
Значения:
serve
build
apply(config, env) {
return env.command === 'serve'
}
apply(config, env) {
return env.command === 'build'
}
Vite автоматически поддерживает PostCSS.
Можно подключать плагины:
export default {
css: {
postcss: {
plugins: [
require('autoprefixer')
]
}
}
}
import tailwindcss from 'tailwindcss'
export default {
css: {
postcss: {
plugins: [
tailwindcss()
]
}
}
}
Наиболее популярные официальные плагины:
| Плагин | Назначение |
|---|---|
@vitejs/plugin-vue |
Vue |
@vitejs/plugin-react |
React |
@vitejs/plugin-legacy |
Legacy browser support |
import react from '@vitejs/plugin-react'
export default {
plugins: [react()]
}
import vue from '@vitejs/plugin-vue'
export default {
plugins: [vue()]
}
import legacy from '@vitejs/plugin-legacy'
export default {
plugins: [
legacy({
targets: ['defaults', 'not IE 11']
})
]
}
Большинство Rollup-плагинов работает в Vite без изменений.
import replace from '@rollup/plugin-replace'
export default {
plugins: [
replace({
__TEST__: true
})
]
}
Некоторые Rollup-плагины:
Для диагностики часто используют:
console.log(id)
или:
debugger
Полезно логировать:
transform(code, id) {
console.log('transform:', id)
}
Медленные плагины обычно вызывают:
Основные подходы:
Для сложных преобразований используют AST.
Популярные библиотеки:
import MagicString from 'magic-string'
transform(code) {
const s = new MagicString(code)
s.prepend('const DEV = true;\n')
return {
code: s.toString(),
map: s.generateMap()
}
}
Без sourcemap:
Vite отслеживает импорт-граф и определяет:
Плагин может влиять на этот процесс через
handleHotUpdate.
Во время dev-режима:
Browser request
↓
Vite dev server
↓
Plugin pipeline
↓
Transformed module
↓
Browser
Во время build:
Entry
↓
Rollup graph
↓
Plugin hooks
↓
Chunks/assets
↓
Dist
Качественный Vite-плагин обычно:
plugin/
├── index.js
├── transform.js
├── runtime.js
├── utils.js
├── cache.js
└── constants.js
Проверяются:
transform(code, id) {
if (id.includes('node_modules')) {
return
}
}
return {
code,
map
}
Возникает при постоянной генерации файлов внутри watched-директорий.
Причины:
В экосистеме существует множество популярных решений:
vite-plugin-pagesvite-plugin-pwavite-plugin-svg-iconsvite-plugin-inspectvite-plugin-checkervite-plugin-compressionОни реализуют: