8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"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 && 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 > 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>"
|
||
}
|