From 9170c7af163bffa650b0799ab62b1d84e4fb83e9 Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Fri, 4 Sep 2026 00:10:20 +0300 Subject: [PATCH] Editorial: refine article 324 jQuery and Webpack --- editorial/agent-rewrites/324.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/editorial/agent-rewrites/324.json b/editorial/agent-rewrites/324.json index f4dc574..65a0a76 100644 --- a/editorial/agent-rewrites/324.json +++ b/editorial/agent-rewrites/324.json @@ -3,8 +3,5 @@ "slug": "использование-jquery-в-webpack", "title": "jQuery в Webpack: как связать модульный код и legacy-плагины", "excerpt": "После миграции на Webpack плагин видит $ только на одной странице или получает другой объект jQuery. Разбираем границы ProvidePlugin, window.jQuery и externals и заканчиваем проверяемым критерием готовности.", - "contentHtml": "

После переноса старого фронтенда в Webpack кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на странице с «правильным» порядком скриптов. Ошибка часто выглядит случайной: новый модуль импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в нескольких бандлах и релиз, который нельзя уверенно проверить одной сборкой.

\n

Тезис простой: сначала нужно назвать потребителя jQuery, затем выбрать один способ доставки для каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin помогает старому коду, который обращается к свободным $ или jQuery. Глобальный объект нужен только тем потребителям, которые действительно читают window.jQuery. CDN и externals образуют другой контракт: Webpack больше не кладёт библиотеку в бандл и ждёт готового глобального объекта снаружи.

\n

Симптом начинается с границы видимости

\n

Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.

\n

ProvidePlugin решает третий случай. Webpack замечает свободный идентификатор в модуле и автоматически подставляет импорт. Поэтому код с $(selector) может собраться без строки import. Это не запись переменной в глобальный объект браузера и не исправление порядка независимых тегов script.

\n
\"Схема
Один entry должен доставить один объект jQuery раньше плагина, который его использует. Иллюстрация показывает границу между графом модулей и глобальным API браузера.
\n

Три способа доставки

\n

В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.

\n
СпособЧто получает кодКогда подходитЧего он не делает
Явный importЛокальный объект из графа WebpackНовый код и код, который можно менятьНе создаёт window.jQuery сам по себе
ProvidePluginАвтоматически подставленный импорт для свободного имениLegacy-модули с $ или jQueryНе чинит внешний script и не гарантирует порядок загрузки
externals или CDNГлобальный объект, предоставленный HTML или платформойОдна контролируемая внешняя загрузкаНе включает jQuery в бандл и не проверяет URL CDN
\n

Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, добавьте глобальный экспорт в одном entry. Не смешивайте этот режим с CDN без причины. Иначе один бандл будет использовать пакет, а другой — внешний файл.

\n

Минимальная конфигурация для переходного проекта

\n

Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.

\n
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};
\n

В новом модуле зависимость остаётся видимой:

\n
import $ from 'jquery';\n\nexport function mount(form) {\n  return $(form).find('[data-mask]').length;\n}
\n

В legacy-модуле строка импорта может отсутствовать:

\n
$('.phone').inputmask('+7 (999) 999-99-99');
\n

Оба фрагмента должны ссылаться на один экземпляр, если Webpack собирает их в общий runtime или правильно выносит общий модуль. Это нужно проверить в фактической конфигурации. Само совпадение версий в package.json такого доказательства не даёт.

\n

Когда нужен window.jQuery

\n

Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода сделайте отдельный модуль-инициализатор и импортируйте его до плагина:

\n
// 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';
\n

Такой порядок относится к импортам внутри одного entry. Он не управляет отдельным CDN-скриптом, который HTML загрузит позже. В браузере проверьте именно window.jQuery перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.

\n

Отрицательный путь: CDN и externals

\n

Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:

\n
module.exports = {\n  externals: {\n    jquery: 'jQuery'\n  }\n};
\n

Теперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:

\n
<script src=\"https://cdn.example.test/jquery.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>
\n

