Vite 6 environment API: гайд
Vite 6 принёс Environment API — новый способ описывать сборку для нескольких сред (браузер, Node, Worker, edge). Раньше это решалось монолитной конфигурацией с условиями, что приводило к запутанным vite.config.ts на больших проектах. Теперь у каждой среды собственный конфиг, и сборщик аккуратно обслуживает их параллельно.
Расскажу, что это такое, как мигрировать и где это реально меняет жизнь.
Что такое environment
В Vite 6 каждая «среда» (environment) — это отдельная сборка с собственными настройками: формат модуля, target-окружение, набор плагинов. Стандартные имена: client, ssr, worker. Можно создавать свои.
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
environments: {
client: {
build: {
outDir: 'dist/client',
},
},
ssr: {
build: {
outDir: 'dist/server',
ssr: 'src/entry-server.ts',
},
},
edge: {
build: {
outDir: 'dist/edge',
rollupOptions: { input: 'src/entry-edge.ts' },
},
resolve: {
conditions: ['edge'],
},
},
},
});В этой конфигурации три отдельные сборки. Каждая со своими условиями разрешения зависимостей, форматами и плагинами.
Зачем это нужно
До Vite 6 типичный SSR-проект выглядел так:
// до 6.x
export default defineConfig({
build: {
rollupOptions: {
output: { entryFileNames: 'client.js' },
},
},
ssr: {
noExternal: ['react'],
},
// ... условия для разных режимов через if (process.env.SSR) ...
});На больших проектах конфиг разрастался до 200+ строк. С environments всё это разделено по логическим зонам.
Миграция
Если у тебя простой клиентский проект — миграция не нужна. Старый конфиг работает, environments активируются автоматически с дефолтным client.
Если у тебя SSR — стоит переехать на новую модель. Что нужно сделать:
- Перенеси содержимое
buildвenvironments.client.build. - Содержимое
ssr— вenvironments.ssr.build. - Параметры, специфичные для SSR (
noExternal,external) — вenvironments.ssr.resolve. - Запусти
vite build --environment ssrи убедись, что артефакт собирается, как раньше.
Скрипты обычно становятся такими:
{
"scripts": {
"build": "vite build && vite build --environment ssr",
"build:edge": "vite build --environment edge"
}
}Условные импорты
Environment API даёт возможность точно контролировать, какие модули попадают в какую сборку. Условие в resolve.conditions:
environments: {
client: {
resolve: { conditions: ['browser'] },
},
ssr: {
resolve: { conditions: ['node'] },
},
}Когда модуль в package.json имеет exports с разными условиями, Vite выберет правильное:
{
"exports": {
".": {
"browser": "./dist/browser.js",
"node": "./dist/node.js",
"default": "./dist/index.js"
}
}
}До этого подобные условия требовали внешних плагинов или ручных подмен. Сейчас — нативно.
Плагины и среды
Плагины могут указывать, в каких средах они активны:
import type { Plugin } from 'vite';
function myPlugin(): Plugin {
return {
name: 'my-plugin',
applyToEnvironment(env) {
return env.name === 'client';
},
transform(code, id) {
// ...
},
};
}Это особенно полезно для плагинов, которые делают разные вещи на сервере и клиенте, или для плагинов, которые в SSR должны быть отключены (например, тех, что инжектят CSS).
Кастомные среды
Самый интересный сценарий — собственные среды. Например, если ты собираешь два разных клиента (для основного приложения и для встраиваемого виджета):
environments: {
client: {
build: { rollupOptions: { input: 'src/main.tsx' } },
},
widget: {
build: {
outDir: 'dist/widget',
rollupOptions: {
input: 'src/widget.tsx',
output: { format: 'iife', name: 'MyWidget' },
},
},
},
}И в package.json:
{
"scripts": {
"build:all": "vite build && vite build --environment widget"
}
}На предыдущей мажорной версии для этого приходилось держать второй конфиг или хитро управлять process.env. С environments — это два блока в одном конфиге, и команда сразу понимает структуру сборки.
Dev-сервер с несколькими средами
В dev-режиме Vite поддерживает все среды одновременно. Это особенно удобно для SSR-проектов: один процесс держит и dev-сервер для клиента, и SSR-рендер. Нет нужды в отдельных портах или сложных перезапусках.
Если используешь Astro или SvelteKit — они уже под капотом интегрированы с Environment API, и тебе ничего делать не надо. Если самостоятельный SSR — добавь обработчик:
import express from 'express';
import { createServer } from 'vite';
const app = express();
const vite = await createServer({ server: { middlewareMode: true } });
app.use(vite.middlewares);
app.use(async (req, res) => {
const url = req.originalUrl;
const template = await vite.transformIndexHtml(url, baseHtml);
const { render } = await vite.ssrLoadModule('/src/entry-server.ts');
const appHtml = await render(url);
res.send(template.replace('<!--app-->', appHtml));
});
app.listen(3000);В 6.x этот код почти не отличается от 5.x, но под капотом Vite использует ту же модель сред — это даёт более предсказуемый dev-server.
Edge runtime
Если деплоишь в Cloudflare Workers, Vercel Edge или подобные — environment API сильно упрощает сборку. Заводишь среду edge с conditions: ['edge', 'worker', 'browser'], и она получает только подходящие версии модулей. Вес итогового бандла часто падает в полтора-два раза по сравнению с обычной SSR-сборкой.
Грабли при миграции
- Плагины из старых версий могут не поддерживать
applyToEnvironment. Большинство мейнстримных уже обновлены, мелкие — могут давать предупреждения. На моём проекте с миграции один такой плагин пришлось заменить на форк. - Условия в
resolve.conditionsжёстче приводятся к версии. Если ты использовал старые костыли вродеrequire.context, они не сработают. - Параллельные сборки требуют больше памяти. На больших проектах с тремя средами и множеством файлов RAM может не хватить — увеличь
--max-old-space-size.
В сухом остатке
Environment API — это структурное улучшение, которое в первую очередь нужно SSR и сложным проектам с несколькими целевыми платформами. Для простого SPA — можешь даже не заметить. Если у тебя SSR-проект, edge-функции или несколько клиентов — миграция окупается за день и убирает тонну условий из конфигов.
Для меня основная польза в том, что конфиг стал читаться как описание архитектуры. «Что мы собираем — клиент, SSR, edge?» — глянул в environments, увидел три блока. Это правильная декомпозиция, которую раньше приходилось городить руками.