Кастомный HTML-трансформ через transformIndexHtml
## Механизм `transformIndexHtml` в архитектуре Vite
В Vite HTML рассматривается как полноценная точка входа в приложение. В отличие от классических бандлеров, где HTML является лишь шаблоном для вставки итоговых бандлов, Vite обрабатывает HTML через цепочку плагинов, позволяя модифицировать его на уровне разработки и сборки. Центральным механизмом кастомизации HTML выступает хук `transformIndexHtml`.
Этот хук является частью плагинной системы Vite и позволяет перехватывать и преобразовывать содержимое HTML до его отправки в браузер или записи в билд-выход. Он интегрирован в жизненный цикл обработки index.html и может работать как в dev-режиме, так и в production-сборке.
---
## Базовая модель обработки HTML в Vite
Vite воспринимает `index.html` как модуль, который проходит через несколько стадий:
1. Загрузка исходного HTML
2. Применение плагинов через `transformIndexHtml`
3. Обработка специальных директив (`%ENV%`, `@vite/client`, `type="module"` скрипты)
4. Инжект HMR-клиента в dev-режиме
5. Финальная сборка HTML
На каждом этапе плагины могут вмешиваться в структуру документа.
---
## Сигнатура и режимы работы `transformIndexHtml`
Хук может быть объявлен в нескольких формах:
* синхронная функция
* асинхронная функция
* объект с методами `transformIndexHtml` и `enforce`
### Базовая сигнатура
```js
export default function myPlugin() {
return {
name: 'my-plugin',
transformIndexHtml(html, ctx) {
return html
}
}
}
```
### Асинхронный вариант
```js
transformIndexHtml: async (html, ctx) => {
const modified = await someAsyncProcessing(html)
return modified
}
```
---
## Контекст выполнения (`ctx`)
Вторым параметром передаётся контекст, содержащий информацию о текущем этапе обработки:
```ts
interface IndexHtmlTransformContext {
path: string
filename: string
server?: ViteDevServer
bundle?: Record
chunk?: any
}
```
### Основные поля:
* **path** — путь к HTML файлу
* **filename** — абсолютный путь на файловой системе
* **server** — доступен в dev-режиме, позволяет взаимодействовать с Vite dev server
* **bundle** — доступен в build-режиме, содержит собранные ассеты
* **chunk** — информация о текущем чанке (в некоторых сценариях SSR или продвинутых сборках)
Контекст позволяет реализовывать поведение, зависящее от окружения.
---
## Форматы возврата результата
`transformIndexHtml` поддерживает несколько типов возврата.
### 1. Строка HTML
Самый простой вариант:
```js
transformIndexHtml(html) {
return html.replace('', '')
}
```
### 2. Массив тегов (HTML transform pipeline)
Vite поддерживает структурированное добавление тегов:
```js
transformIndexHtml() {
return [
{
tag: 'script',
attrs: { src: '/inject.js' },
injectTo: 'body'
}
]
}
```
### 3. Комбинированный подход
Можно вернуть как строку, так и массив, но чаще выбирается один формат для предсказуемости пайплайна.
---
## Модель инъекций HTML-тегов
Vite использует внутреннюю систему размещения тегов:
* `head-prepend`
* `head`
* `body-prepend`
* `body`
* `body-append`
Пример:
```js
transformIndexHtml() {
return [
{
tag: 'meta',
attrs: { charset: 'UTF-8' },
injectTo: 'head-prepend'
},
{
tag: 'script',
attrs: { type: 'module', src: '/main.js' },
injectTo: 'body'
}
]
}
```
Порядок критичен: `head-prepend` выполняется раньше остальных вставок в ``.
---
## Фазы выполнения transformIndexHtml
Хук может выполняться в разных фазах pipeline:
### 1. Pre-transform
Перед основными преобразованиями HTML:
```js
enforce: 'pre'
```
Используется для:
* удаления лишних тегов
* подготовки шаблонов
* подмены переменных
---
### 2. Normal transform
Стандартная стадия. Используется чаще всего.
---
### 3. Post-transform
```js
enforce: 'post'
```
Применяется после всех остальных плагинов. Полезно для:
* финальной модификации
* инжекта аналитики
* корректировки уже собранного HTML
---
## Пример полного плагина
```js
export default function htmlPlugin() {
return {
name: 'html-injector',
transformIndexHtml: {
enforce: 'pre',
transform(html, ctx) {
const isDev = !!ctx.server
if (isDev) {
return [
{
tag: 'script',
attrs: {
src: '/dev-only.js'
},
injectTo: 'body'
}
]
}
return [
{
tag: 'script',
attrs: {
src: '/analytics.js'
},
injectTo: 'head'
}
]
}
}
}
}
```
---
## Работа с шаблонами HTML
Vite не ограничивает HTML как статический файл. Через `transformIndexHtml` можно реализовать полноценный шаблонизатор.
### Пример подстановки переменных
```js
transformIndexHtml(html) {
return html.replaceAll('%APP_NAME%', 'My Vite App')
}
```
### Более сложный вариант
```js
transformIndexHtml(html, ctx) {
const mode = ctx.server ? 'development' : 'production'
return html.replace('%MODE%', mode)
}
```
---
## Интеграция с dev-server
В режиме разработки `server` в контексте позволяет:
* подписываться на WebSocket события
* проверять конфигурацию Vite
* динамически менять HTML в зависимости от HMR состояния
Пример:
```js
transformIndexHtml(html, ctx) {
if (ctx.server) {
ctx.server.ws.on('connection', () => {
console.log('HMR connected')
})
}
return html
}
```
---
## Взаимодействие с build pipeline
В production-режиме доступно поле `bundle`, содержащее итоговые ассеты:
```js
transformIndexHtml(html, ctx) {
if (ctx.bundle) {
const scripts = Object.keys(ctx.bundle)
.filter(file => file.endsWith('.js'))
return html.replace(
'',
scripts.map(s => ``).join('') + '