URL в примере учебный. Не подставляйте его в рабочую страницу. Для production нужны зафиксированная версия, контроль доступности, политика безопасности и понятный план отказа. Если CDN недоступен, Webpack не сможет компенсировать это настройкой ProvidePlugin. Если браузер загрузит две версии jQuery, плагины могут зарегистрироваться в одном объекте, а приложение — работать с другим.

\n

Симптом → причина → проверка → действие

\n
СимптомПричинаПроверкаДействие
jQuery is not definedПлагин выполняется до глобального объектаПорядок Network и typeof window.jQuery перед импортомСобрать entry с инициализатором или исправить порядок внешних scripts
$(...).inputmask is not a functionПлагин получил другой объект или не загрузилсяСравнить window.jQuery === $ и проверить регистрацию $.fn.inputmaskОставить один источник jQuery и импортировать плагин после него
Работает только на одной страницеProvidePlugin действует только в собранных модулях этого entryСравнить entry, HTML и содержимое chunk-файловДобавить зависимость в нужный entry, а не рассчитывать на соседний бандл
Размер растёт после добавления второго entryНет общего chunk или настроены две независимые поставкиПосмотреть stats и число включённых модулей jqueryНастроить общую доставку только после проверки поведения legacy-кода
CDN-версия ломает плагинВерсия или порядок внешней загрузки не совпадает с контрактомПроверить фактический URL, версию и момент выполнения плагинаЗафиксировать совместимую версию или вернуть пакет в граф Webpack
\n

Порядок действий

\n
  1. Найдите все обращения к $, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.
  2. Для каждого entry выберите источник: пакет в графе Webpack или внешний глобальный script. Не оставляйте выбор на уровне случайного HTML-порядка.
  3. Оставьте явный import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.
  4. Если плагин читает window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.
  5. Соберите каждый entry и проверьте, что в нём есть ожидаемая зависимость или явно описан externals.
  6. Откройте реальную форму. Проверьте window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.
  7. После миграции потребителя удалите лишнюю глобальную настройку и повторите проверку. Иначе временная совместимость станет постоянной зависимостью.
\n

Ограничения

\n

ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.

\n

Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.

\n

В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.

\n

Проверяемый критерий готовности

\n

Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.

\n

Проверяемые источники

\n", - "contentHtml": "

После переноса старого фронтенда в Webpack кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на странице с «правильным» порядком скриптов. Ошибка часто выглядит случайной: новый модуль импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в нескольких бандлах и релиз, который нельзя уверенно проверить одной сборкой.

\n

Тезис простой: сначала нужно назвать потребителя jQuery, затем выбрать один способ доставки для каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin помогает старому коду, который обращается к свободным $ или jQuery. Глобальный объект нужен только тем потребителям, которые действительно читают window.jQuery. CDN и externals образуют другой контракт: Webpack больше не кладёт библиотеку в бандл и ждёт готового глобального объекта снаружи.

\n

Симптом начинается с границы видимости

\n

Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.

\n

ProvidePlugin решает третий случай. Webpack замечает свободный идентификатор в модуле и автоматически подставляет импорт. Поэтому код с $(selector) может собраться без строки import. Это не запись переменной в глобальный объект браузера и не исправление порядка независимых тегов script.

\n
\"Схема
Один entry должен доставить один объект jQuery раньше плагина, который его использует. Иллюстрация показывает границу между графом модулей и глобальным API браузера.
\n

Три способа доставки

\n

В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.

\n
СпособЧто получает кодКогда подходитЧего он не делает
Явный importЛокальный объект из графа WebpackНовый код и код, который можно менятьНе создаёт window.jQuery сам по себе
ProvidePluginАвтоматически подставленный импорт для свободного имениLegacy-модули с $ или jQueryНе чинит внешний script и не гарантирует порядок загрузки
externals или CDNГлобальный объект, предоставленный HTML или платформойОдна контролируемая внешняя загрузкаНе включает jQuery в бандл и не проверяет URL CDN
\n

Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, добавьте глобальный экспорт в одном entry. Не смешивайте этот режим с CDN без причины. Иначе один бандл будет использовать пакет, а другой — внешний файл.

