Files
progcode/editorial/agent-rewrites/324.json
T

8 lines
18 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": 324,
"slug": "использование-jquery-в-webpack",
"title": "jQuery в Webpack: как связать модульный код и legacy-плагины",
"excerpt": "После миграции на Webpack плагин видит $ только на одной странице или получает другой объект jQuery. Разбираем границы ProvidePlugin, window.jQuery и externals и заканчиваем проверяемым критерием готовности.",
"contentHtml": "<p>В переходном проекте после переноса старого фронтенда на Webpack разработчик открывает страницу с формой телефона: кнопка с inputmask перестаёт работать. В консоли появляется <code>jQuery is not defined</code>, <code>$(...).inputmask is not a function</code> или плагин загружается только на одной странице. Это учебный сценарий, но симптом проверяемый: модульный код импортирует jQuery, а legacy-файл ищет её в <code>window</code>. Цена ошибки — сломанная форма, дублированная библиотека в бандлах и релиз, который нельзя подтвердить одной сборкой.</p>\n<p>Решение начинается с карты потребителей jQuery и границы каждого entry. Явный <code>import</code> связывает зависимость с графом модулей. <code>ProvidePlugin</code> подставляет экспорт модуля для свободных <code>$</code> и <code>jQuery</code>, а отдельное правило для <code>window.jQuery</code> задаёт глобальный контракт в обрабатываемом Webpack-коде. CDN и <code>externals</code> работают иначе: библиотека остаётся вне бандла, и окружение страницы обязано предоставить её до запуска потребителя.</p>\n<h2>Симптом начинается с границы видимости</h2>\n<p>Модуль с <code>import $ from 'jquery'</code> получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <code>&lt;script&gt;</code> в HTML увидит <code>window.$</code>. Обратное тоже верно: наличие <code>window.jQuery</code> не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.</p>\n<p>Для свободных <code>$</code> и <code>jQuery</code> работает <code>ProvidePlugin</code>. Webpack замечает идентификатор в модуле и автоматически подставляет экспорт пакета, поэтому код с <code>$(selector)</code> может собраться без строки <code>import</code>. Само правило для <code>$</code> или <code>jQuery</code> не означает, что внешний тег <code>script</code> получит глобал. Если нужен именно <code>window.jQuery</code>, его задают отдельным правилом ProvidePlugin или явным bootstrap-модулем.</p>\n<figure><img src=\"/assets/illustrations/jquery-webpack.svg\" alt=\"Схема связи jQuery, Webpack и legacy-плагина\" loading=\"lazy\" /><figcaption>Один entry должен доставить один объект jQuery раньше плагина, который его использует. Иллюстрация показывает границу между графом модулей и глобальным API браузера.</figcaption></figure>\n<h2>Три способа доставки</h2>\n<p>В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.</p>\n<table><thead><tr><th scope=\"col\">Способ</th><th scope=\"col\">Что получает код</th><th scope=\"col\">Когда подходит</th><th scope=\"col\">Чего он не делает</th></tr></thead><tbody><tr><td>Явный import</td><td>Локальный объект из графа Webpack</td><td>Новый код и код, который можно менять</td><td>Не создаёт <code>window.jQuery</code> без отдельного присваивания</td></tr><tr><td>ProvidePlugin</td><td>Автоматически подставленный импорт для свободного имени</td><td>Legacy-модули с <code>$</code> или <code>jQuery</code></td><td>Правило для <code>$</code> не управляет внешним script; порядок всё равно нужно проверить</td></tr><tr><td>externals или CDN</td><td>Зависимость вне бандла, которую предоставляет окружение</td><td>Одна контролируемая внешняя загрузка</td><td>Не включает jQuery в бандл и не проверяет URL, версию или доступность</td></tr></tbody></table>\n<p>Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает <code>ProvidePlugin</code>. Если плагин проверяет именно <code>window.jQuery</code>, выберите один явный способ: правило ProvidePlugin для глобального свойства или bootstrap-модуль до side effect плагина. Не смешивайте пакет и CDN в одном entry без причины: один бандл может использовать локальный пакет, а другой — внешний объект.</p>\n<h2>Минимальная конфигурация для переходного проекта</h2>\n<p>Ниже учебный пример. В нём два entry, один пакет <code>jquery</code> и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.</p>\n<pre><code>const webpack = require('webpack');\n\nmodule.exports = {\n entry: {\n legacy: './src/legacy-entry.js',\n modern: './src/modern-entry.js'\n },\n plugins: [\n new webpack.ProvidePlugin({\n $: 'jquery',\n jQuery: 'jquery'\n })\n ]\n};</code></pre>\n<p>В новом модуле зависимость остаётся видимой:</p>\n<pre><code>import $ from 'jquery';\n\nexport function mount(form) {\n return $(form).find('[data-mask]').length;\n}</code></pre>\n<p>В legacy-модуле строка импорта может отсутствовать:</p>\n<pre><code>$('.phone').inputmask('+7 (999) 999-99-99');</code></pre>\n<p>Два entry не обязаны автоматически использовать один экземпляр. Общий chunk, единый внешний объект или корректно настроенное разделение модулей могут убрать дубликат, но это нужно подтвердить в production-графе и в браузере. Совпадение версий в <code>package.json</code> само по себе такого доказательства не даёт.</p>\n<h2>Когда нужен window.jQuery</h2>\n<p>Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут <code>window.jQuery</code>. Для такого кода можно либо явно записать импортированный объект в <code>window</code>, либо поручить это ProvidePlugin в модулях, которые анализирует Webpack.</p><p>Явный bootstrap показывает порядок выполнения и подходит, когда точка запуска плагина находится под вашим контролем:</p>\n<pre><code>// src/jquery-global.js\nimport $ from 'jquery';\n\nwindow.$ = $;\nwindow.jQuery = $;\n\n// src/legacy-entry.js\nimport './jquery-global';\nimport 'inputmask/dist/jquery.inputmask';\nimport './legacy-app';</code></pre>\n<p>Если legacy-файл входит в граф Webpack, тот же глобальный контракт можно описать конфигурацией:</p>\n<pre><code>new webpack.ProvidePlugin({\n 'window.jQuery': 'jquery'\n});</code></pre>\n<p>Это правило следует проверять на собранном entry. Оно не загружает произвольный внешний script и не исправляет плагин, который запускается вне графа.</p>\n<p>Такой порядок относится к импортам внутри одного entry. Он не управляет отдельным CDN-скриптом, который HTML загрузит позже. В браузере проверьте именно <code>window.jQuery</code> перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.</p>\n<h2>Отрицательный путь: CDN и externals</h2>\n<p>Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:</p>\n<pre><code>module.exports = {\n externals: {\n jquery: 'jQuery'\n }\n};</code></pre>\n<p>Теперь <code>import $ from 'jquery'</code> в собранном коде означает обращение к внешнему <code>jQuery</code>. HTML обязан загрузить библиотеку раньше бандла:</p>\n<pre><code>&lt;script src=\"/assets/vendor/jquery-3.x.y.min.js\"&gt;&lt;/script&gt;\n&lt;script src=\"/assets/legacy.js\"&gt;&lt;/script&gt;</code></pre>\n<p>Путь в примере обозначает зафиксированную версию, которую отдаёт ваш vendor или CDN. Не подставляйте неизвестный URL в рабочую страницу: для production нужны закреплённая версия, проверка доступности, политика безопасности и понятный план отказа.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td><code>jQuery is not defined</code></td><td>Плагин выполняется до глобального объекта</td><td>Порядок Network и <code>typeof window.jQuery</code> перед импортом</td><td>Собрать entry с инициализатором или исправить порядок внешних scripts</td></tr><tr><td><code>$(...).inputmask is not a function</code></td><td>Плагин получил другой объект или не загрузился</td><td>Сравнить <code>window.jQuery === $</code> и проверить регистрацию <code>$.fn.inputmask</code></td><td>Оставить один источник jQuery и импортировать плагин после него</td></tr><tr><td>Работает только на одной странице</td><td>ProvidePlugin действует только в собранных модулях этого entry</td><td>Сравнить entry, HTML и содержимое chunk-файлов</td><td>Добавить зависимость в нужный entry, а не рассчитывать на соседний бандл</td></tr><tr><td>Размер растёт после добавления второго entry</td><td>Нет общего chunk или настроены две независимые поставки</td><td>Посмотреть stats и число включённых модулей jquery</td><td>Настроить общую доставку только после проверки поведения legacy-кода</td></tr><tr><td>CDN-версия ломает плагин</td><td>Версия или порядок внешней загрузки не совпадает с контрактом</td><td>Проверить фактический URL, версию и момент выполнения плагина</td><td>Зафиксировать совместимую версию или вернуть пакет в граф Webpack</td></tr></tbody></table>\n<h2>Порядок действий</h2>\n<ol><li>Найдите все обращения к <code>$</code>, <code>jQuery</code> и <code>window.jQuery</code>. Отдельно отметьте модули, которые выполняются сразу при импорте.</li><li>Для каждого entry выберите источник: пакет в графе Webpack или внешний глобальный script. Не оставляйте выбор на уровне случайного HTML-порядка.</li><li>Оставьте явный <code>import $ from 'jquery'</code> в новом коде. Добавьте <code>ProvidePlugin</code> только к переходному слою, который нельзя быстро изменить.</li><li>Если плагин читает <code>window.jQuery</code>, создайте один инициализатор и импортируйте его перед этим плагином.</li><li>Соберите каждый entry и проверьте, что в нём есть ожидаемая зависимость или явно описан <code>externals</code>.</li><li>Откройте реальную форму. Проверьте <code>window.jQuery</code>, <code>$.fn.jquery</code>, метод legacy-плагина и обработчик пользовательского события.</li><li>После миграции потребителя удалите лишнюю глобальную настройку и повторите проверку. Иначе временная совместимость станет постоянной зависимостью.</li></ol>\n<h2>Ограничения</h2>\n<p><code>ProvidePlugin</code> не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.</p>\n<p>Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с <code>noConflict</code>. Проверяйте реальный сценарий, а не только успешную компиляцию.</p>\n<p>В серверном рендеринге и Web Worker нет обычного <code>window</code>. Код, который без проверки обращается к <code>window.jQuery</code>, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://webpack.js.org/plugins/provide-plugin/\" target=\"_blank\" rel=\"noopener\">Webpack: ProvidePlugin</a> — описывает автоматическую подстановку модулей и пример с <code>$</code>, <code>jQuery</code> и <code>window.jQuery</code>.</li><li><a href=\"https://webpack.js.org/configuration/externals/\" target=\"_blank\" rel=\"noopener\">Webpack: externals</a> — фиксирует контракт зависимостей, которые остаются вне сборки.</li><li><a href=\"https://api.jquery.com/jQuery.noConflict/\" target=\"_blank\" rel=\"noopener\">jQuery API: jQuery.noConflict()</a> — описывает глобальные имена <code>$</code> и <code>jQuery</code>, конфликт библиотек и последствия загрузки двух версий.</li></ul>"
}