diff --git a/editorial/agent-rewrites/343.json b/editorial/agent-rewrites/343.json index 2fa86bd..41735d8 100644 --- a/editorial/agent-rewrites/343.json +++ b/editorial/agent-rewrites/343.json @@ -1 +1 @@ -{"index":343,"slug":"editorial-2018-06-field-webpack-entry","title":"Webpack 4: почему новый entry раздувает bundle и как это доказать","excerpt":"После добавления entry сборка может вырасти по ожидаемой причине, из-за дублирования модулей или из-за ошибочного HTML. Разбираем stats.json, граф chunks и сетевой след страницы, затем выбираем точечную настройку.","contentHtml":"
Симптом появляется сразу после добавления второй точки входа: в dist возникает новый admin.[contenthash].js, а site.[contenthash].js тоже становится тяжелее. Иногда обычная страница ещё и запрашивает административный файл. Пользователь скачивает код, которым не воспользуется, а команда начинает менять splitChunks вслепую. Цена ошибки — лишний трафик в критическом пути, более долгий первый запуск и риск получить сломанный runtime.
Тезис простой: новый entry сам по себе не доказывает дублирование. Он добавляет новый старт в граф зависимостей. Дублирование возникает, когда один модуль достижим из нескольких начальных chunks и сборка не вынесла его в общий chunk. Отдельная причина — неправильный список script в HTML. Поэтому нужно проверить три слоя: emitted-ассеты, связи modules/chunks и реальные запросы страницы.
Webpack начинает обход графа с каждой точки входа. Для site он проходит импорты страницы, для admin — импорты панели. Если обе ветки доходят до одного пакета, например react или общего модуля приложения, этот пакет входит в область обеих веток. В Webpack 4 он не обязан автоматически стать одним отдельным файлом для initial chunks.
Важно различать entry, chunk и asset. entry — старт обхода. chunk — внутренняя группа модулей, которую Webpack планирует загрузить вместе. asset — файл, записанный в выходной каталог. HTML может подключить несколько assets одного entrypoint, а один asset может быть частью отношения с несколькими chunks. Сравнение только размеров файлов скрывает эту связь.
Предположим, приложение обслуживает две HTML-страницы. Обычная страница должна загружать site, административная — admin. Учебная конфигурация ниже показывает модель. Она не утверждает, что такой порог или имя cache group подходят конкретному проекту.
module.exports = { mode: 'production', entry: { site: './src/site.js', admin: './src/admin.js' }, optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } }, runtimeChunk: 'single' } };В этом примере chunks: 'all' расширяет область работы оптимизатора, а runtimeChunk: 'single' выносит служебный runtime в общий файл. Ни одна из опций не исправляет неверный HTML. Если шаблон подключает admin на /, браузер скачает его независимо от того, насколько аккуратно собран граф.
Снимки нужно делать в одинаковых условиях. Режим, минификация, source map, версия webpack, версия webpack-cli и плагины меняют результат сильнее, чем небольшая правка entry. Возьмите один коммит и сохраните два файла: stats-before.json до добавления entry и stats-after.json после него. Запускайте локальный бинарник проекта, а не случайную глобальную версию.
./node_modules/.bin/webpack --mode production --profile --json > stats-after.json\n# Затем верните прежнее значение entry и повторите ту же команду для stats-before.json.Снимок должен быть валидным JSON. Логи из конфигурации не должны попадать в stdout. Параметр --profile добавляет время сборки по модулям. Для ответа о размере он необязателен, но полезен, если новый entry одновременно замедлил компиляцию.
Первый слой — список assets. Сравните имя, размер и принадлежность к chunks. Новый admin.[contenthash].js ожидаем: у новой страницы должен появиться собственный код. Вопрос начинается там, где старый initial asset вырос или в нём повторился крупный модуль.
Второй слой — modules. Найдите модули, у которых массив chunks содержит больше одного идентификатора. Это сильный сигнал повторной достижимости, но не окончательный вывод о сетевой загрузке. Модуль может находиться в async chunk, в runtime-связи или в структуре, которую браузер не запрашивает на данной странице.
Учебный скрипт ниже печатает кандидатов на повтор. Он рассчитан на форму stats, которую выдаёт совместимая версия Webpack 4. Формат stats меняется между версиями, поэтому перед применением проверьте поля своего файла.
const fs = require('fs'); const stats = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); const repeated = (stats.modules || []).filter((module) => Array.isArray(module.chunks) && module.chunks.length > 1).sort((a, b) => (b.size || 0) - (a.size || 0)); for (const module of repeated.slice(0, 30)) console.log((module.size || 0) + '\\t' + module.name + '\\t' + module.chunks.join(','));Третий слой — entrypoints и chunks. Свяжите найденный модуль с конкретными стартами. Если общий пакет нужен обеим страницам, вынесение может уменьшить повтор в initial assets. Если модуль нужен только admin, переносить его в vendors нельзя: обычная страница начнёт загружать чужой код.
| Симптом | Вероятная причина | Проверка | Действие |
|---|---|---|---|
Появился новый admin-asset, а site почти не изменился | Добавилась отдельная страница, а не дублирование | Сравнить assets и список файлов обычного HTML | Оставить entry; не менять splitChunks без повторного модуля |
site вырос, крупный пакет есть в двух initial chunks | Обе точки входа достигают общий модуль | Сопоставить modules[].chunks с entrypoints | Проверить cache group или явную зависимость; пересобрать |
Обычная страница запрашивает admin | Шаблон или HTML-плагин подключает чужой entry | Посмотреть script-теги и Network на / | Исправить карту assets страницы до оптимизации chunks |
| Размер вырос только в development | Сравниваются разные режимы, source map или профили | Повторить два production-снимка одной командой | Считать выводом только сопоставимый результат |
| Модуль отмечен в нескольких chunks, но запросов больше не стало | Сигнал относится к графу, а не к загрузке выбранной страницы | Проверить entrypoint и Network с пустым кешем | Не выносить модуль автоматически; оценить его реальную загрузку |
Настройка оправдана после двух доказательств. Во-первых, один и тот же достаточно крупный код действительно принадлежит двум нужным начальным путям. Во-вторых, каждая страница сможет получить общий chunk без лишнего запроса или ошибки runtime. Размер общего файла сам по себе не задаёт выгоду: один дополнительный запрос может оказаться дороже небольшого повторения.
Для пакетов из node_modules часто начинают с отдельной cache group. Для прикладного общего кода сначала проверьте его границу. Если модуль нужен только редкому действию внутри панели, динамический import() может быть лучше начального общего chunk. Если две страницы имеют разные сроки жизни кеша, единый файл может чаще инвалидироваться и ухудшить повторные визиты.
В старой конфигурации Webpack 4 нельзя механически копировать пример из свежей документации. Сверьте доступные опции и фактический формат stats. Например, современная схема dependOn и настройки runtime могут отличаться от проекта на Webpack 4. Версия сборщика — часть условия эксперимента, а не примечание в конце.
stats-before.json и stats-after.json одной командой сборки.stats-fixed.json.Stats показывает компиляцию, а не реальную стоимость передачи. Он не учитывает в полном объёме gzip или Brotli, HTTP-кеш, CDN, приоритеты загрузки и время исполнения. Network показывает запросы конкретного браузерного сценария, но не доказывает поведение всех страниц и устройств. Для производительности нужен отдельный замер, а не вывод из суммы файлов в dist.
Метод также не решает проблему неправильного контракта HTML, нескольких runtime или несовместимого загрузчика. Если две script-последовательности инициализируют один модуль независимо, оптимизация размера может оставить ошибку выполнения. Проверяйте порядок тегов, runtime и консоль браузера после изменения.
Учебные команды и конфиг выше не являются production-рецептом. Подставьте реальные пути проекта, зафиксируйте версию и подберите пороги по измерению. Не объявляйте оптимизацию успешной только потому, что сборка завершилась с кодом 0.
Готовность проверяема: stats-fixed.json подтверждает ожидаемое распределение модулей; обычный HTML не содержит admin-script; Network обычной страницы не запрашивает административный asset; административная страница получает все нужные chunks; обе страницы проходят загрузку без ошибок runtime. Если хотя бы одно условие не выполнено, причина не доказана.
В типичном стендовом сценарии после коммита со второй точкой входа инженер запускает production-сборку и открывает обычную страницу. В dist появляется новый admin.[contenthash].js, а site.[contenthash].js тоже становится тяжелее. Иногда обычная страница ещё и запрашивает административный файл. Пользователь скачивает код, которым не воспользуется, а команда начинает менять splitChunks вслепую. Цена ошибки — лишний трафик в критическом пути, более долгий первый запуск и риск получить сломанный runtime.
Новый entry сам по себе не доказывает дублирование. Он добавляет новый старт в граф зависимостей. Дублирование возникает, когда один модуль достижим из нескольких начальных chunks и сборка не вынесла его в общий chunk. Отдельная причина — неправильный список script в HTML. Поэтому нужно проверить три слоя: emitted-ассеты, связи modules/chunks и реальные запросы страницы.
Webpack начинает обход графа с каждой точки входа. Для site он проходит импорты страницы, для admin — импорты панели. Если обе ветки доходят до одного пакета, например react или общего модуля приложения, этот пакет входит в область обеих веток. В Webpack 4 он не обязан автоматически стать одним отдельным файлом для initial chunks.
Важно различать entry, chunk и asset. entry — старт обхода. chunk — внутренняя группа модулей, которую Webpack планирует загрузить вместе. asset — файл, записанный в выходной каталог. Один entrypoint может ссылаться на несколько emitted-assets, а один chunk содержит множество modules. Сравнение только размеров файлов скрывает эту связь.
Предположим, приложение обслуживает две HTML-страницы. Обычная страница должна загружать site, административная — admin. Учебная конфигурация ниже показывает модель. Она не утверждает, что такой порог или имя cache group подходят конкретному проекту.
module.exports = { mode: 'production', entry: { site: './src/site.js', admin: './src/admin.js' }, optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } }, runtimeChunk: 'single' } };В этом примере chunks: 'all' расширяет область работы оптимизатора, а runtimeChunk: 'single' выносит служебный runtime в общий файл. При нескольких HTML-страницах шаблон должен подключить runtime перед нужным entry-asset. Ни одна из опций не исправляет неверный HTML. Если шаблон подключает admin на /, браузер скачает его независимо от того, насколько аккуратно собран граф.
Снимки нужно делать в одинаковых условиях. Режим, минификация, source map, версия webpack, версия webpack-cli и плагины меняют результат сильнее, чем небольшая правка entry. Возьмите один коммит и сохраните два файла: stats-before.json до добавления entry и stats-after.json после него. Запускайте локальный бинарник проекта, а не случайную глобальную версию.
./node_modules/.bin/webpack --mode production --profile --json=stats-after.json\n# Затем верните прежнее значение entry и повторите ту же команду для stats-before.json.Команда должна создать валидный JSON-файл; не смешивайте его с обычным логом сборки. Параметр --profile добавляет время сборки по модулям. Для ответа о размере он необязателен, но полезен, если новый entry одновременно замедлил компиляцию.
Первый слой — список assets. Сравните имя, размер и принадлежность к chunks. Новый admin.[contenthash].js ожидаем: у новой страницы должен появиться собственный код. Вопрос начинается там, где старый initial asset вырос или в нём повторился крупный модуль.
Второй слой — modules. Найдите модули, у которых массив chunks содержит больше одного идентификатора. Это сильный сигнал повторной достижимости, но не окончательный вывод о сетевой загрузке. Модуль может находиться в async chunk, в runtime-связи или в структуре, которую браузер не запрашивает на данной странице.
Учебный скрипт ниже печатает кандидатов на повтор. Он рассчитан на форму stats, которую выдаёт совместимая версия Webpack 4. Формат stats меняется между версиями, поэтому перед применением проверьте поля своего файла.
const fs = require('fs'); const stats = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); const repeated = (stats.modules || []).filter((module) => Array.isArray(module.chunks) && module.chunks.length > 1).sort((a, b) => (b.size || 0) - (a.size || 0)); for (const module of repeated.slice(0, 30)) console.log((module.size || 0) + '\\t' + module.name + '\\t' + module.chunks.join(','));Третий слой — entrypoints и chunks. Свяжите найденный модуль с конкретными стартами. Если общий пакет нужен обеим страницам, вынесение может уменьшить повтор в initial assets. Если модуль нужен только admin, переносить его в vendors нельзя: обычная страница начнёт загружать чужой код.
| Симптом | Вероятная причина | Проверка | Действие |
|---|---|---|---|
Появился новый admin-asset, а site почти не изменился | Добавилась отдельная страница, а не дублирование | Сравнить assets и список файлов обычного HTML | Оставить entry; не менять splitChunks без повторного модуля |
site вырос, крупный пакет есть в двух initial chunks | Обе точки входа достигают общий модуль | Сопоставить modules[].chunks с entrypoints | Проверить cache group или явную зависимость; пересобрать |
Обычная страница запрашивает admin | Шаблон или HTML-плагин подключает чужой entry | Посмотреть script-теги и Network на / | Исправить карту assets страницы до оптимизации chunks |
| Размер вырос только в development | Сравниваются разные режимы, source map или профили | Повторить два production-снимка одной командой | Считать выводом только сопоставимый результат |
| Модуль отмечен в нескольких chunks, но запросов больше не стало | Сигнал относится к графу, а не к загрузке выбранной страницы | Проверить entrypoint и Network с пустым кешем | Не выносить модуль автоматически; оценить его реальную загрузку |
Настройка оправдана после двух доказательств. Во-первых, один и тот же достаточно крупный код действительно принадлежит двум нужным начальным путям. Во-вторых, каждая страница сможет получить общий chunk без лишнего запроса или ошибки runtime. Размер общего файла сам по себе не задаёт выгоду: один дополнительный запрос может оказаться дороже небольшого повторения.
Для пакетов из node_modules часто начинают с отдельной cache group. Для прикладного общего кода сначала проверьте его границу. Если модуль нужен только редкому действию внутри панели, динамический import() может быть лучше начального общего chunk. Если две страницы имеют разные сроки жизни кеша, единый файл может чаще инвалидироваться и ухудшить повторные визиты.
В старой конфигурации Webpack 4 нельзя механически копировать пример из свежей документации. Сверьте доступные опции и фактический формат stats. Например, современная схема dependOn и настройки runtime могут отличаться от проекта на Webpack 4. Версия сборщика — часть условия эксперимента, а не примечание в конце.
stats-before.json и stats-after.json одной командой сборки.stats-fixed.json.Stats показывает компиляцию, а не реальную стоимость передачи. Он не учитывает в полном объёме gzip или Brotli, HTTP-кеш, CDN, приоритеты загрузки и время исполнения. Network показывает запросы конкретного браузерного сценария, но не доказывает поведение всех страниц и устройств. Для производительности нужен отдельный замер, а не вывод из суммы файлов в dist.
Метод также не решает проблему неправильного контракта HTML, нескольких runtime или несовместимого загрузчика. Если две script-последовательности инициализируют один модуль независимо, оптимизация размера может оставить ошибку выполнения. Проверяйте порядок тегов, runtime и консоль браузера после изменения.
Учебные команды и конфиг выше не являются production-рецептом. Подставьте реальные пути проекта, зафиксируйте версию и подберите пороги по измерению. Не объявляйте оптимизацию успешной только потому, что сборка завершилась с кодом 0.
Готовность проверяема: stats-fixed.json подтверждает ожидаемое распределение модулей; обычный HTML не содержит admin-script; Network обычной страницы не запрашивает административный asset; административная страница получает все нужные chunks; обе страницы проходят загрузку без ошибок runtime. Если хотя бы одно условие не выполнено, причина не доказана.