\n

Минимальная конфигурация для переходного проекта

\n

Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.

\n
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};
\n

В новом модуле зависимость остаётся видимой:

\n
import $ from 'jquery';\n\nexport function mount(form) {\n  return $(form).find('[data-mask]').length;\n}
\n

В legacy-модуле строка импорта может отсутствовать:

\n
$('.phone').inputmask('+7 (999) 999-99-99');
\n

Оба фрагмента должны ссылаться на один экземпляр, если Webpack собирает их в общий runtime или правильно выносит общий модуль. Это нужно проверить в фактической конфигурации. Само совпадение версий в package.json такого доказательства не даёт.

\n

Когда нужен window.jQuery

\n

Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода сделайте отдельный модуль-инициализатор и импортируйте его до плагина:

\n
// 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';
\n

Такой порядок относится к импортам внутри одного entry. Он не управляет отдельным CDN-скриптом, который HTML загрузит позже. В браузере проверьте именно window.jQuery перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.

\n

Отрицательный путь: CDN и externals

\n

Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:

\n
module.exports = {\n  externals: {\n    jquery: 'jQuery'\n  }\n};
\n

Теперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:

\n
<script src=\"https://cdn.example.test/jquery.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>
\n

URL в примере учебный. Не подставляйте его в рабочую страницу. Для production нужны зафиксированная версия, контроль доступности, политика безопасности и понятный план отказа. Если CDN недоступен, Webpack не сможет компенсировать это настройкой ProvidePlugin. Если браузер загрузит две версии jQuery, плагины могут зарегистрироваться в одном объекте, а приложение — работать с другим.

\n

Симптом → причина → проверка → действие

\n
СимптомПричинаПроверкаДействие
jQuery is not definedПлагин выполняется до глобального объектаПорядок Network и typeof window.jQuery перед импортомСобрать entry с инициализатором или исправить порядок внешних scripts
$(...).inputmask is not a functionПлагин получил другой объект или не загрузилсяСравнить window.jQuery === $ и проверить регистрацию $.fn.inputmaskОставить один источник jQuery и импортировать плагин после него
Работает только на одной страницеProvidePlugin действует только в собранных модулях этого entryСравнить entry, HTML и содержимое chunk-файловДобавить зависимость в нужный entry, а не рассчитывать на соседний бандл
Размер растёт после добавления второго entryНет общего chunk или настроены две независимые поставкиПосмотреть stats и число включённых модулей jqueryНастроить общую доставку только после проверки поведения legacy-кода
CDN-версия ломает плагинВерсия или порядок внешней загрузки не совпадает с контрактомПроверить фактический URL, версию и момент выполнения плагинаЗафиксировать совместимую версию или вернуть пакет в граф Webpack
\n

Порядок действий

\n
  1. Найдите все обращения к $, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.
  2. Для каждого entry выберите источник: пакет в графе Webpack или внешний глобальный script. Не оставляйте выбор на уровне случайного HTML-порядка.
  3. Оставьте явный import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.
  4. Если плагин читает window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.
  5. Соберите каждый entry и проверьте, что в нём есть ожидаемая зависимость или явно описан externals.
  6. Откройте реальную форму. Проверьте window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.
  7. После миграции потребителя удалите лишнюю глобальную настройку и повторите проверку. Иначе временная совместимость станет постоянной зависимостью.
\n

Ограничения

\n

ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.

\n

Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.

\n

В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.

\n

Проверяемый критерий готовности

\n

Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.

\n

Проверяемые источники

\n", - "contentHtml": "

После переноса старого фронтенда в Webpack кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на странице с «правильным» порядком скриптов. Ошибка часто выглядит случайной: новый модуль импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в нескольких бандлах и релиз, который нельзя уверенно проверить одной сборкой.

\n

Тезис простой: сначала нужно назвать потребителя jQuery, затем выбрать один способ доставки для каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin помогает старому коду, который обращается к свободным $ или jQuery. Глобальный объект нужен только тем потребителям, которые действительно читают window.jQuery. CDN и externals образуют другой контракт: Webpack больше не кладёт библиотеку в бандл и ждёт готового глобального объекта снаружи.

