Files
progcode/editorial/agent-rewrites/343.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

2 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"index":343,"slug":"editorial-2018-06-field-webpack-entry","title":"Webpack 4: почему новый entry раздувает bundle и как это доказать","excerpt":"После добавления entry сборка может вырасти по ожидаемой причине, из-за дублирования модулей или из-за ошибочного HTML. Разбираем stats.json, граф chunks и сетевой след страницы, затем выбираем точечную настройку.","contentHtml":"<p>Симптом появляется сразу после добавления второй точки входа: в <code>dist</code> возникает новый <code>admin.[contenthash].js</code>, а <code>site.[contenthash].js</code> тоже становится тяжелее. Иногда обычная страница ещё и запрашивает административный файл. Пользователь скачивает код, которым не воспользуется, а команда начинает менять <code>splitChunks</code> вслепую. Цена ошибки — лишний трафик в критическом пути, более долгий первый запуск и риск получить сломанный runtime.</p><p>Тезис простой: новый entry сам по себе не доказывает дублирование. Он добавляет новый старт в граф зависимостей. Дублирование возникает, когда один модуль достижим из нескольких начальных chunks и сборка не вынесла его в общий chunk. Отдельная причина — неправильный список <code>script</code> в HTML. Поэтому нужно проверить три слоя: emitted-ассеты, связи modules/chunks и реальные запросы страницы.</p><h2>Что именно делает entry</h2><p>Webpack начинает обход графа с каждой точки входа. Для <code>site</code> он проходит импорты страницы, для <code>admin</code> — импорты панели. Если обе ветки доходят до одного пакета, например <code>react</code> или общего модуля приложения, этот пакет входит в область обеих веток. В Webpack 4 он не обязан автоматически стать одним отдельным файлом для initial chunks.</p><p>Важно различать entry, chunk и asset. <code>entry</code> — старт обхода. <code>chunk</code> — внутренняя группа модулей, которую Webpack планирует загрузить вместе. <code>asset</code> — файл, записанный в выходной каталог. HTML может подключить несколько assets одного entrypoint, а один asset может быть частью отношения с несколькими chunks. Сравнение только размеров файлов скрывает эту связь.</p><p>Предположим, приложение обслуживает две HTML-страницы. Обычная страница должна загружать <code>site</code>, административная — <code>admin</code>. Учебная конфигурация ниже показывает модель. Она не утверждает, что такой порог или имя cache group подходят конкретному проекту.</p><pre><code>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' } };</code></pre><p>В этом примере <code>chunks: 'all'</code> расширяет область работы оптимизатора, а <code>runtimeChunk: 'single'</code> выносит служебный runtime в общий файл. Ни одна из опций не исправляет неверный HTML. Если шаблон подключает <code>admin</code> на <code>/</code>, браузер скачает его независимо от того, насколько аккуратно собран граф.</p><h2>Сначала фиксирую сравнение</h2><p>Снимки нужно делать в одинаковых условиях. Режим, минификация, source map, версия webpack, версия webpack-cli и плагины меняют результат сильнее, чем небольшая правка entry. Возьмите один коммит и сохраните два файла: <code>stats-before.json</code> до добавления entry и <code>stats-after.json</code> после него. Запускайте локальный бинарник проекта, а не случайную глобальную версию.</p><pre><code>./node_modules/.bin/webpack --mode production --profile --json &gt; stats-after.json\n# Затем верните прежнее значение entry и повторите ту же команду для stats-before.json.</code></pre><p>Снимок должен быть валидным JSON. Логи из конфигурации не должны попадать в stdout. Параметр <code>--profile</code> добавляет время сборки по модулям. Для ответа о размере он необязателен, но полезен, если новый entry одновременно замедлил компиляцию.</p><figure><img src='/assets/editorial/2018/webpack-entry-diagnosis-2018.svg' alt='Схема диагностики Webpack: два entry ведут к chunks и assets, затем HTML и Network подтверждают фактическую загрузку' loading='lazy' /><figcaption>Stats описывает результат компиляции. HTML и Network показывают, какие файлы получает конкретная страница. Нужны оба наблюдения.</figcaption></figure><h2>Читаю stats по слоям</h2><p>Первый слой — список <code>assets</code>. Сравните имя, размер и принадлежность к chunks. Новый <code>admin.[contenthash].js</code> ожидаем: у новой страницы должен появиться собственный код. Вопрос начинается там, где старый initial asset вырос или в нём повторился крупный модуль.</p><p>Второй слой — <code>modules</code>. Найдите модули, у которых массив <code>chunks</code> содержит больше одного идентификатора. Это сильный сигнал повторной достижимости, но не окончательный вывод о сетевой загрузке. Модуль может находиться в async chunk, в runtime-связи или в структуре, которую браузер не запрашивает на данной странице.</p><p>Учебный скрипт ниже печатает кандидатов на повтор. Он рассчитан на форму stats, которую выдаёт совместимая версия Webpack 4. Формат stats меняется между версиями, поэтому перед применением проверьте поля своего файла.</p><pre><code>const fs = require('fs'); const stats = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); const repeated = (stats.modules || []).filter((module) =&gt; Array.isArray(module.chunks) &amp;&amp; module.chunks.length &gt; 1).sort((a, b) =&gt; (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(','));</code></pre><p>Третий слой — <code>entrypoints</code> и <code>chunks</code>. Свяжите найденный модуль с конкретными стартами. Если общий пакет нужен обеим страницам, вынесение может уменьшить повтор в initial assets. Если модуль нужен только <code>admin</code>, переносить его в vendors нельзя: обычная страница начнёт загружать чужой код.</p><h2>Симптомы и точечные действия</h2><div class='table-scroll'><table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope='col'>Симптом</th><th scope='col'>Вероятная причина</th><th scope='col'>Проверка</th><th scope='col'>Действие</th></tr></thead><tbody><tr><td>Появился новый <code>admin</code>-asset, а <code>site</code> почти не изменился</td><td>Добавилась отдельная страница, а не дублирование</td><td>Сравнить assets и список файлов обычного HTML</td><td>Оставить entry; не менять <code>splitChunks</code> без повторного модуля</td></tr><tr><td><code>site</code> вырос, крупный пакет есть в двух initial chunks</td><td>Обе точки входа достигают общий модуль</td><td>Сопоставить <code>modules[].chunks</code> с entrypoints</td><td>Проверить cache group или явную зависимость; пересобрать</td></tr><tr><td>Обычная страница запрашивает <code>admin</code></td><td>Шаблон или HTML-плагин подключает чужой entry</td><td>Посмотреть script-теги и Network на <code>/</code></td><td>Исправить карту assets страницы до оптимизации chunks</td></tr><tr><td>Размер вырос только в development</td><td>Сравниваются разные режимы, source map или профили</td><td>Повторить два production-снимка одной командой</td><td>Считать выводом только сопоставимый результат</td></tr><tr><td>Модуль отмечен в нескольких chunks, но запросов больше не стало</td><td>Сигнал относится к графу, а не к загрузке выбранной страницы</td><td>Проверить entrypoint и Network с пустым кешем</td><td>Не выносить модуль автоматически; оценить его реальную загрузку</td></tr></tbody></table></div><h2>Когда менять splitChunks</h2><p>Настройка оправдана после двух доказательств. Во-первых, один и тот же достаточно крупный код действительно принадлежит двум нужным начальным путям. Во-вторых, каждая страница сможет получить общий chunk без лишнего запроса или ошибки runtime. Размер общего файла сам по себе не задаёт выгоду: один дополнительный запрос может оказаться дороже небольшого повторения.</p><p>Для пакетов из <code>node_modules</code> часто начинают с отдельной cache group. Для прикладного общего кода сначала проверьте его границу. Если модуль нужен только редкому действию внутри панели, динамический <code>import()</code> может быть лучше начального общего chunk. Если две страницы имеют разные сроки жизни кеша, единый файл может чаще инвалидироваться и ухудшить повторные визиты.</p><p>В старой конфигурации Webpack 4 нельзя механически копировать пример из свежей документации. Сверьте доступные опции и фактический формат stats. Например, современная схема <code>dependOn</code> и настройки runtime могут отличаться от проекта на Webpack 4. Версия сборщика — часть условия эксперимента, а не примечание в конце.</p><h2>Порядок проверки</h2><ol><li>Выписать HTML-документы и назначить каждому ровно те entry, которые ему нужны.</li><li>Зафиксировать версию webpack, webpack-cli, режим, source map и одинаковый коммит.</li><li>Сохранить <code>stats-before.json</code> и <code>stats-after.json</code> одной командой сборки.</li><li>Сравнить assets: новые файлы, изменение размеров и связанные chunks.</li><li>Найти крупные модули с несколькими chunk-идентификаторами и связать их с конкретными entrypoints.</li><li>Открыть обычную и административную страницы с очищенным кешем; проверить script-теги и Network.</li><li>Изменить одну cache group или один HTML-маршрут, затем создать <code>stats-fixed.json</code>.</li><li>Повторить ту же проверку для обеих страниц и отдельно пройти отрицательный путь: обычная страница не должна загружать admin-код.</li></ol><h2>Ограничения и критерий готовности</h2><p>Stats показывает компиляцию, а не реальную стоимость передачи. Он не учитывает в полном объёме gzip или Brotli, HTTP-кеш, CDN, приоритеты загрузки и время исполнения. Network показывает запросы конкретного браузерного сценария, но не доказывает поведение всех страниц и устройств. Для производительности нужен отдельный замер, а не вывод из суммы файлов в <code>dist</code>.</p><p>Метод также не решает проблему неправильного контракта HTML, нескольких runtime или несовместимого загрузчика. Если две script-последовательности инициализируют один модуль независимо, оптимизация размера может оставить ошибку выполнения. Проверяйте порядок тегов, runtime и консоль браузера после изменения.</p><p>Учебные команды и конфиг выше не являются production-рецептом. Подставьте реальные пути проекта, зафиксируйте версию и подберите пороги по измерению. Не объявляйте оптимизацию успешной только потому, что сборка завершилась с кодом 0.</p><p>Готовность проверяема: <code>stats-fixed.json</code> подтверждает ожидаемое распределение модулей; обычный HTML не содержит <code>admin</code>-script; Network обычной страницы не запрашивает административный asset; административная страница получает все нужные chunks; обе страницы проходят загрузку без ошибок runtime. Если хотя бы одно условие не выполнено, причина не доказана.</p><h2>Проверяемые источники</h2><ul><li><a href='https://webpack.js.org/configuration/entry-context/' target='_blank' rel='noopener noreferrer'>webpack: Entry and Context</a> — официальное описание entry, массивов entry и связи начальной точки с графом модулей. Синтаксис нужно сверять с версией сборщика проекта.</li><li><a href='https://webpack.js.org/api/stats/' target='_blank' rel='noopener noreferrer'>webpack: Stats Data</a> — официальное описание JSON-статистики, modules, chunks и команды экспорта. Структура отдельных полей может зависеть от версии и выбранных stats-опций.</li><li><a href='https://webpack.js.org/guides/code-splitting/' target='_blank' rel='noopener noreferrer'>webpack: Code Splitting</a> — официальные варианты разделения кода через entry, динамический import и оптимизацию chunks. Пример настройки нужно проверять на тестовой сборке.</li></ul>"}