2 lines
18 KiB
JSON
2 lines
18 KiB
JSON
{"index":343,"slug":"editorial-2018-06-field-webpack-entry","title":"Webpack 4: почему новый entry раздувает bundle и как это доказать","excerpt":"После добавления entry сборка может вырасти по ожидаемой причине, из-за дублирования модулей или из-за ошибочного HTML. Разбираем stats.json, граф chunks и сетевой след страницы, затем выбираем точечную настройку.","contentHtml":"<p>В типичном стендовом сценарии после коммита со второй точкой входа инженер запускает production-сборку и открывает обычную страницу. В <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> — файл, записанный в выходной каталог. Один entrypoint может ссылаться на несколько emitted-assets, а один chunk содержит множество modules. Сравнение только размеров файлов скрывает эту связь.</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-страницах шаблон должен подключить runtime перед нужным entry-asset. Ни одна из опций не исправляет неверный 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=stats-after.json\n# Затем верните прежнее значение entry и повторите ту же команду для stats-before.json.</code></pre><p>Команда должна создать валидный JSON-файл; не смешивайте его с обычным логом сборки. Параметр <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) => 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(','));</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://v4.webpack.js.org/configuration/entry-context/' target='_blank' rel='noopener noreferrer'>webpack: Entry and Context</a> — официальное описание entry и нескольких точек входа в Webpack 4. Синтаксис нужно сверять с конфигурацией проекта.</li><li><a href='https://v4.webpack.js.org/api/stats/' target='_blank' rel='noopener noreferrer'>webpack: Stats Data</a> — описание JSON-статистики Webpack 4, включая modules, chunks, entrypoints и экспорт через CLI. Набор полей зависит от версии и выбранных stats-опций.</li><li><a href='https://v4.webpack.js.org/guides/code-splitting/' target='_blank' rel='noopener noreferrer'>webpack: Code Splitting</a> — официальное руководство по разделению кода и настройке общих chunks в Webpack 4. Пример настройки нужно проверять на тестовой сборке.</li></ul>"}
|