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 и реальные запросы страницы.

Что именно делает entry

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 одновременно замедлил компиляцию.

Схема диагностики Webpack: два entry ведут к chunks и assets, затем HTML и Network подтверждают фактическую загрузку
Stats описывает результат компиляции. HTML и Network показывают, какие файлы получает конкретная страница. Нужны оба наблюдения.

Читаю stats по слоям

Первый слой — список 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 с пустым кешемНе выносить модуль автоматически; оценить его реальную загрузку

Когда менять splitChunks

Настройка оправдана после двух доказательств. Во-первых, один и тот же достаточно крупный код действительно принадлежит двум нужным начальным путям. Во-вторых, каждая страница сможет получить общий chunk без лишнего запроса или ошибки runtime. Размер общего файла сам по себе не задаёт выгоду: один дополнительный запрос может оказаться дороже небольшого повторения.

Для пакетов из node_modules часто начинают с отдельной cache group. Для прикладного общего кода сначала проверьте его границу. Если модуль нужен только редкому действию внутри панели, динамический import() может быть лучше начального общего chunk. Если две страницы имеют разные сроки жизни кеша, единый файл может чаще инвалидироваться и ухудшить повторные визиты.

В старой конфигурации Webpack 4 нельзя механически копировать пример из свежей документации. Сверьте доступные опции и фактический формат stats. Например, современная схема dependOn и настройки runtime могут отличаться от проекта на Webpack 4. Версия сборщика — часть условия эксперимента, а не примечание в конце.

Порядок проверки

  1. Выписать HTML-документы и назначить каждому ровно те entry, которые ему нужны.
  2. Зафиксировать версию webpack, webpack-cli, режим, source map и одинаковый коммит.
  3. Сохранить stats-before.json и stats-after.json одной командой сборки.
  4. Сравнить assets: новые файлы, изменение размеров и связанные chunks.
  5. Найти крупные модули с несколькими chunk-идентификаторами и связать их с конкретными entrypoints.
  6. Открыть обычную и административную страницы с очищенным кешем; проверить script-теги и Network.
  7. Изменить одну cache group или один HTML-маршрут, затем создать stats-fixed.json.
  8. Повторить ту же проверку для обеих страниц и отдельно пройти отрицательный путь: обычная страница не должна загружать admin-код.

Ограничения и критерий готовности

Stats показывает компиляцию, а не реальную стоимость передачи. Он не учитывает в полном объёме gzip или Brotli, HTTP-кеш, CDN, приоритеты загрузки и время исполнения. Network показывает запросы конкретного браузерного сценария, но не доказывает поведение всех страниц и устройств. Для производительности нужен отдельный замер, а не вывод из суммы файлов в dist.

Метод также не решает проблему неправильного контракта HTML, нескольких runtime или несовместимого загрузчика. Если две script-последовательности инициализируют один модуль независимо, оптимизация размера может оставить ошибку выполнения. Проверяйте порядок тегов, runtime и консоль браузера после изменения.

Учебные команды и конфиг выше не являются production-рецептом. Подставьте реальные пути проекта, зафиксируйте версию и подберите пороги по измерению. Не объявляйте оптимизацию успешной только потому, что сборка завершилась с кодом 0.

Готовность проверяема: stats-fixed.json подтверждает ожидаемое распределение модулей; обычный HTML не содержит admin-script; Network обычной страницы не запрашивает административный asset; административная страница получает все нужные chunks; обе страницы проходят загрузку без ошибок runtime. Если хотя бы одно условие не выполнено, причина не доказана.

Проверяемые источники

"} +{"index":343,"slug":"editorial-2018-06-field-webpack-entry","title":"Webpack 4: почему новый entry раздувает bundle и как это доказать","excerpt":"После добавления entry сборка может вырасти по ожидаемой причине, из-за дублирования модулей или из-за ошибочного HTML. Разбираем stats.json, граф chunks и сетевой след страницы, затем выбираем точечную настройку.","contentHtml":"

