Webpack

Что это такое

Webpack — это сборщик модулей. Он стартует с одного или нескольких файлов точки входа, идёт по каждому импорту, применяет преобразования к файлам по пути, а на выходе получается несколько файлов, которые браузер умеет загружать. Именно Webpack сделал зависимости из npm пригодными для браузера; десять лет он оставался инструментом сборки по умолчанию для больших фронтенд-приложений.

Почему это важно

Модель Webpack стоит понимать, даже если вы никогда его не настраиваете. Терминология, которую он ввёл, стала общей для всех фронтенд-инструментов: точка входа, выход, загрузчик, плагин, разделение кода, чанк, хеширование, tree shaking. К тому же он до сих пор работает во множестве продакшн-проектов, и его конфигурация нередко оказывается тем местом, где сборка медленна или вывод неожиданно велик. Разберитесь, что делает каждая часть, и конфигурация на пятьсот строк перестанет быть заклинанием и станет тем, что можно починить.

Ключевые идеи

Весь смысл — в графе зависимостей

Webpack не загружает файлы в том порядке, в каком вы их перечислили, — он их находит. Файл точки входа импортирует другие, те в свою очередь импортируют ещё, и именно граф определяет вывод. Поэтому добавление импорта в любом месте меняет бандл; поэтому же случайный импорт большой библиотеки способен удвоить его.

// webpack.config.js
export default {
  mode: "production",
  entry: "./src/index.js",
  output: { filename: "[name].[contenthash].js", clean: true },
};

Хеш содержимого в имени файла меняется только при изменении содержимого — именно это позволяет браузеру кэшировать бандл на год.

Загрузчики преобразуют, плагины расширяют

Загрузчик — функция, применяемая к содержимому файла до того, как он войдёт в граф: скомпилировать TypeScript, преобразовать таблицу стилей, встроить небольшое изображение. Плагин же подключается к жизненному циклу сборки и делает что-то более широкое: выпускает HTML-файл, копирует ресурсы, анализирует бандл. Если изменение касается отдельного файла, это загрузчик; если всей сборки — плагин.

Разделение кода — это конфигурация плюс import()

Динамический import() создаёт отдельный чанк, который запрашивается при выполнении. Так тяжёлый редактор или редко посещаемый маршрут остаётся вне начальной загрузки. Сборщик умеет и сам выделять общий код между точками входа — отсюда и берётся рантайм-чанк.

Разработка и продакшн — разные сборки

Разработке нужны быстрые пересборки и читаемый исходный код, продакшену — маленький вывод, минификация и tree shaking. mode переключает значения по умолчанию. Много сложности в конфигурации появляется как раз у проектов, которые пытались обслужить обе задачи одним набором настроек.

Сложность конфигурации — известная цена

Гибкость Webpack означает, что выразить можно почти любую сборку — и допустить почти любую ошибку. Большая конфигурация с несколькими точками входа, несколькими загрузчиками, сцепленными на каждый тип файла, и плагинами, которые правят друг друга: именно из-за неё многие команды перешли к инструментам с меньшим числом решений.

Практика

Проследите одну зависимость до бандла

Добавьте в файл точки входа импорт небольшой библиотеки, соберите проект и поищите её код в выводе. Затем импортируйте её динамически в ветви, которая выполняется редко, и сравните результат.

Готово, когда: первая сборка включает библиотеку в основной бандл, а вторая выпускает её отдельным чанком, который не запрашивается при загрузке.

Измерьте цену одного загрузчика

Возьмите рабочую конфигурацию и отключите загрузчик, который преобразует какой-нибудь тип файлов; затем пересоберите проект.

Готово, когда: вы можете сказать, что сломалось и преобразовывал ли загрузчик содержимое или лишь менял сообщение об ошибке при его отсутствии.

Частые ошибки

  • Импорт целой библиотеки ради одной функции — в граф входит весь пакет; исправить: импортируйте конкретный модуль или выберите пакет поменьше.
  • Продакшн на отладочной сборке — вывод не минифицирован, не хеширован и намного больше; исправить: собирайте с mode: "production" и проверяйте артефакты.
  • Один загрузчик, настроенный дважды на пересекающиеся типы файлов — один молча побеждает, и преобразование будто не работает; исправить: держите одно правило на расширение и проверяйте разрешённую конфигурацию.
  • Игнорирование отчёта о бандле — случайная зависимость удваивает загрузку, и никто не замечает; исправить: осматривайте вывод после добавления зависимости.
  • Сложная конфигурация в проекте, которому она больше не нужна — сборку менять дольше, чем пользоваться ею; исправить: пересмотрите инструмент, когда требования сузились.

Что читать дальше

  • JavaScript (02-frontend/06-javascript) — синтаксис модулей, который задаёт граф, по которому идёт сборщик.
  • Управление пакетами (npm) (02-frontend/04-package-management-npm) — откуда берутся зависимости, которые он собирает.
  • Vite (02-frontend/09-vite) — инструмент, который заменил его в большинстве новых проектов, и почему.