Files
progcode/editorial/agent-rewrites/323.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
17 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": 323,
"slug": "editorial-2019-01-mechanism-jquery-webpack",
"title": "jQuery в Webpack: почему legacy-плагин теряет глобальный объект",
"excerpt": "В development форма работает, а production-сборка получает undefined вместо window.jQuery. Разбираем разницу между ProvidePlugin и глобальным объектом, порядок запуска legacy-плагина и проверку фактических production-ассетов.",
"contentHtml": "<p>В development поле телефона принимает ввод, а после production-сборки браузер сообщает, что <code>window.jQuery</code> не определён. Иногда ошибка выглядит иначе: <code>jQuery</code> существует, но у поля нет метода старого плагина. Локальная форма работает, релизная — нет. Цена ошибки — сломанная форма на реальном маршруте и повторная сборка с догадками о порядке скриптов.</p>\n<p>Тезис простой: доступный в модуле идентификатор <code>$</code> и свойство <code>window.jQuery</code> — разные контракты. Webpack может подставить модуль в код, который обращается к свободному имени. Legacy-плагин может читать глобальный объект сразу при загрузке. Тогда важны не только пакет и конфигурация, но и момент выполнения каждого файла.</p>\n<h2>Сначала зафиксируйте симптом</h2>\n<p>Проверьте один и тот же сценарий в development и production: открыть страницу, найти поле, дождаться загрузки, ввести значение и посмотреть консоль. Запишите точное место отказа. Ошибка <code>window.jQuery is undefined</code> указывает на глобальный контракт. Ошибка <code>$(...).inputmask is not a function</code> может означать ранний запуск плагина, вторую копию jQuery или отсутствие регистрации метода.</p>\n<p>Учебный пример ниже использует старый jQuery-плагин, который выполняется во время загрузки файла. Это не утверждение о поведении каждой версии inputmask. Конкретный пакет нужно проверить в своей версии: прочитать entry файла, поставить остановку перед инициализацией и посмотреть, какое имя он читает.</p>\n<h2>Механизм: два слоя видимости</h2>\n<p>Модуль получает свои импорты через граф зависимостей. Глобальный объект живёт в окружении страницы. Когда код пишет <code>import $ from 'jquery'</code>, он получает локальную переменную. Эта строка сама по себе не обязана создать <code>window.$</code> или <code>window.jQuery</code>. Присваивание в <code>window</code> делает отдельный мост.</p>\n<p><code>ProvidePlugin</code> работает на этапе сборки. Webpack подставляет модуль, когда встречает свободный идентификатор в анализируемом модуле. Поэтому конфигурация с <code>$</code> и <code>jQuery</code> помогает исходному коду, который вызывает их без явного импорта. Если библиотека ищет именно <code>window.jQuery</code>, задайте этот контракт явно или создайте его в bootstrap-модуле.</p>\n<p>Порядок тоже имеет значение. Статический импорт объявляет зависимость до тела текущего модуля. Если импортированный плагин выполняет проверку сразу, он может прочитать глобал до строки, которая должна его создать. Вызов CommonJS <code>require()</code> в учебном примере расположен после присваивания, чтобы граница была видна в runtime. Это приём для legacy-шва, а не рекомендация строить новый код на глобальных переменных.</p>\n<pre><code>import $ from 'jquery';\n\nfunction exposeJQuery(jq) {\n if (window.jQuery &amp;&amp; window.jQuery !== jq) {\n throw new Error('Two jQuery instances reached the page');\n }\n\n window.$ = jq;\n window.jQuery = jq;\n}\n\nexposeJQuery($);\nrequire('inputmask/dist/jquery.inputmask');\nrequire('./legacy-form');</code></pre>\n<p>Пример учебный. Он предполагает, что плагин имеет CommonJS-совместимый вход и читает глобал во время загрузки. Если пакет экспортирует фабрику, требует вызова инициализации или использует другой путь, адаптируйте только точку подключения. Не переносите этот код в проект без проверки entry и версии зависимости.</p>\n<h2>Что делает ProvidePlugin</h2>\n<pre><code>const webpack = require('webpack');\n\nmodule.exports = {\n mode: 'production',\n entry: {\n site: './src/bootstrap-legacy.js',\n },\n plugins: [\n new webpack.ProvidePlugin({\n $: 'jquery',\n jQuery: 'jquery',\n }),\n ],\n};</code></pre>\n<p>В таком варианте Webpack обслуживает свободные имена внутри модулей, которые он анализирует. Конфигурация не доказывает, что в момент загрузки внешнего legacy-файла уже существует <code>window.jQuery</code>. Она также не доказывает, что HTML подключил одну копию jQuery. Поэтому после настройки проверяйте три факта отдельно: какое имя читает плагин, какой объект назначен глобалу и сколько экземпляров попало в production-граф.</p>\n<p>Если зависимость действительно требует глобальное свойство, ProvidePlugin можно настроить на <code>window.jQuery</code>. На старых сборках всё равно полезно оставить явный bootstrap: он показывает владельца глобала, позволяет проверить конфликт экземпляров и задаёт точку перед запуском plugin-кода. Оба подхода требуют проверки конкретного пакета и собранного результата.</p>\n<h2>Симптомы и минимальные проверки</h2>\n<table><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>В development работает, в production — <code>undefined</code></td><td>В dev глобал случайно создаёт layout или внешний script</td><td>Сравнить production HTML, Network и initiator</td><td>Убрать случайный источник или назначить глобал в явном bootstrap</td></tr><tr><td>В модуле есть <code>$</code>, плагин не видит <code>window.jQuery</code></td><td>ProvidePlugin подставил локальный идентификатор, но не выполнен нужный глобальный контракт</td><td>Поставить остановку перед загрузкой plugin и проверить <code>window.jQuery</code></td><td>Создать глобал до runtime-загрузки плагина или настроить точное сопоставление</td></tr><tr><td>После splitChunks метод пропал</td><td>HTML, runtime и chunks выпущены не одним набором или изменился порядок исполнения</td><td>Сверить имена фактических ассетов и stats-файл</td><td>Публиковать HTML и assets как один выпуск; не угадывать имя vendor-файла</td></tr><tr><td>На странице две копии jQuery</td><td>Одна пришла из layout или CDN, вторая — из bundle</td><td>Проверить <code>window.jQuery === $</code> и модули с именем <code>jquery</code></td><td>Оставить одного владельца и объяснить каждую копию в графе</td></tr><tr><td>Глобал есть, но метода нет</td><td>Плагин не загрузился, выполнился до моста или подключён не тот entry</td><td>Проверить Network, экспорт plugin и момент регистрации метода</td><td>Исправить точку подключения; не маскировать отказ повторным вызовом</td></tr></tbody></table>\n<figure><img src='/assets/editorial/2019/webpack-jquery-order-2019.svg' alt='Порядок загрузки production-ассетов: runtime, chunk с jQuery, bootstrap, legacy-плагин и форма'><figcaption>Иллюстрация показывает учебную модель: runtime и общий chunk загружаются, bootstrap назначает <code>window.jQuery</code>, затем запускается legacy-плагин. Красная ветка обозначает ранний запуск до создания глобала.</figcaption></figure>\n<h2>Проверяю собранный граф, а не только исходники</h2>\n<p>Исходный файл показывает намерение, но не фактический порядок production-страницы. После сборки откройте HTML и перечислите стартовые script-теги. Затем в DevTools проверьте запросы, initiator и ошибки выполнения. Если используется runtime Webpack, убедитесь, что он подгружает нужные chunks до вызова bootstrap. Не подставляйте вручную вчерашнее имя <code>vendors~site.js</code>: hashed-имя и набор chunks зависят от конфигурации.</p>\n<p>Для учебной проверки можно получить stats-файл и найти в нём модули jQuery:</p>\n<pre><code>webpack --mode production --profile --json &gt; dist/stats.json</code></pre>\n<p>Команда не выдаёт готовый диагноз. В stats-файле ищите все вхождения jQuery, связь с entry и причины появления chunks. Повторное вхождение требует объяснения, но само число строк не доказывает наличие двух runtime-экземпляров. Сопоставьте граф с проверкой объектов в браузере.</p>\n<h2>Порядок действий</h2>\n<ol><li>Зафиксируйте production-симптом на одном URL и одном сценарии формы.</li><li>Прочитайте исходный entry legacy-плагина и определите, читает ли он <code>window.jQuery</code>, свободное имя или экспорт функции.</li><li>Сравните development и production HTML, script-теги, runtime и chunks.</li><li>До запуска плагина проверьте <code>window.jQuery</code>, <code>window.$</code> и равенство глобала импортированному объекту.</li><li>Проверьте Network и initiator: все стартовые ассеты должны прийти без 404 и из одного выпуска.</li><li>Соберите stats-файл и найдите все модули jQuery, их entry и причины дублирования.</li><li>Если плагин читает глобал при загрузке, назначьте его в bootstrap и вызывайте runtime <code>require()</code> после присваивания.</li><li>После фикса очистите кэш, повторите открытие страницы и проверьте регистрацию метода, ввод в поле и отрицательный путь загрузки.</li></ol>\n<h2>Отрицательный путь и ограничения</h2>\n<p>Проверяйте не только успешную форму. Если legacy-плагин не загрузился, приложение не должно тихо показать видимость исправной маски. Добавьте явную ошибку в development, fallback для поля и сообщение, которое не блокирует ввод без необходимости. Если загрузка optional-части падает, основная форма должна сохранить понятное состояние.</p>\n<p>Глобальный jQuery остаётся техническим долгом. Новые модули лучше писать с явными импортами и локальными зависимостями. Не отключайте <code>splitChunks</code> только потому, что после миграции проявился сбой. Сначала докажите, что нарушен порядок, дублируется библиотека или HTML ссылается на несовместимый набор ассетов.</p>\n<p>Версии Webpack, формат пакета, loader и способ генерации HTML меняют детали. Поэтому статья не обещает фиксированное имя chunk и не утверждает конкретный production-результат. Учебная проверка применима только после сверки с версией проекта, исходником плагина и фактическим <code>dist</code>.</p>\n<h2>Критерий готовности</h2>\n<p>Исправление готово, когда один и тот же production-сценарий проходит после очистки кэша, <code>window.jQuery</code> равен ожидаемому экземпляру, legacy-плагин регистрирует нужный метод, а форма работает без внешнего случайного script. В Network нет 404, HTML и chunks принадлежат одному выпуску, а stats-файл объясняет каждую копию jQuery. Отказ optional-плагина не скрывает состояние формы.</p>\n<p>Если хотя бы один факт не подтверждён, результатом остаётся гипотеза. Не называйте её исправлением. Сначала вернитесь к моменту чтения глобала и отделите проблему видимости от проблемы порядка, графа или DOM.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://webpack.js.org/plugins/provide-plugin/' target='_blank' rel='noopener'>Webpack: ProvidePlugin</a> — правила автоматической подстановки модулей для свободных идентификаторов и пример с jQuery.</li><li><a href='https://webpack.js.org/guides/shimming/' target='_blank' rel='noopener'>Webpack: Shimming</a> — ограничения legacy-модулей и требования к анализу кода.</li><li><a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules' target='_blank' rel='noopener'>MDN: JavaScript modules</a> — область видимости модулей и порядок подготовки импортов к выполнению.</li></ul>"
}