Files
progcode/editorial/agent-rewrites/345.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

8 lines
15 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": 345,
"slug": "editorial-2018-06-practice-webpack-entry",
"title": "Webpack 4: две страницы, общие chunks и проверяемая сборка",
"excerpt": "Две HTML-страницы могут тянуть один и тот же код дважды. Разбираем границу entry, настройку splitChunks и проверку итоговых ассетов в Webpack 4.",
"contentHtml": "<p>После добавления страницы заказа production-сборка стала тяжелее. В каталоге появились <code>catalog.js</code> и <code>checkout.js</code>, а jQuery и модуль форматирования цены повторяются в обоих файлах. Пользователь каталога загружает код заказа, хотя не открывает заказ. Ошибка увеличивает первый запрос, усложняет кеширование и маскирует реальную границу страниц.</p>\n<p>Причина проста: два entry запускают два графа зависимостей. Webpack видит общий импорт из каждого графа, но не обязан сам превратить его в отдельный файл. Нужны два решения: entry должны описывать реальные точки запуска, а <code>optimization.splitChunks</code> — выделять код, который достигается из нескольких chunks. <strong>Общий модуль не становится общим только из-за имени папки <code>shared</code>.</strong></p>\n<h2>Что именно считает Webpack</h2>\n<p>Entry — это начало выполнения конкретного сценария. В многостраничном сайте у документа каталога есть свой entry, у документа заказа — свой. Каждый entry строит граф импортов. Если оба графа доходят до <code>src/shared/money.js</code>, модуль присутствует в обоих графах. Дальше оптимизатор сравнивает размер, число использований и правила cache group.</p>\n<p>Эти уровни нельзя смешивать. Entry отвечает на вопрос «какой сценарий стартует». <code>splitChunks</code> отвечает на вопрос «какие модули вынести в отдельный chunk». <code>runtimeChunk</code> отвечает за служебный runtime Webpack, который связывает модули и chunks. Третий entry с именем <code>common</code> не заменяет оптимизацию: он сам становится ещё одной точкой запуска.</p>\n<figure><img src=\"/assets/editorial/2018/webpack-entry-shared-chunks-2018.svg\" alt=\"Две страницы Webpack 4 используют общий runtime, vendors и common, но запускают разные entry\" /><figcaption>Схема показывает роли файлов. <code>catalog</code> и <code>checkout</code> запускают свои сценарии, а общие chunks подключаются к обеим страницам.</figcaption></figure>\n<h2>Минимальный граф зависимостей</h2>\n<p>Ниже учебный пример. Он показывает механизм, но не обещает конкретные размеры файлов и не заменяет сборку проекта. Оба сценария используют jQuery и одну функцию. Код страницы остаётся раздельным.</p>\n<pre><code>// src/catalog.js\nimport $ from 'jquery';\nimport { formatPrice } from './shared/money';\n\n$('[data-price]').each(function () {\n this.textContent = formatPrice(this.dataset.price);\n});\n\n// src/checkout.js\nimport $ from 'jquery';\nimport { formatPrice } from './shared/money';\n\n$('[data-total]').text(formatPrice(window.checkoutTotal));\n\n// src/shared/money.js\nexport function formatPrice(value) {\n return Number(value).toFixed(2) + ' ₽';\n}</code></pre>\n<p>Если собрать такой граф только с двумя entry, сборщик создаст стартовые chunks для каталога и заказа. Общий код может остаться внутри них. Это допустимый результат с точки зрения корректности: оба сценария получают нужный модуль. Но он может быть невыгоден для первой загрузки. Поэтому решение нужно принимать по списку ассетов и сетевому запросу, а не по названию исходной директории.</p>\n<h2>Конфигурация Webpack 4</h2>\n<p>В Webpack 4 оставьте в <code>entry</code> только реальные точки запуска. Для общих библиотек используйте <code>splitChunks</code>. В учебном примере <code>minSize: 0</code> помогает увидеть даже маленький модуль. В рабочей сборке это условие может создать отдельный запрос ради нескольких строк. Порог нужно проверить на размере и времени загрузки конкретного проекта.</p>\n<pre><code>// webpack.config.js\nconst path = require('path');\n\nmodule.exports = {\n mode: 'production',\n entry: {\n catalog: './src/catalog.js',\n checkout: './src/checkout.js',\n },\n output: {\n path: path.resolve(__dirname, 'dist'),\n filename: '[name].[contenthash].js',\n },\n optimization: {\n runtimeChunk: 'single',\n splitChunks: {\n chunks: 'all',\n cacheGroups: {\n vendors: {\n test: /[\\/]node_modules[\\/]/,\n name: 'vendors',\n chunks: 'all',\n priority: -10,\n },\n common: {\n name: 'common',\n minChunks: 2,\n minSize: 0,\n chunks: 'all',\n priority: -20,\n reuseExistingChunk: true,\n },\n },\n },\n },\n};</code></pre>\n<p>Группа <code>vendors</code> отбирает модули из <code>node_modules</code>. Группа <code>common</code> ищет код, который достигается как минимум из двух chunks. Приоритеты разрешают пересечение правил. <code>reuseExistingChunk: true</code> позволяет повторно использовать уже созданный chunk, когда это возможно.</p>\n<p><code>runtimeChunk: 'single'</code> выносит runtime в отдельный общий файл. Он не выносит jQuery и не делает общий chunk из <code>money.js</code>. За это отвечает <code>splitChunks</code>. Если убрать runtime из проверки, можно ошибочно решить, что два похожих служебных фрагмента — это дублирование прикладного кода.</p>\n<h2>Как страница подключает результат</h2>\n<p>Имена с <code>[contenthash]</code> меняются после содержательных изменений. HTML не должен навсегда содержать строку из примера. Шаблонизатор, плагин или серверная сборка должны получить актуальную карту ассетов. Важно также сохранить порядок: runtime, общие chunks, затем код конкретной страницы.</p>\n<pre><code>&lt;!-- catalog.html: имена условные, порядок показан явно --&gt;\n&lt;script src=\"/assets/runtime.8ab1.js\"&gt;&lt;/script&gt;\n&lt;script src=\"/assets/vendors.34cd.js\"&gt;&lt;/script&gt;\n&lt;script src=\"/assets/common.91ef.js\"&gt;&lt;/script&gt;\n&lt;script src=\"/assets/catalog.a2b3.js\"&gt;&lt;/script&gt;</code></pre>\n<p>Страница каталога не должна подключать <code>checkout</code>. Страница заказа не должна подключать <code>catalog</code>. Если общий chunk не появился, это не всегда ошибка: модуль мог быть слишком мал, использоваться только одним entry или не пройти условия cache group. Проверяйте фактическую карту сборки.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Оба entry заметно повторяют jQuery</td><td>Общий модуль не попал в подходящую cache group</td><td>Посмотреть stats и список модулей в chunks</td><td>Проверить <code>chunks</code>, <code>minChunks</code>, <code>minSize</code> и путь модуля</td></tr><tr><td>На странице заказа запускается каталог</td><td>HTML подключает соседний entry</td><td>Открыть вкладку Network и список script-тегов</td><td>Оставить в шаблоне только runtime, нужные общие chunks и <code>checkout</code></td></tr><tr><td>После сборки не найден общий файл</td><td>Код мал или нужен только одному entry</td><td>Проверить число достижений модуля в stats</td><td>Не создавать chunk ради имени; сравнить стоимость запроса и дублирования</td></tr><tr><td>В консоли ошибка при старте</td><td>Неверный порядок или устаревший hash в HTML</td><td>Очистить кеш, проверить ответы Network и порядок загрузки</td><td>Формировать HTML из актуальной карты ассетов</td></tr><tr><td>Общий chunk стал слишком большим</td><td>В него попал код, который нужен только редкому сценарию</td><td>Сопоставить состав chunk с первым экраном</td><td>Ослабить cache group или перенести поздний код в динамический <code>import()</code></td></tr></tbody></table></div>\n<h2>Порядок действий</h2>\n<ol><li>Перечислите HTML-документы и подтвердите, что каждый документ запускает отдельный сценарий.</li><li>Создайте по одному entry на документ. Не добавляйте в <code>entry</code> библиотеки только ради их общего имени.</li><li>Найдите повторяющийся импорт и соберите минимальный учебный граф с одним общим модулем.</li><li>Настройте <code>splitChunks</code> и отдельно решите, нужен ли единый <code>runtimeChunk</code>.</li><li>Сделайте production-сборку и сохраните stats или карту ассетов до изменения и после него.</li><li>Проверьте состав каждого HTML-документа. Сначала runtime и общие chunks, затем собственный entry.</li><li>Откройте каждую страницу с очищенным кешем. Проверьте консоль, Network и отсутствие кода соседнего сценария.</li><li>Повторите сборку после изменения одного общего модуля и убедитесь, что изменились только ожидаемые hash и chunks.</li></ol>\n<h2>Отрицательный путь и ограничения</h2>\n<p>Не всякий общий импорт нужно выносить. Маленький модуль может дешевле повторить, чем загружать новый файл. Большая библиотека может использоваться двумя entry, но быть нужна только после открытия модального окна. В таком случае общий стартовый chunk ухудшит первую загрузку. Для позднего кода рассмотрите динамический <code>import()</code> и проверяйте его отдельным сценарием.</p>\n<p>Эта схема относится к Webpack 4. В ней нет современного API <code>dependOn</code>, и нельзя переносить настройки из другой версии без сверки документации. Сборка также не исправит ошибку, если один HTML намеренно подключает два entry: нужно отдельно проверить количество runtime и порядок запуска.</p>\n<p>Учебный пример не доказывает выигрыш в production. На него влияют размер модулей, HTTP-протокол, кеш, HTML, который формирует сервер, и порядок загрузки. Сравнивайте измеренные ассеты и запросы своего проекта.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Считайте работу готовой, когда для каждой страницы можно показать четыре доказательства: entry, который запускает сценарий; список общих chunks; отсутствие entry соседней страницы; успешный запуск с очищенным кешем. Эти сведения должны совпадать в конфигурации, сгенерированном HTML, stats и вкладке Network. Размер одного стартового файла сам по себе не является критерием.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://v4.webpack.js.org/concepts/entry-points/\" target=\"_blank\" rel=\"noopener noreferrer\">Webpack 4: Entry Points</a></li><li><a href=\"https://v4.webpack.js.org/guides/code-splitting/\" target=\"_blank\" rel=\"noopener noreferrer\">Webpack 4: Code Splitting</a></li><li><a href=\"https://v4.webpack.js.org/configuration/optimization/\" target=\"_blank\" rel=\"noopener noreferrer\">Webpack 4: optimization</a></li></ul>"
}