Бандлеры внутри: Vite, esbuild, SWC, свой за 80 строк
Каждый раз, когда ты пишешь import { useState } from 'react', между этой
строкой и работающей страницей стоит инструмент, который превращает десятки
файлов в один или несколько JS-файлов для браузера. Этот инструмент называется
бандлером. Большую часть карьеры с ним достаточно молча соглашаться, но в трёх
случаях знание устройства спасает: когда страница долго грузится, когда
бандл-аналитика показывает мегабайт там, где должно быть 200 КБ, и когда команда
выбирает стек на новый проект. Этот урок разбирает, что делает бандлер,
чем отличаются Vite, esbuild, SWC и Rspack, и пишет собственный
маленький бандлер примерно на 80 строк, чтобы внутреннее устройство стало
осязаемым.
1. Зачем бандлер
Браузер умеет грузить <script type="module"> и понимает ESM-импорты. Зачем
тогда нужен бандлер?
- Граф зависимостей. Бандлер строит граф модулей из entry, рекурсивно тянет импорты, и подаёт их браузеру одним или несколькими запросами вместо сотен.
- Tree-shaking. Если ты импортировал из
lodashодну функциюchunk, в bundle уезжает только она, а не весь lodash. Браузер этого не умеет. - Транспиляция. TS, JSX, последние ES-фичи превращаются в код, который поймёт прицельный browserslist. Сам браузер JSX и TS не понимает.
- Code splitting. Граф режется на чанки по dynamic-import или маршрутам, лишний код не доезжает на первую страницу.
- Asset pipeline. Картинки, шрифты, CSS-модули попадают в граф вместе с JS, получают хэши, версионируются.
- Source maps. Bundled-позиция сопоставляется с исходной, чтобы стектрейс показывал твой код, а не минифицированную кашу.
Что взять с собой
- Бандлер собирает граф модулей и режет его на чанки.
- Tree-shaking, транспиляция, asset pipeline и source maps это его же работа.
- Браузер ESM умеет, но без бандлера нет ни tree-shaking, ни code splitting.
2. Модульный граф и code splitting
Поиграй с маленьким графом Echo. Видно, как dynamic-import (lazy-ребро) делит граф на два чанка и сколько ты экономишь на первой загрузке.
В графе видна суть
code splitting:
лазейное ребро (import() или React.lazy) превращает поддерево в отдельный
чанк. Всё, что доступно только через это ребро, не попадает в первый запрос. В
нашем примере SearchPage со своим VirtualScroller и date-fns весят суммарно
49 КБ это почти половина общего бандла. Без splitting эти 49 КБ грузятся всегда,
даже если пользователь не зашёл на /search.
Tree-shaking это сосед code splitting: он работает уже внутри одного чанка.
Из условного import { chunk } from 'lodash-es' в бандл попадёт только функция
chunk и её транзитивные зависимости, остальной lodash вырежется. Условия два:
ESM-семантика (статические импорты, без require) и пометка sideEffects в
package.json пакета. CommonJS-пакеты тришейкятся плохо.
Что взять с собой
- Граф модулей режется на чанки по dynamic-import и маршрутам.
- Lazy-ребро уносит поддерево в отдельный чанк, экономя первый запрос.
- Tree-shaking вырезает неиспользуемое внутри чанка, требует ESM и
sideEffects.
3. Vite: ESM в dev, Rollup в prod
Vite
устроен необычно: разделил dev и prod. В разработке Vite вообще не бандлит. Он
поднимает маленький dev-сервер, который отдаёт каждый твой файл браузеру отдельным
ESM-модулем. Браузер сам выстраивает граф через <script type="module">.
Изменил файл, Vite пересылает только его, горячая замена работает мгновенно.
В проде Vite зовёт Rollup: вот тут уже честное построение графа, tree-shaking, code splitting, минификация. Получается лучшее с обеих сторон: быстрый dev и зрелый prod.
// vite.config.ts
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
});
manualChunks: { vendor: ['react', 'react-dom'] } создаст отдельный
vendor-чанк это редко меняющийся код, его браузер закэширует надолго. А твой
рабочий код в main будет инвалидироваться чаще, не утаскивая за собой
React. Стандартный приём, чтобы CDN-кэш работал нормально.
Что взять с собой
- В dev Vite не бандлит, отдаёт модули браузеру напрямую как ESM.
- В prod использует Rollup: tree-shaking, code splitting, минификация.
manualChunksвыносит редко меняющийся код в отдельный vendor-чанк.
4. Компиляторы: esbuild, SWC, Rust-семья
Внутри любого бандлера есть транспилятор: то, что превращает TSX в JS, и старый ES в современный. Они и определяют, насколько быстрая будет сборка.
- esbuild написан на Go, делает почти всё в параллель. Vite использует его для pre-bundling зависимостей (CommonJS в ESM, объединение мелких модулей).
- SWC это Rust-транспилятор, заменивший Babel в Next.js и Parcel 2. Поддерживает плагины (как Babel), быстрый, есть интеграции с Vite и Webpack.
- Rspack и Turbopack это Rust-бандлеры с Webpack-совместимым API. Rspack используют, когда есть большой проект на Webpack и хочется ускорить сборку в десятки раз без переписывания конфига. Turbopack пишут авторы Next.js, идёт как dev-режим в Next 15+.
Общая черта последних пяти лет: тяжёлая работа переехала из JavaScript на Go и Rust. Сборка большого проекта раньше занимала минуты, теперь секунды.
Что взять с собой
- Внутри бандлера всегда есть транспилятор, на нём держится скорость.
- esbuild на Go и SWC на Rust в десятки раз быстрее Babel.
- Rspack и Turbopack это Webpack-совместимые Rust-замены для legacy.
5. Webpack: где ещё актуален
Webpack много лет был стандартом, но сейчас уступил Vite в новых проектах. Знать его всё ещё стоит по двум причинам.
Первая: большая часть существующих frontend-проектов на Webpack, и они не
переписываются за неделю. Loader-ы (babel-loader, css-loader), plugin-ы
(html-webpack-plugin, mini-css-extract-plugin), модель чанков и
splitChunksPlugin это рабочий инструмент, на котором живёт корпоративный фронт.
Вторая: Module Federation это уникальная функция Webpack 5 (Rspack теперь тоже умеет), на которой строят микрофронтенды. Полноценной замены пока нет.
Что взять с собой
- Webpack стандарт уходит, но legacy-проекты на нём ещё долго.
- Loader-ы и plugin-ы Webpack стоит уметь читать, даже не выбирая его новым.
- Module Federation для микрофронтендов это уникальная фича Webpack/Rspack.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.