\n

Симптом начинается с границы видимости

\n

Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.

\n

ProvidePlugin решает третий случай. Webpack замечает свободный идентификатор в модуле и автоматически подставляет импорт. Поэтому код с $(selector) может собраться без строки import. Это не запись переменной в глобальный объект браузера и не исправление порядка независимых тегов script.

\n
\"Схема
Один entry должен доставить один объект jQuery раньше плагина, который его использует. Иллюстрация показывает границу между графом модулей и глобальным API браузера.
\n

Три способа доставки

\n

В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.

\n
СпособЧто получает кодКогда подходитЧего он не делает
Явный importЛокальный объект из графа WebpackНовый код и код, который можно менятьНе создаёт window.jQuery сам по себе
ProvidePluginАвтоматически подставленный импорт для свободного имениLegacy-модули с $ или jQueryНе чинит внешний script и не гарантирует порядок загрузки
externals или CDNГлобальный объект, предоставленный HTML или платформойОдна контролируемая внешняя загрузкаНе включает jQuery в бандл и не проверяет URL CDN
\n

Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, добавьте глобальный экспорт в одном entry. Не смешивайте этот режим с CDN без причины. Иначе один бандл будет использовать пакет, а другой — внешний файл.

\n

Минимальная конфигурация для переходного проекта

\n

Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.

\n
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};
\n

В новом модуле зависимость остаётся видимой:

\n
import $ from 'jquery';\n\nexport function mount(form) {\n  return $(form).find('[data-mask]').length;\n}
\n

В legacy-модуле строка импорта может отсутствовать:

\n
$('.phone').inputmask('+7 (999) 999-99-99');
\n

Оба фрагмента должны ссылаться на один экземпляр, если Webpack собирает их в общий runtime или правильно выносит общий модуль. Это нужно проверить в фактической конфигурации. Само совпадение версий в package.json такого доказательства не даёт.

\n

Когда нужен window.jQuery

\n

Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода сделайте отдельный модуль-инициализатор и импортируйте его до плагина:

\n
// 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';
\n

Такой порядок относится к импортам внутри одного entry. Он не управляет отдельным CDN-скриптом, который HTML загрузит позже. В браузере проверьте именно window.jQuery перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.

\n

Отрицательный путь: CDN и externals

\n

Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:

\n
module.exports = {\n  externals: {\n    jquery: 'jQuery'\n  }\n};
\n

Теперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:

\n
<script src=\"https://cdn.example.test/jquery.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>
\n

URL в примере учебный. Не подставляйте его в рабочую страницу. Для production нужны зафиксированная версия, контроль доступности, политика безопасности и понятный план отказа. Если CDN недоступен, Webpack не сможет компенсировать это настройкой ProvidePlugin. Если браузер загрузит две версии jQuery, плагины могут зарегистрироваться в одном объекте, а приложение — работать с другим.

\n

Симптом → причина → проверка → действие

\n
СимптомПричинаПроверкаДействие
jQuery is not definedПлагин выполняется до глобального объектаПорядок Network и typeof window.jQuery перед импортомСобрать entry с инициализатором или исправить порядок внешних scripts
$(...).inputmask is not a functionПлагин получил другой объект или не загрузилсяСравнить window.jQuery === $ и проверить регистрацию $.fn.inputmaskОставить один источник jQuery и импортировать плагин после него
Работает только на одной страницеProvidePlugin действует только в собранных модулях этого entryСравнить entry, HTML и содержимое chunk-файловДобавить зависимость в нужный entry, а не рассчитывать на соседний бандл
Размер растёт после добавления второго entryНет общего chunk или настроены две независимые поставкиПосмотреть stats и число включённых модулей jqueryНастроить общую доставку только после проверки поведения legacy-кода
CDN-версия ломает плагинВерсия или порядок внешней загрузки не совпадает с контрактомПроверить фактический URL, версию и момент выполнения плагинаЗафиксировать совместимую версию или вернуть пакет в граф Webpack
\n

