Современная фронтенд-разработка технологически весьма сложная: множество зависимостей, микросервисы, размеры самих проектов, плагины для настройки окружения и многое другое.
Это касается и одного из самых ключевых этапов в разработке - сборка проекта. Множество проектов, на которых я работал, собирались (и собираются) с помощью Webpack. Это классический и проверенный временем инструмент: со своими преимуществами и недостатками.
В какой-то момент недостатки стали перешивать:
Время сборки: среднее время ожидание сборки нашего проекта на достаточно среднем ПК составляет около 3-х минут. До этого активно предпринимались попытки оптимизировать скорость сборки (esbuild-loader, например);
HMR (Hot Module Replacement): составлял, в среднем, около 8-12 секунд, что также сложно назвать плюсом;
Сложность конфигурации: необходимость подключения скриптов, плагинов (HtmlWebpackPlugin, miniCssExrtactPlugin, HMR, EnvironmentPlugin и т.д).
Мы задумались об альтернативах. И единственным подходящим вариантом для нас показался Vite из-за его простой, а главное быстрой сборки. А получилось ли у нас успешно мигрировать и каких результатов мы достигли, я и расскажу в данной статье.
Почему именно Vite?
1. Актуальность, экосистема
С одной стороны, Webpack, выпущенный уже более 13 лет назад, до сих пор остается актуальным сборщиком, согласно статистике по скачиваниям с npmjs.
С другой стороны, относительно молодой Vite, дебютировавший в 2020 году, актуальность которого с каждым годом активно растёт.
Что Webpack, что Vite регулярно обновляются, а также, судя по статистике Npm, активно используются. Несмотря на уже солидный возраст существования Webpack, выпущен 13 лет назад, Vite, дебютировавший в 2020 году, не сильно от него отстаёт.
Более того, пока популярность Webpack находится на плато, у Vite она только растёт.



2.Базовая конфигурация
Vite предлагает простую «нулевую» настройку, где достаточно использовать один плагин для запуска приложения. Настройка сервера разработки, параметры сборки уже предустановлены по умолчанию в настройках самого Vite:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https: //vite.dev/config/
export default defineConfig({
plugins: [react()]
Webpack: необходимо указать точки входа, выхода, плагины, модули (загрузчики по типу babel/css-loader)
export default (): webpack.Configuration => ({
mode: 'development',
entry: path.resolve (__dirname,'src','index.tsx'),
output: {
filename: 'test-bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new HtmlWebpackPlugin ({
template: path.resolve (__dirname, 'public','index.html'),
}),
],
module: {
rules: [
{ test: /\.(js|jsx|tsx)$/, exclude: /node_modules/,use: 'babel-loader' },
{ test: /\.css$/,use:['style-loader','css-loader']},
],
},
resolve: {
extensions: ['.tsx','.ts', '.js'],
mainFiles: ['index'],
},
devServer: {
port: 3000,
open: true,
hot: true,
},
});
Наш конфиг в Webpack составлял под 200+ строк.
Vite вышел примерно в 60 строк кода с максимально простым и понятным конфигом.
3. Время сборки (dev)
При первом запуске в Webpack, происходит следующая цепочка действий:
Чтение всех файлов, начиная с точки входа;
Определение зависимостей;
Обработка через скрипты (Babel), плагины;
Генерация бандлов.
В данном случае происходит линейная обработка всех файлов. С учетом увеличения их количества растягивается время начальной сборки.
Конечно, есть варианты оптимизации: кэширование результатов лоадеров, разделения кода на чанки, многопоточная обработка с помощью thread-loader, замена Babel на esbuild-loader и многое другое. И результат действительно есть, но хочется все ещё быстрее и, в идеале, попроще.
На Vite же, принцип работы строится вокруг деления приложения на зависимости (node_modules) и исходный код:
Зависимости, которые предварительно собираются с помощью esbuild - бандлер, написанный на Go, в задачи которого входят: транспиляция и минификация;
Исходный код, который обслуживается через нативные ES Modules браузера, что позволяет загружать модули по требованию и избегать предварительной сборки исходного кода.
Vite запускает сервер практически мгновенно за счет использования инструментов ESM, esbuild и отсутствия бандлинга в dev-режиме.
4. HMR
Hot Module Replacement (HMR), она же - горячая замена модулей - механизм, связанный с обновлением модулей на клиенте при их изменении на сервере. По результатам миграции, HMR на Vite происходит практически мгновенно (~40-50мс). На Webpack этот процесс занимает чуть дольше (~400мс на основном проекте). Несмотря на достаточно быструю реакцию со стороны обоих сборщиков, Vite выходит победителем, причем данные показатели сохраняются как на пустом, так и на мигрированном проекте.
За счёт чего достигается такая разница в скоростях HMR?
На Webpack при включении плагина HotModuleReplacementPlugin внедряется HMR Runtime, который получает уведомления об изменениях на сервере и, при изменении модулей (загрузка/замена/удаление), генерирует и применяет соответствующие обновления для этих модулей. Даже при изменении одного файла, HMR runtime необходимо провести:
Анализ графа зависимостей - поиск модулей, которые находятся в зависимости от изменённого файла. Если измененный файл является зависимостью для других модулей, они также будут пересобраны;
Пересборку этих модулей с обновлением маппинга;
Отправку в браузер обновленных изменений в виде JSON (the updated manifest).
Есть следующая структура зависимостей:
App.js -> Component.js -> data.js
index.js -> data.js
При изменении файла “data.js” (например, добавлен комментарий), произойдет пересборка всех зависимых модулей, т.е:
- data.js;
- Component.js;
- App.js;
- index.js.
Таким образом, сложность пересборки зависит от количества затронутых модулей.
На Vite используется ESM-ориентированный подход с использованием HMR API (встроенный плагин, не требует дополнительных установок), который использует нативные возможности браузера для точечных обновлений и включает следующие этапы при изменении файла:
Обработка измененного файла через плагины (например, TypeScript -> JavaScript);
HMR Update: отправка уведомления от сервера к браузеру;
Перезагрузка модуля через динамический импорт
Используя пример с структурой файлов выше, изменения затронут только файл data.js: изменения файла -> транспиляция -> динамическое обновление модуля (зависимые модули Component.js, App.js и index.js также остаются в кеше, но их зависимости обновляются).
Подводя итоги, ключевыми отличиями HMR от Webpack можно выделить:
Отсутствие пересборки зависимостей;
Минимальная нагрузка на сеть: загрузка только изменённого data.js, вместо JSON со всеми зависимостями (как на Webpack);
Кеширование модулей: Kek.js, App.js, Index.js остаются в кеше браузера, пока не изменятся.
Дополнительно, оставлю небольшие замеры скорости холодного старта (в dev) и HMR на пустом и нашем проекте:
В качестве железа используется ноутбук со следующими характеристиками:
Ryzen 7 4800H;
Оперативной памяти 16гб;
OS Ubuntu версии 24.04.


