Vite — быстрая сборка фронтенда
Vite («быстрый» по-французски) — сборщик, который сделал работу разработчиков мгновенной. Вместо предварительной сборки всего проекта он отдаёт код по мере надобности и перезагружает только изменившееся — это и есть HMR за миллисекунды.
1. Что такое Vite
Vite — инструмент сборки и dev-сервер для современных веб-проектов. Его идеи:
- Нативные ESM — в dev браузер сам грузит модули, Vite лишь проксирует и транслирует их на лету;
- HMR — горячая перезагрузка: изменение компонента не перезагружает страницу;
- Rollup под капотом — скоростная и надёжная продакшен-сборка.
2. Создание проекта
npm create vite@latest my-app
# select framework: Vanilla / Vue / React / Svelte / Solid / Qwik
# select variant: TypeScript или JavaScript
cd my-app
npm install
npm run devСтруктура минимального проекта:
my-app/
├── index.html # точка входа (корень проекта)
├── src/
│ ├── main.ts
│ └── App.tsx
├── vite.config.ts # конфигурация Vite
└── package.json3. Dev-сервер и HMR
Команда npm run dev поднимает сервер на http://localhost:5173. Что происходит:
- модули отдаются как есть (ESM), поэтому старт почти мгновенный;
- при изменении файла обновляется только соответствующий модуль;
- CSS и компоненты фреймворков обновляются без сброса состояния приложения.
npm run dev -- --port 3000 # сменить порт
npm run dev -- --host # открыть в локальной сети4. Конфигурация и плагины
Главный файл настройки — vite.config.ts:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()], // для React-проекта
server: {
port: 3000,
proxy: { "/api": "http://localhost:8080" }
},
build: {
outDir: "dist",
sourcemap: false
}
});Плагины расширяют Vite: TypeScript-транспиляция, CSS-инъекция, генерация PWA (vite-plugin-pwa) и многое другое.
5. Переменные окружения
Значения в файле .env попадают в код только с префиксом VITE_:
# .env
VITE_API_URL=https://api.example.com
VITE_APP_VERSION=1.2.0// в коде
const api = import.meta.env.VITE_API_URL;
const isDev = import.meta.env.DEV; // true в developmentVITE_ не попадают в клиентский бандл — это защита от случайной утечки секретов. Секреты сервера храни в .env.local и не коммить.6. Импорт ресурсов и CSS
Vite нативно понимает импорты картинок, шрифтов, SVG и CSS:
import logo from "./logo.svg"; // URL ресурса, файл хэшируется
import "./styles.css"; // стили попадут в бандл
import styles from "./m.css"; // CSS Modules: styles.title
<img src={logo} alt="логотип" />Для больших SVG можно подключить vite-svg-loader и использовать как компонент Vue/React.
7. Продакшен-сборка
npm run build
# создаст dist/: index.html + минифицированные JS/CSS
npm run preview # локально посмотреть собранноеЧто делает build:
- транспилирует TypeScript и JSX;
- минифицирует код (esbuild/terser);
- добавляет хэши к именам файлов для кэш-инвалидации;
- выносит общий код в отдельные чанки.
8. Оптимизация и code splitting
Тяжёлые зависимости выносим в отдельные чанки через manualChunks, а страницы грузим лениво через динамический импорт:
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ["vue", "element-plus"]
}
}
}
}// Ленивая загрузка страницы в Vue Router
const Product = () => import("../views/Product.vue");
// ленивая загрузка в React
const Product = lazy(() => import("./views/Product"));9. Vite + Vue, React, TypeScript
| Шаблон | Команда | Курс |
|---|---|---|
| Vue + TS | --template vue-ts | Vue 3 |
| React + TS | --template react-ts | React |
| Vanilla + TS | --template vanilla-ts | JavaScript |
В шаблонах уже настроены ESLint (курс), TypeScript (курс) и HMR. Для тестов используй Vitest — его создал тот же автор, что и Vite (курс Vitest).
10. Proxy и работа с API
На время разработки бэкенд отделён от фронтенда. Проксируй запросы на реальный API, чтобы не ловить CORS:
server: {
proxy: {
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api/, "")
}
}
}// в коде: fetch("/api/products") → http://localhost:8000/products
const res = await fetch("/api/products");11. Режим библиотеки (lib mode)
Vite собирает не только приложения, но и библиотеки: UI-компоненты, SDK, утилиты. В конфиге задаются точка входа и форматы вывода:
build: {
lib: {
entry: "src/index.ts",
name: "MyLib",
formats: ["es", "cjs"]
},
rollupOptions: {
external: ["vue"], // vue не попадёт в бандл
output: { globals: { vue: "Vue" } }
}
}В package.json укажи точки входа:
{
"main": "./dist/my-lib.cjs",
"module": "./dist/my-lib.js",
"exports": { ".": "./dist/my-lib.js" }
}external и добавляй их же в peerDependencies — иначе в приложении окажется два экземпляра фреймворка разных версий.12. Vite и PWA
Плагин vite-plugin-pwa подключает сервис-воркер, офлайн-кэш и установку приложения буквально одной строкой:
import { VitePWA } from "vite-plugin-pwa";
export default defineConfig({
plugins: [
VitePWA({
registerType: "autoUpdate",
manifest: {
name: "Мой магазин",
short_name: "Shop",
theme_color: "#2563eb",
icons: [{ src: "/icon.png", sizes: "192x192", type: "image/png" }]
}
})
]
});- первый визит работает как обычный сайт, ресурсы кэшируются;
- после сборки сервис-воркер включает офлайн-режим для всех страниц;
- ярлык «Установить» появляется браузером автоматически при соблюдении критериев.
localhost проверить можно, но без выпущенного сертификата браузер откажет в сервис-воркере.13. CI, деплой и аудит
Продакшен-сборку удобно гонять в непрерывной интеграции. Пример пайплайна на GitHub Actions (курс здесь):
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run test
- run: npm run build
- uses: actions/upload-pages-artifact@v3
with: { path: "dist" }Перед выкладкой прогоняй в том же джобе тесты Vitest и линт ESLint, а после сборки смотри отчёт о чанках и скорость Lighthouse.
tsconfig: когда npm run build падает на ошибках типов, баги ловятся в CI, а не на проде.14. SSR и SSG: серверный рендер и статика
Vite собирает не только SPA. Для серверного рендера есть отдельная команда сборки, а для генерации статических страниц — связка vite-ssg:
vite build --ssr src/server.ts # точка входа для сервера
npm i -D vite-ssg
import { ViteSSG } from "vite-ssg/vue"
ViteSSG({ routes, renderPages }) # HTML генерится на этапе сборки- SSR — страницы рендерятся на каждом запросе, нужен Node-сервер;
- SSG — HTML собирается один раз при build и раздаётся как статика;
- гидрация — Vue/React «оживляют» уже готовый HTML на клиенте.
15. Инспекция модулей и отладка сборки
Когда неясно, почему модуль попал в бандл или трансформировался странно — установи vite-plugin-inspect. Он показывает промежуточные состояния кода: исходник → транспилированный → минифицированный, и какие плагины что изменили.
npm i -D vite-plugin-inspect
plugins: [inspect()]
// в dev открой localhost:5173/__inspect
// time — сколько ждал шаг, transform — изменения кодаНа вопросы «почему сборка долгая» отвечает профилирование vite build --profile, а размер чанков покажет визуализатор бандла:
npm i -D rollup-plugin-visualizer
visualizer({ gzipSize: true }) // карта бандла в браузере16. Монорепозитории и рабочее окружение
Vite дружит с workspace-монорепами (pnpm/npm workspaces): несколько пакетов собираются в одном конфиге, а зависимости линкуются автоматически.
pnpm-workspace.yaml
packages: [ "apps/*", "packages/*" ]
// пакет components импортируется по имени пакета
import { Button } from "@ui/button"- общие конфиги выносятся в
presets/vite.base.tsи объединяются черезmergeConfig; - при работе с локальными пакетами следи за их пересборкой через
optimizeDeps; - в CI добавляй
vite build --force --logLevel warn, чтобы кэш не протухал между джобами.
17. Миграция с Webpack и чек-лист запуска
Переезд с Webpack на Vite проходит в три шага:
- перенеси
entryиloadersвplugins(babel →@vitejs/plugin-legacy, sass-loader → обработка из коробки); - замени
DefinePluginнаimport.meta.env, а webpack-алиасы — наresolve.alias; - проверь полифиллы и версии зависимостей.
resolve: { alias: { "@": "/src" } },
define: {}, // вместо DefinePlugin
optimizeDeps: { include: ["lodash"] }Финальный чек-лист прод-запуска:
- CSP-заголовки и отключение sourcemap в проде;
- предзагрузка критичного CSS и preload-атрибуты;
- сверка версий Node и пакетов через
npm audit.
18. import.meta.glob: модули по шаблону
Вместо десятка строк import v1 from "@/views/Page1.vue" можно импортировать сразу всё, что попадает под маску:
const lazyPages = import.meta.glob("../views/*.vue");
// { "../views/Home.vue": () => import("../views/Home.vue"), ... }
const eagerPages = import.meta.glob("../utils/*.ts", { eager: true });- ленивый вариант возвращает функции — идеально для роутов: передай их прямо в
defineAsyncComponentили router; eager: trueгрузит модули синхронно — для словарей, типов и малых утилит;- квадратные скобки
[id]в маске ловят динамические имена, аimport.meta.globработает и на Node (Vitest, vite-node).
const pages = import.meta.glob("../views/*.vue", { eager: true });
const routes = Object.entries(pages).map(([path, mod]) => ({
path: "/" + path.split("/").pop().replace(/\.vue$/, ""),
component: mod.default
}));?raw вернёт содержимое файла строкой (SVG, markdown), а ?url — URL ресурса без хэширования. Это запасной выход для «нестандартных» файлов.19. Собственный плагин: виртуальные модули
Плагин Vite — это объект с именем и хуками. Самые частые хуки: config (правка конфига), transform (меняет код модуля перед отдачей) и resolveId/load (виртуальные модули):
export default function myPlugin() {
return {
name: "my-plugin",
transform(code, id) {
if (id.endsWith(".tpl")) {
return `export default ${JSON.stringify(code)}`;
}
},
resolveId(id) {
if (id === "virtual:readme") return "\0virtual:readme";
},
load(id) {
if (id === "\0virtual:readme") return `export default "документ";`;
}
};
}- префикс
\0помечает виртуальный id, чтобы другие плагины не трогали его — соглашение экосистемы; configureServerдобавляет middleware в dev-сервер;- хуки могут быть async и получать
contextс информацией о сборке.
transform лёгким и без сайд-эффектов: он вызывается на каждый модуль при каждой инвалидации, и тяжёлый плагин мгновенно замедлит dev-сервер.vite-plugin: тип для defineConfig подскажет все хуки прямо в редакторе.20. DevTools и интеграция с редактором
Быстрый dev-сервер — половина дела; вторая половина — отладка. В dev-режиме Vite отдаёт исходники как есть, поэтому переходить к коду можно прямо из браузера:
npm i -D @vitejs/plugin-vue-devtools
// vite.config.ts
import vue from "@vitejs/plugin-vue";
import vueDevTools from "@vitejs/plugin-vue-devtools";
plugins: [vue(), vueDevTools()]- Vue DevTools-плагин даёт timeline рендеров, инспекцию компонентов и переход к исходнику во встроенном окне;
- для React используй расширение браузера + визуализатор Profiler для поиска лишних перерисовок;
- эндпоинты
/__inspectи/__open-in-editorоткрывают файлы в VS Code по клику на ошибку.
npm run dev -- --debug # лог преобразований и resolve каждого модуля
npm run dev -- --host 0.0.0.0 # доступ с телефона и планшета по LAN.vscode/launch.json или запусти vite через отладчик типа node: точка останова встанет прямо в исходнике.21. Подводные камни Node и кэша зависимостей
«У меня не собирается, а у коллеги да» — частая жалоба в Vite-проектах. Разница почти всегда в версиях или кэше:
| Симптом | Причина | Решение |
|---|---|---|
| Ошибка ESM-CJS при импорте | библиотека без поддержки ESM | optimizeDeps.include или обновление пакета |
| Старые результаты после обновления | кэш node_modules/.vite | vite --force |
| Сервер «роняет» на каждом файле | несовместимая версия Node | сверка с engines в package.json |
| Чужие хэши в бандле | несовпадение lock-файлов в CI | npm ci в пайплайне |
// package.json
"engines": { "node": ">=20" },
"packageManager": "pnpm@9".nvmrc или volta, иначе «работает у меня» будет преследовать команду до конца проекта.rm -rf node_modules/.vite и перезапуск. В большинстве странных фризов виноват протухший deps-кэш.Что дальше
Размести свой проект на GitHub и деплой на Pages/Vercel, стили — в Sass. Качество кода проверит ESLint, а скорость загрузки — аудит Lighthouse.