Порядок действий

\n
  1. Найдите все обращения к $, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.
  2. Для каждого entry выберите источник: пакет в графе Webpack или внешний глобальный script. Не оставляйте выбор на уровне случайного HTML-порядка.
  3. Оставьте явный import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.
  4. Если плагин читает window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.
  5. Соберите каждый entry и проверьте, что в нём есть ожидаемая зависимость или явно описан externals.
  6. Откройте реальную форму. Проверьте window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.
  7. После миграции потребителя удалите лишнюю глобальную настройку и повторите проверку. Иначе временная совместимость станет постоянной зависимостью.
\n

Ограничения

\n

ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.

\n

Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.

\n

В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.

\n

Проверяемый критерий готовности

\n

Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.

\n

Проверяемые источники

\n", - "contentHtml": "

После переноса старого фронтенда в Webpack кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на странице с «правильным» порядком скриптов. Ошибка часто выглядит случайной: новый модуль импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в нескольких бандлах и релиз, который нельзя уверенно проверить одной сборкой.

\n

Тезис простой: сначала нужно назвать потребителя jQuery, затем выбрать один способ доставки для каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin помогает старому коду, который обращается к свободным $ или jQuery. Глобальный объект нужен только тем потребителям, которые действительно читают window.jQuery. CDN и externals образуют другой контракт: Webpack больше не кладёт библиотеку в бандл и ждёт готового глобального объекта снаружи.

\n

Симптом начинается с границы видимости

\n

Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.

\n

ProvidePlugin решает третий случай. Webpack замечает свободный идентификатор в модуле и автоматически подставляет импорт. Поэтому код с $(selector) может собраться без строки import. Это не запись переменной в глобальный объект браузера и не исправление порядка независимых тегов script.

\n
\"Схема
Один entry должен доставить один объект jQuery раньше плагина, который его использует. Иллюстрация показывает границу между графом модулей и глобальным API браузера.
\n

Три способа доставки

\n

В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.

\n
СпособЧто получает кодКогда подходитЧего он не делает
Явный importЛокальный объект из графа WebpackНовый код и код, который можно менятьНе создаёт window.jQuery сам по себе
ProvidePluginАвтоматически подставленный импорт для свободного имениLegacy-модули с $ или jQueryНе чинит внешний script и не гарантирует порядок загрузки
externals или CDNГлобальный объект, предоставленный HTML или платформойОдна контролируемая внешняя загрузкаНе включает jQuery в бандл и не проверяет URL CDN
\n

Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, добавьте глобальный экспорт в одном entry. Не смешивайте этот режим с CDN без причины. Иначе один бандл будет использовать пакет, а другой — внешний файл.

\n

Минимальная конфигурация для переходного проекта

\n

Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.

\n
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};
\n

В новом модуле зависимость остаётся видимой:

\n
import $ from 'jquery';\n\nexport function mount(form) {\n  return $(form).find('[data-mask]').length;\n}
\n

В legacy-модуле строка импорта может отсутствовать:

\n
$('.phone').inputmask('+7 (999) 999-99-99');
\n

Оба фрагмента должны ссылаться на один экземпляр, если Webpack собирает их в общий runtime или правильно выносит общий модуль. Это нужно проверить в фактической конфигурации. Само совпадение версий в package.json такого доказательства не даёт.

\n

Когда нужен window.jQuery

\n

Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода сделайте отдельный модуль-инициализатор и импортируйте его до плагина:

\n
// 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';
\n

Такой порядок относится к импортам внутри одного entry. Он не управляет отдельным CDN-скриптом, который HTML загрузит позже. В браузере проверьте именно window.jQuery перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.

\n

Отрицательный путь: CDN и externals

\n

Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:

\n
module.exports = {\n  externals: {\n    jquery: 'jQuery'\n  }\n};
\n

Теперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:

\n
<script src=\"https://cdn.example.test/jquery.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>
\n