Процесс миграции на Vite
Этот процесс также хочется отнести к плюсам, т. к. явных сложностей у нас не возникло и весь перенос занял меньше одного рабочего дня:
Включение плагинов (react, tailwindcss, mkcert);
Определили env-переменные через define;
Настроили rollupOptions в build.
И вуаля! Практически с первого раза, наш фронтенд завёлся без каких-либо явных проблем.
Бонусом: на замену Jest, подключили Vitest , обладающего своими преимуществами по сравнению c Jest
Несложная миграция за счёт API, похожего на Jest;
Не требует отдельных конфигурационных файлов (jest.config.js). Vitest использует тот же vite.config.ts. Нет необходимости дублировать настройки (транспиляции, алиасы).
О недостатках переезда на Vite
Как и написал чуть выше, проблемы были и сохраняются некоторые недостатки, над которыми следуют немного поработать, как пример:
Необходимость немного покопаться в rollupOptions: prod-сборка по времени +- не изменилась, но иногда она занимает чуть больше времени, чем на Webpack;
Module Federation: микрофронтенды с первого раза завести не удалось. Shared-зависимости 1 микрофронтенда в Webpack имели примерно следующую структуру:
shared: {
'some-library': {
// Загружаем модуль только один раз
singleton: true,
// Минимальная версия для загрузки
requiredVersion: '99.09',
// Загрузка модуля сразу при инициализации приложения
eager: true,
},
На Vite каждая shared-зависимость включала в себя лишь requiredVersion и микрофронты изначально не завелись, ссылаясь на несовпадения версий. По итогу shared-зависимости просто были переданы массивом, и они заработали.
Заключение
Vite - очень быстрый и простой по настройкам сборщик, который в нашем кейсе оправдал себя на 100%:
Успешно и практически бесшовно переехали на новый сборщик;
Легко настроили его конфиг под наши потребности;
Сократили время сборки (в dev) минимум в 25–30 раз;
Идеально работающий HMR;
Подключили новый для нас инструмент для тестирования (Vitest).