From e58e51cc41c159116625ed58a2fea98746c44e5e Mon Sep 17 00:00:00 2001
From: "E.Gavrilov" После добавления Тезис: Webpack строит граф от каждого entry. Один и тот же модуль попадает в два начальных bundle, когда оба графа до него доходят. Место файла в репозитории не определяет границу bundle. Сначала нужно установить реальные точки запуска. Затем — решить, какие общие части выделить через Entry задаёт начало обхода. Webpack читает entry, проходит его Объект с ключами Здесь Пример учебный. Он показывает направление рёбер и не сообщает размер bundle, время сборки или результат конкретного production-проекта. Размеры нужно измерять в своей версии Webpack и в одинаковом режиме. У массива другой контракт. Запись Третий вариант В Webpack 4 разделение задаёт После разделения в каждом entry всё ещё может быть служебный код Webpack. Runtime хранит сведения о модулях и загрузке chunks. Это не то же самое, что Если одна HTML-страница включает два runtime, импортированные модули могут инициализироваться в разных контекстах. Два script-тега не являются нейтральным способом «подключить ещё один модуль». Для одной страницы оставьте один настоящий старт, а дополнительное поведение импортируйте из него. Если код не нужен при первом открытии, рассмотрите динамический Ниже — пример проверки, а не production-результат. Снимки нужно получить одной версией локального Список модулей в нескольких chunks — повод для проверки, а не готовый диагноз. Один модуль может легитимно участвовать в начальном и асинхронном пути. Сопоставьте его с entrypoint. Затем откройте HTML и Network для каждой страницы. В Network видны реальные запросы, а stats описывает компиляцию. Не каждый общий модуль нужно выносить. Маленький модуль может добавить отдельный запрос и не дать выигрыша. Большая библиотека, нужная только модальному окну, не должна попадать в стартовый общий chunk. Для неё лучше проверить отложенную загрузку. Эта статья описывает модель Webpack 4. Современная документация содержит дополнительные поля entry, например Stats показывает компиляцию, но не доказывает скорость сети. Размер asset может отличаться от переданных байтов после сжатия и кеша. Network показывает один URL и один момент. Для вывода о performance нужны одинаковые условия измерения и отдельный критерий. Сборка готова, когда другой инженер без устного пояснения может назвать HTML-документ, его entry, общий chunk и причины его появления. В stats видны ожидаемые entrypoints. В HTML страницы нет чужого entry. В Network запрашиваются только runtime, общие chunks и код этой страницы. Для каждого вынесенного модуля есть объяснение размера и причины загрузки. Если один из этих ответов неизвестен, работу нельзя считать законченной. Сначала восстановите связь «HTML → entry → graph → chunk → запрос». Только после этого меняйте пороги, runtime или структуру импортов.admin.js production-сборка начинает отдавать два больших стартовых файла. В обоих находится date-format.js. Иногда обычная страница ещё и загружает скрипт админки. Цена ошибки — лишние байты в критическом пути, второй runtime и код, который браузер скачивает, но не выполняет. Если исправить только имя файла или перенести модуль в другую папку, причина останется.optimization.splitChunks, а какие оставить в странице или загрузить позже.Что именно означает entry
\nimport и require, затем повторяет обход для найденных модулей и ассетов. Так появляется внутренний граф зависимостей. Output-файл — только один из результатов этого графа.site и admin означает два самостоятельных старта. Обычно это верно для двух HTML-документов: сервер отдаёт каталог с одним сценарием и панель с другим. Если один HTML подключает оба entry, конфигурация описывает не две страницы, а два старта внутри одной страницы. Это отдельная ошибка.Минимальный пример
\n
\n// src/site.js\nimport { formatDate } from './shared/date-format';\nimport { mountSearch } from './site/search';\n\nmountSearch(formatDate);\n\n// src/admin.js\nimport { formatDate } from './shared/date-format';\nimport { mountReport } from './admin/report';\n\nmountReport(formatDate);\n\n// src/shared/date-format.js\nexport function formatDate(date) {\n return date.getFullYear() + '-'\n + String(date.getMonth() + 1).padStart(2, '0');\n}site/search нужен только сайту, а admin/report — только панели. shared/date-format достижим из обоих стартов. Webpack видит две цепочки, а не один «общий файл». До правила разделения общий модуль может попасть в каждый initial chunk.Массив entry не создаёт вторую страницу
\nentry: ['./src/polyfills.js', './src/site.js'] создаёт один multi-main entry. Webpack загружает файлы в указанном порядке и включает их зависимости в один стартовый граф. Это подходит для полифиллов или подготовительного кода, который всегда нужен сайту.
\nmodule.exports = {\n // Один entry и один стартовый граф.\n entry: ['./src/polyfills.js', './src/site.js'],\n\n // Два entry и два независимых старта.\n // Это имеет смысл при двух HTML-документах.\n // entry: {\n // site: './src/site.js',\n // admin: './src/admin.js',\n // },\n};vendor: ['jquery'] не делает библиотеку страницей. В Webpack 4 отдельный vendor entry — наследие старой схемы с CommonsChunkPlugin. Для общего кода используйте splitChunks, если это соответствует размеру и загрузке проекта. Не создавайте фиктивную точку запуска только для того, чтобы получить имя файла.Как разделить общий участок графа
\noptimization.splitChunks. Правило может искать модули, которые используются в нескольких chunks, и собирать их в отдельный chunk. Оно не обязано выносить каждый общий импорт: на решение влияют тип chunk, минимальный размер, лимиты запросов и cache group.
\nmodule.exports = {\n entry: {\n site: './src/site.js',\n admin: './src/admin.js',\n },\n optimization: {\n splitChunks: {\n chunks: 'all',\n cacheGroups: {\n common: {\n name: 'common',\n minChunks: 2,\n minSize: 0,\n chunks: 'all',\n },\n },\n },\n },\n};minSize: 0 стоит в примере только для видимости механизма. В рабочем проекте нулевой порог может создать слишком много маленьких запросов. Сначала найдите повторяющийся модуль и его размер. Потом выберите порог, который оправдан кешированием и числом запросов. Не принимайте появление файла common.js за доказательство ускорения.Runtime не равен общему модулю
\ndate-format.js или библиотека из node_modules. Для нескольких страниц можно отдельно рассмотреть runtimeChunk: 'single', но это решение меняет служебный слой, а не прикладную зависимость.import().Симптом → причина → проверка → действие
\n
\nСимптом Причина Проверка Действие Один модуль виден в двух initial chunks Оба entry достигают его, общего правила нет или оно не сработало Посмотреть modules, chunks и entrypoints в statsПроверить splitChunks, размер и cache groupВ dist появился admin.jsДобавился отдельный entry, а не обязательно лишняя загрузка Сверить script-теги HTML страницы сайта Убрать чужой entry из шаблона Массив entry приняли за две страницы Multi-main entry ошибочно смешали с object syntax Проверить число HTML-документов и ключей entry Оставить массив для одного старта или разделить страницы объектом После splitChunks выросло число файлов Порог слишком низкий или группа дробит мелкие модули Сравнить размер chunks, количество запросов и кеширование Поднять порог или сузить cache group Bundle большой, но страница его не запрашивает Ассет существует в сборке, но не входит в этот entrypoint Открыть Network и исходный HTML конкретного URL Не оптимизировать неиспользуемый страницей ассет Проверка на учебной сборке
\nwebpack-cli, в одном режиме и на одном наборе исходников. Сравнение development и production скрывает причину за минификацией, source map и разными плагинами.
\n./node_modules/.bin/webpack --mode production --profile --json > stats-after.json\n\nnode -e "const s=require('./stats-after.json');\nfor (const m of s.modules || []) {\n if ((m.chunks || []).length > 1) {\n console.log(m.size, m.name, m.chunks.join(','));\n }\n}"Порядок действий
\n
\nentry. Убедитесь, что массив означает подготовку одного старта, а объект — независимые страницы.splitChunks. Не создавайте vendor entry для библиотеки.import().Отрицательный путь и ограничения
\ndependOn и runtime. Их нельзя механически переносить в конфигурацию Webpack 4. Сначала определите версию сборщика и сверяйтесь с документацией этой версии.Критерий готовности
\nПроверяемые источники
\nadmin.js, после чего production-сборка стала отдавать два больших стартовых файла. Он открыл stats и заметил код общего модуля date-format в обоих entry. В браузере каталога заодно загрузился скрипт админки. Цена ошибки — лишние байты в критическом пути, второй runtime и код, который браузер скачивает, но не выполняет. Первая проверка должна ответить на вопрос: один HTML действительно запускает два entry или это ошибка шаблона?
Webpack строит граф от каждого entry. Один и тот же модуль попадает в два начальных bundle, когда оба графа до него доходят. Место файла в репозитории не определяет границу bundle. Сначала нужно установить реальные точки запуска. Затем — решить, какие общие части выделить через optimization.splitChunks, а какие оставить в странице или загрузить позже.
Сначала разработчик сверяет фактические <script>-теги в HTML каталога и панели. Затем он сопоставляет их с ключами entry и смотрит, какие модули входят в каждый граф. После этого становится видно, является ли повторение общего кода ожидаемым результатом двух страниц или одна страница ошибочно загружает чужой entry. Только после такой проверки имеет смысл менять конфигурацию.
Entry задаёт начало обхода. Webpack читает entry, проходит его import и require, затем повторяет обход для найденных модулей и ассетов. Так появляется внутренний граф зависимостей. Output-файл — только один из результатов этого графа.
Объект с ключами site и admin означает два самостоятельных старта. Обычно это верно для двух HTML-документов: сервер отдаёт каталог с одним сценарием и панель с другим. Если один HTML подключает оба entry, конфигурация описывает не две страницы, а два старта внутри одной страницы. Это отдельная ошибка.
// src/site.js\nimport { formatDate } from './shared/date-format';\nimport { mountSearch } from './site/search';\n\nmountSearch(formatDate);\n\n// src/admin.js\nimport { formatDate } from './shared/date-format';\nimport { mountReport } from './admin/report';\n\nmountReport(formatDate);\n\n// src/shared/date-format.js\nexport function formatDate(date) {\n return date.getFullYear() + '-'\n + String(date.getMonth() + 1).padStart(2, '0');\n}\nЗдесь site/search нужен только сайту, а admin/report — только панели. shared/date-format достижим из обоих стартов. Webpack видит две цепочки, а не один «общий файл». До правила разделения общий модуль может попасть в каждый initial chunk.
Пример учебный. Он показывает направление рёбер и не сообщает размер bundle, время сборки или результат конкретного production-проекта. Размеры нужно измерять в своей версии Webpack и в одинаковом режиме.
\nУ массива другой контракт. Запись entry: ['./src/polyfills.js', './src/site.js'] создаёт один multi-main entry. Webpack загружает файлы в указанном порядке и включает их зависимости в один стартовый граф. Это подходит для полифиллов или подготовительного кода, который всегда нужен сайту.
module.exports = {\n // Один entry и один стартовый граф.\n entry: ['./src/polyfills.js', './src/site.js'],\n\n // Два entry и два независимых старта.\n // Это имеет смысл при двух HTML-документах.\n // entry: {\n // site: './src/site.js',\n // admin: './src/admin.js',\n // },\n};\nТретий вариант vendor: ['jquery'] не делает библиотеку страницей. В Webpack 4 отдельный vendor entry — наследие старой схемы с CommonsChunkPlugin. Для общего кода используйте splitChunks, если это соответствует размеру и загрузке проекта. Не создавайте фиктивную точку запуска только для того, чтобы получить имя файла.
В Webpack 4 разделение задаёт optimization.splitChunks. Правило может искать модули, которые используются в нескольких chunks, и собирать их в отдельный chunk. Оно не обязано выносить каждый общий импорт: на решение влияют тип chunk, минимальный размер, лимиты запросов и cache group.
module.exports = {\n entry: {\n site: './src/site.js',\n admin: './src/admin.js',\n },\n optimization: {\n splitChunks: {\n chunks: 'all',\n cacheGroups: {\n common: {\n name: 'common',\n minChunks: 2,\n minSize: 0,\n chunks: 'all',\n },\n },\n },\n },\n};\nminSize: 0 стоит в примере только для видимости механизма. В рабочем проекте нулевой порог может создать слишком много маленьких запросов. Сначала найдите повторяющийся модуль и его размер. Потом выберите порог, который оправдан кешированием и числом запросов. Не принимайте появление файла common.js за доказательство ускорения.
После разделения в каждом entry всё ещё может быть служебный код Webpack. Runtime хранит сведения о модулях и загрузке chunks. Это не то же самое, что date-format.js или библиотека из node_modules. Для нескольких страниц можно отдельно рассмотреть runtimeChunk: 'single', но это решение меняет служебный слой, а не прикладную зависимость.
Если одна HTML-страница включает два runtime, импортированные модули могут инициализироваться в разных контекстах. Два script-тега не являются нейтральным способом «подключить ещё один модуль». Для одной страницы оставьте один настоящий старт, а дополнительное поведение импортируйте из него. Если код не нужен при первом открытии, рассмотрите динамический import().
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Один модуль виден в двух initial chunks | Оба entry достигают его, общего правила нет или оно не сработало | Посмотреть modules, chunks и entrypoints в stats | Проверить splitChunks, размер и cache group |
В dist появился admin.js | Добавился отдельный entry, а не обязательно лишняя загрузка | Сверить script-теги HTML страницы сайта | Убрать чужой entry из шаблона |
| Массив entry приняли за две страницы | Multi-main entry ошибочно смешали с object syntax | Проверить число HTML-документов и ключей entry | Оставить массив для одного старта или разделить страницы объектом |
| После splitChunks выросло число файлов | Порог слишком низкий или группа дробит мелкие модули | Сравнить размер chunks, количество запросов и кеширование | Поднять порог или сузить cache group |
| Bundle большой, но страница его не запрашивает | Ассет существует в сборке, но не входит в этот entrypoint | Открыть Network и исходный HTML конкретного URL | Не оптимизировать неиспользуемый страницей ассет |
Ниже — пример проверки, а не production-результат. Снимки нужно получить одной версией локального webpack-cli, в одном режиме и на одном наборе исходников. Сравнение development и production скрывает причину за минификацией, source map и разными плагинами.
./node_modules/.bin/webpack --mode production --profile --json > stats-after.json\n\nnode -e "const s=require('./stats-after.json');\nfor (const m of s.modules || []) {\n if ((m.chunks || []).length > 1) {\n console.log(m.size, m.name, m.chunks.join(','));\n }\n}"\nСписок модулей в нескольких chunks — повод для проверки, а не готовый диагноз. Один модуль может легитимно участвовать в начальном и асинхронном пути. Сопоставьте его с entrypoint. Затем откройте HTML и Network для каждой страницы. В Network видны реальные запросы, а stats описывает компиляцию.
\nentry. Убедитесь, что массив означает подготовку одного старта, а объект — независимые страницы.splitChunks. Не создавайте vendor entry для библиотеки.import().Не каждый общий модуль нужно выносить. Маленький модуль может добавить отдельный запрос и не дать выигрыша. Большая библиотека, нужная только модальному окну, не должна попадать в стартовый общий chunk. Для неё лучше проверить отложенную загрузку.
\nЭта статья описывает модель Webpack 4. Современная документация содержит дополнительные поля entry, например dependOn и runtime. Их нельзя механически переносить в конфигурацию Webpack 4. Сначала определите версию сборщика и сверяйтесь с документацией этой версии.
Stats показывает компиляцию, но не доказывает скорость сети. Размер asset может отличаться от переданных байтов после сжатия и кеша. Network показывает один URL и один момент. Для вывода о performance нужны одинаковые условия измерения и отдельный критерий.
\nСборка готова, когда другой инженер без устного пояснения может назвать HTML-документ, его entry, общий chunk и причины его появления. В stats видны ожидаемые entrypoints. В HTML страницы нет чужого entry. В Network запрашиваются только runtime, общие chunks и код этой страницы. Для каждого вынесенного модуля есть объяснение размера и причины загрузки.
\nЕсли один из этих ответов неизвестен, работу нельзя считать законченной. Сначала восстановите связь «HTML → entry → graph → chunk → запрос». Только после этого меняйте пороги, runtime или структуру импортов.
\n