URL в примере учебный. Не подставляйте его в рабочую страницу. Для production нужны зафиксированная версия, контроль доступности, политика безопасности и понятный план отказа. Если CDN недоступен, Webpack не сможет компенсировать это настройкой ProvidePlugin. Если браузер загрузит две версии jQuery, плагины могут зарегистрироваться в одном объекте, а приложение — работать с другим.

\n

Симптом → причина → проверка → действие

\n
СимптомПричинаПроверкаДействие
jQuery is not definedПлагин выполняется до глобального объектаПорядок Network и typeof window.jQuery перед импортомСобрать entry с инициализатором или исправить порядок внешних scripts
$(...).inputmask is not a functionПлагин получил другой объект или не загрузилсяСравнить window.jQuery === $ и проверить регистрацию $.fn.inputmaskОставить один источник jQuery и импортировать плагин после него
Работает только на одной страницеProvidePlugin действует только в собранных модулях этого entryСравнить entry, HTML и содержимое chunk-файловДобавить зависимость в нужный entry, а не рассчитывать на соседний бандл
Размер растёт после добавления второго entryНет общего chunk или настроены две независимые поставкиПосмотреть stats и число включённых модулей jqueryНастроить общую доставку только после проверки поведения legacy-кода
CDN-версия ломает плагинВерсия или порядок внешней загрузки не совпадает с контрактомПроверить фактический URL, версию и момент выполнения плагинаЗафиксировать совместимую версию или вернуть пакет в граф Webpack
\n

Порядок действий

\n
  1. Найдите все обращения к $, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.
  2. Для каждого entry выберите источник: пакет в графе Webpack или внешний глобальный script. Не оставляйте выбор на уровне случайного HTML-порядка.
  3. Оставьте явный import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.
  4. Если плагин читает window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.
  5. Соберите каждый entry и проверьте, что в нём есть ожидаемая зависимость или явно описан externals.
  6. Откройте реальную форму. Проверьте window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.
  7. После миграции потребителя удалите лишнюю глобальную настройку и повторите проверку. Иначе временная совместимость станет постоянной зависимостью.
\n

Ограничения

\n

ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.

\n

Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.

\n

В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.

\n

Проверяемый критерий готовности

\n

Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.

\n

Проверяемые источники

\n" + "contentHtml": "

В переходном проекте после переноса старого фронтенда на Webpack разработчик открывает страницу с формой телефона: кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на одной странице. Это учебный сценарий, но симптом проверяемый: модульный код импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в бандлах и релиз, который нельзя подтвердить одной сборкой.

\n

Решение начинается с карты потребителей jQuery и границы каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin подставляет экспорт модуля для свободных $ и jQuery, а отдельное правило для window.jQuery задаёт глобальный контракт в обрабатываемом Webpack-коде. CDN и externals работают иначе: библиотека остаётся вне бандла, и окружение страницы обязано предоставить её до запуска потребителя.

\n

Симптом начинается с границы видимости

\n

Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.

\n

Для свободных $ и jQuery работает ProvidePlugin. Webpack замечает идентификатор в модуле и автоматически подставляет экспорт пакета, поэтому код с $(selector) может собраться без строки import. Само правило для $ или jQuery не означает, что внешний тег script получит глобал. Если нужен именно window.jQuery, его задают отдельным правилом ProvidePlugin или явным bootstrap-модулем.

\n
\"Схема
Один entry должен доставить один объект jQuery раньше плагина, который его использует. Иллюстрация показывает границу между графом модулей и глобальным API браузера.
\n

Три способа доставки

\n

В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.

\n
СпособЧто получает кодКогда подходитЧего он не делает
Явный importЛокальный объект из графа WebpackНовый код и код, который можно менятьНе создаёт window.jQuery без отдельного присваивания
ProvidePluginАвтоматически подставленный импорт для свободного имениLegacy-модули с $ или jQueryПравило для $ не управляет внешним script; порядок всё равно нужно проверить
externals или CDNЗависимость вне бандла, которую предоставляет окружениеОдна контролируемая внешняя загрузкаНе включает jQuery в бандл и не проверяет URL, версию или доступность
\n

Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, выберите один явный способ: правило ProvidePlugin для глобального свойства или bootstrap-модуль до side effect плагина. Не смешивайте пакет и CDN в одном entry без причины: один бандл может использовать локальный пакет, а другой — внешний объект.