В типичном стендовом сценарии после коммита со второй точкой входа инженер запускает production-сборку и открывает обычную страницу. В dist появляется новый admin.[contenthash].js, а site.[contenthash].js тоже становится тяжелее. Иногда обычная страница ещё и запрашивает административный файл. Пользователь скачивает код, которым не воспользуется, а команда начинает менять splitChunks вслепую. Цена ошибки — лишний трафик в критическом пути, более долгий первый запуск и риск получить сломанный runtime.

Новый entry сам по себе не доказывает дублирование. Он добавляет новый старт в граф зависимостей. Дублирование возникает, когда один модуль достижим из нескольких начальных chunks и сборка не вынесла его в общий chunk. Отдельная причина — неправильный список script в HTML. Поэтому нужно проверить три слоя: emitted-ассеты, связи modules/chunks и реальные запросы страницы.

Что именно делает entry

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 одновременно замедлил компиляцию.

Схема диагностики Webpack: два entry ведут к chunks и assets, затем HTML и Network подтверждают фактическую загрузку
Stats описывает результат компиляции. HTML и Network показывают, какие файлы получает конкретная страница. Нужны оба наблюдения.

Читаю stats по слоям

Первый слой — список 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 с пустым кешемНе выносить модуль автоматически; оценить его реальную загрузку

Когда менять splitChunks

Настройка оправдана после двух доказательств. Во-первых, один и тот же достаточно крупный код действительно принадлежит двум нужным начальным путям. Во-вторых, каждая страница сможет получить общий chunk без лишнего запроса или ошибки runtime. Размер общего файла сам по себе не задаёт выгоду: один дополнительный запрос может оказаться дороже небольшого повторения.

Для пакетов из node_modules часто начинают с отдельной cache group. Для прикладного общего кода сначала проверьте его границу. Если модуль нужен только редкому действию внутри панели, динамический import() может быть лучше начального общего chunk. Если две страницы имеют разные сроки жизни кеша, единый файл может чаще инвалидироваться и ухудшить повторные визиты.

В старой конфигурации Webpack 4 нельзя механически копировать пример из свежей документации. Сверьте доступные опции и фактический формат stats. Например, современная схема dependOn и настройки runtime могут отличаться от проекта на Webpack 4. Версия сборщика — часть условия эксперимента, а не примечание в конце.

Порядок проверки

  1. Выписать HTML-документы и назначить каждому ровно те entry, которые ему нужны.
  2. Зафиксировать версию webpack, webpack-cli, режим, source map и одинаковый коммит.
  3. Сохранить stats-before.json и stats-after.json одной командой сборки.
  4. Сравнить assets: новые файлы, изменение размеров и связанные chunks.
  5. Найти крупные модули с несколькими chunk-идентификаторами и связать их с конкретными entrypoints.
  6. Открыть обычную и административную страницы с очищенным кешем; проверить script-теги и Network.
  7. Изменить одну cache group или один HTML-маршрут, затем создать stats-fixed.json.
  8. Повторить ту же проверку для обеих страниц и отдельно пройти отрицательный путь: обычная страница не должна загружать admin-код.

Ограничения и критерий готовности

Stats показывает компиляцию, а не реальную стоимость передачи. Он не учитывает в полном объёме gzip или Brotli, HTTP-кеш, CDN, приоритеты загрузки и время исполнения. Network показывает запросы конкретного браузерного сценария, но не доказывает поведение всех страниц и устройств. Для производительности нужен отдельный замер, а не вывод из суммы файлов в dist.

Метод также не решает проблему неправильного контракта HTML, нескольких runtime или несовместимого загрузчика. Если две script-последовательности инициализируют один модуль независимо, оптимизация размера может оставить ошибку выполнения. Проверяйте порядок тегов, runtime и консоль браузера после изменения.

Учебные команды и конфиг выше не являются production-рецептом. Подставьте реальные пути проекта, зафиксируйте версию и подберите пороги по измерению. Не объявляйте оптимизацию успешной только потому, что сборка завершилась с кодом 0.

Готовность проверяема: stats-fixed.json подтверждает ожидаемое распределение модулей; обычный HTML не содержит admin-script; Network обычной страницы не запрашивает административный asset; административная страница получает все нужные chunks; обе страницы проходят загрузку без ошибок runtime. Если хотя бы одно условие не выполнено, причина не доказана.

Проверяемые источники

"}