Что делает сборщик (bundler)? Объясните tree shaking и code splitting — как уменьшить бандл приложения.
Короткий ответ
- Сборщик строит граф модулей и выдаёт оптимизированные бандлы
- Tree shaking выбрасывает неиспользуемые экспорты
- Работает благодаря статической природе ES-модулей
- Side effects и CommonJS мешают вытряхиванию кода
- Code splitting режет бандл на чанки по маршрутам
- Динамический import() — точка разреза и ленивая загрузка
- Анализ бандла показывает, что реально тянет вес
Сборщик превращает граф модулей в минифицированные чанки; tree shaking статически удаляет мёртвые экспорты ES-модулей, а code splitting через динамический import откладывает загрузку кода до момента необходимости.
Как сказать вслух
пример ответаСборщик берёт точку входа, обходит все импорты, строит граф модулей и на выходе даёт оптимизированные файлы: с транспиляцией, минификацией и хешами для кэширования. Tree shaking — это удаление кода, который никто не импортирует: оно возможно, потому что импорты и экспорты ES-модулей статичны и видны без запуска кода. Code splitting — разрезание бандла на части: код тяжёлой страницы загружается только когда пользователь на неё перешёл, обычно через динамический import. Вместе это главные инструменты уменьшения начальной загрузки.
Подробный ответ
Основной ответ
Сборщик решает несколько задач: резолв и объединение модулей, прогон через трансформации (TypeScript, JSX), минификация, хеширование имён для долгого кэширования, выдача source maps. Tree shaking опирается на статический анализ ESM: import/export нельзя сформировать динамически, поэтому сборщик точно знает, какие экспорты не используются, и удаляет их. Мешают этому CommonJS (require анализируется плохо) и побочные эффекты на уровне модуля — код, выполняющийся при импорте; поле sideEffects: false в package.json разрешает агрессивное удаление, а импорт всей библиотеки вместо конкретных функций сводит экономию на нет. Code splitting строится на динамическом import(), который возвращает промис и становится границей чанка: типично делят по маршрутам (в React — lazy + Suspense), по тяжёлым виджетам (графики, редакторы) и выносят общие зависимости в отдельные чанки. Современный фон: Vite в dev-режиме отдаёт нативные ES-модули без сборки (отсюда мгновенный старт), а прод собирает Rollup; esbuild и SWC ускорили трансформации на порядок.
Ключевые моменты
- Почему ESM. Статичность import/export позволяет доказать неиспользуемость кода без его выполнения; с CommonJS это почти невозможно.
- Side effects. Код, выполняющийся при импорте модуля, нельзя безопасно удалить; sideEffects в package.json — подсказка сборщику.
- Границы чанков. Динамический import() задаёт точки разреза; деление по маршрутам — первый и самый выгодный шаг.
- Измерение. Визуализаторы бандла (rollup-plugin-visualizer и аналоги) показывают тяжёлые зависимости — часто одна библиотека дат или иконок весит больше всего кода приложения.
Практический контекст
Реальный сценарий: бандл вырос, LCP просел — нужно найти причину и порезать. Интервьюер ждёт конкретики: анализ состава бандла, замена тяжёлых зависимостей, ленивые маршруты, проверка, что библиотека поставляет ESM. Для senior-позиции полезно понимать и границы: чрезмерное дробление на чанки создаёт каскады запросов, поэтому критичные чанки префетчат или объединяют.
Частые ошибки
- Говорят «tree shaking удаляет неиспользуемый код» без понимания, почему для этого нужны ES-модули
- Импортируют библиотеку целиком и ждут, что сборщик «сам разберётся»
- Дробят приложение на десятки мелких чанков, получая водопад сетевых запросов вместо ускорения