\n

Минимальная конфигурация для переходного проекта

\n

Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.

\n
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};
\n

В новом модуле зависимость остаётся видимой:

\n
import $ from 'jquery';\n\nexport function mount(form) {\n  return $(form).find('[data-mask]').length;\n}
\n

В legacy-модуле строка импорта может отсутствовать:

\n
$('.phone').inputmask('+7 (999) 999-99-99');
\n

Два entry не обязаны автоматически использовать один экземпляр. Общий chunk, единый внешний объект или корректно настроенное разделение модулей могут убрать дубликат, но это нужно подтвердить в production-графе и в браузере. Совпадение версий в package.json само по себе такого доказательства не даёт.

\n

Когда нужен window.jQuery

\n

Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода можно либо явно записать импортированный объект в window, либо поручить это ProvidePlugin в модулях, которые анализирует Webpack.

Явный bootstrap показывает порядок выполнения и подходит, когда точка запуска плагина находится под вашим контролем:

\n
// 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';
\n

Если legacy-файл входит в граф Webpack, тот же глобальный контракт можно описать конфигурацией:

\n
new webpack.ProvidePlugin({\n  'window.jQuery': 'jquery'\n});
\n

Это правило следует проверять на собранном entry. Оно не загружает произвольный внешний script и не исправляет плагин, который запускается вне графа.

\n

Такой порядок относится к импортам внутри одного entry. Он не управляет отдельным CDN-скриптом, который HTML загрузит позже. В браузере проверьте именно window.jQuery перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.

\n

Отрицательный путь: CDN и externals

\n

Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:

\n
module.exports = {\n  externals: {\n    jquery: 'jQuery'\n  }\n};
\n

Теперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:

\n
<script src=\"/assets/vendor/jquery-3.x.y.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>
\n

Путь в примере обозначает зафиксированную версию, которую отдаёт ваш vendor или CDN. Не подставляйте неизвестный URL в рабочую страницу: для production нужны закреплённая версия, проверка доступности, политика безопасности и понятный план отказа.

\n

Симптом → причина → проверка → действие

\n
СимптомПричинаПроверкаДействие
jQuery is not definedПлагин выполняется до глобального объектаПорядок Network и typeof window.jQuery перед импортомСобрать entry с инициализатором или исправить порядок внешних scripts
$(...).inputmask is not a functionПлагин получил другой объект или не загрузилсяСравнить window.jQuery === $ и проверить регистрацию $.fn.inputmaskОставить один источник jQuery и импортировать плагин после него
Работает только на одной страницеProvidePlugin действует только в собранных модулях этого entryСравнить entry, HTML и содержимое chunk-файловДобавить зависимость в нужный entry, а не рассчитывать на соседний бандл
Размер растёт после добавления второго entryНет общего chunk или настроены две независимые поставкиПосмотреть stats и число включённых модулей jqueryНастроить общую доставку только после проверки поведения legacy-кода
CDN-версия ломает плагинВерсия или порядок внешней загрузки не совпадает с контрактомПроверить фактический URL, версию и момент выполнения плагинаЗафиксировать совместимую версию или вернуть пакет в граф Webpack
\n

Порядок действий

\n
  1. Найдите все обращения к $, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.
  2. Для каждого entry выберите источник: пакет в графе Webpack или внешний глобальный script. Не оставляйте выбор на уровне случайного HTML-порядка.
  3. Оставьте явный import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.
  4. Если плагин читает window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.
  5. Соберите каждый entry и проверьте, что в нём есть ожидаемая зависимость или явно описан externals.
  6. Откройте реальную форму. Проверьте window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.
  7. После миграции потребителя удалите лишнюю глобальную настройку и повторите проверку. Иначе временная совместимость станет постоянной зависимостью.
\n

Ограничения

\n

ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.

\n

Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.

\n

В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.

\n

Проверяемый критерий готовности

\n

Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.

\n

Проверяемые источники

\n" }