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. Цена ошибки — сломанная форма, дублированная библиотека в нескольких бандлах и релиз, который нельзя уверенно проверить одной сборкой.
Тезис простой: сначала нужно назвать потребителя jQuery, затем выбрать один способ доставки для каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin помогает старому коду, который обращается к свободным $ или jQuery. Глобальный объект нужен только тем потребителям, которые действительно читают window.jQuery. CDN и externals образуют другой контракт: Webpack больше не кладёт библиотеку в бандл и ждёт готового глобального объекта снаружи.
Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.
ProvidePlugin решает третий случай. Webpack замечает свободный идентификатор в модуле и автоматически подставляет импорт. Поэтому код с $(selector) может собраться без строки import. Это не запись переменной в глобальный объект браузера и не исправление порядка независимых тегов script.
В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.
\n| Способ | Что получает код | Когда подходит | Чего он не делает |
|---|---|---|---|
| Явный import | Локальный объект из графа Webpack | Новый код и код, который можно менять | Не создаёт window.jQuery сам по себе |
| ProvidePlugin | Автоматически подставленный импорт для свободного имени | Legacy-модули с $ или jQuery | Не чинит внешний script и не гарантирует порядок загрузки |
| externals или CDN | Глобальный объект, предоставленный HTML или платформой | Одна контролируемая внешняя загрузка | Не включает jQuery в бандл и не проверяет URL CDN |
Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, добавьте глобальный экспорт в одном entry. Не смешивайте этот режим с CDN без причины. Иначе один бандл будет использовать пакет, а другой — внешний файл.
Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.
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В новом модуле зависимость остаётся видимой:
\nimport $ 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 такого доказательства не даёт.
Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода сделайте отдельный модуль-инициализатор и импортируйте его до плагина:
// 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 перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.
Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:
\nmodule.exports = {\n externals: {\n jquery: 'jQuery'\n }\n};\nТеперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:
<script src=\"https://cdn.example.test/jquery.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>\nURL в примере учебный. Не подставляйте его в рабочую страницу. Для production нужны зафиксированная версия, контроль доступности, политика безопасности и понятный план отказа. Если CDN недоступен, Webpack не сможет компенсировать это настройкой ProvidePlugin. Если браузер загрузит две версии jQuery, плагины могут зарегистрироваться в одном объекте, а приложение — работать с другим.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
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 |
$, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.externals.window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.
Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.
В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.
Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.
\n$, jQuery и window.jQuery.После переноса старого фронтенда в Webpack кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на странице с «правильным» порядком скриптов. Ошибка часто выглядит случайной: новый модуль импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в нескольких бандлах и релиз, который нельзя уверенно проверить одной сборкой.
Тезис простой: сначала нужно назвать потребителя jQuery, затем выбрать один способ доставки для каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin помогает старому коду, который обращается к свободным $ или jQuery. Глобальный объект нужен только тем потребителям, которые действительно читают window.jQuery. CDN и externals образуют другой контракт: Webpack больше не кладёт библиотеку в бандл и ждёт готового глобального объекта снаружи.
Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.
ProvidePlugin решает третий случай. Webpack замечает свободный идентификатор в модуле и автоматически подставляет импорт. Поэтому код с $(selector) может собраться без строки import. Это не запись переменной в глобальный объект браузера и не исправление порядка независимых тегов script.
В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.
\n| Способ | Что получает код | Когда подходит | Чего он не делает |
|---|---|---|---|
| Явный import | Локальный объект из графа Webpack | Новый код и код, который можно менять | Не создаёт window.jQuery сам по себе |
| ProvidePlugin | Автоматически подставленный импорт для свободного имени | Legacy-модули с $ или jQuery | Не чинит внешний script и не гарантирует порядок загрузки |
| externals или CDN | Глобальный объект, предоставленный HTML или платформой | Одна контролируемая внешняя загрузка | Не включает jQuery в бандл и не проверяет URL CDN |
Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, добавьте глобальный экспорт в одном entry. Не смешивайте этот режим с CDN без причины. Иначе один бандл будет использовать пакет, а другой — внешний файл.
Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.
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В новом модуле зависимость остаётся видимой:
\nimport $ 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 такого доказательства не даёт.
Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода сделайте отдельный модуль-инициализатор и импортируйте его до плагина:
// 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 перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.
Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:
\nmodule.exports = {\n externals: {\n jquery: 'jQuery'\n }\n};\nТеперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:
<script src=\"https://cdn.example.test/jquery.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>\nURL в примере учебный. Не подставляйте его в рабочую страницу. Для production нужны зафиксированная версия, контроль доступности, политика безопасности и понятный план отказа. Если CDN недоступен, Webpack не сможет компенсировать это настройкой ProvidePlugin. Если браузер загрузит две версии jQuery, плагины могут зарегистрироваться в одном объекте, а приложение — работать с другим.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
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 |
$, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.externals.window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.
Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.
В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.
Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.
\n$, jQuery и window.jQuery.После переноса старого фронтенда в Webpack кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на странице с «правильным» порядком скриптов. Ошибка часто выглядит случайной: новый модуль импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в нескольких бандлах и релиз, который нельзя уверенно проверить одной сборкой.
Тезис простой: сначала нужно назвать потребителя jQuery, затем выбрать один способ доставки для каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin помогает старому коду, который обращается к свободным $ или jQuery. Глобальный объект нужен только тем потребителям, которые действительно читают window.jQuery. CDN и externals образуют другой контракт: Webpack больше не кладёт библиотеку в бандл и ждёт готового глобального объекта снаружи.
Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.
ProvidePlugin решает третий случай. Webpack замечает свободный идентификатор в модуле и автоматически подставляет импорт. Поэтому код с $(selector) может собраться без строки import. Это не запись переменной в глобальный объект браузера и не исправление порядка независимых тегов script.
В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.
\n| Способ | Что получает код | Когда подходит | Чего он не делает |
|---|---|---|---|
| Явный import | Локальный объект из графа Webpack | Новый код и код, который можно менять | Не создаёт window.jQuery сам по себе |
| ProvidePlugin | Автоматически подставленный импорт для свободного имени | Legacy-модули с $ или jQuery | Не чинит внешний script и не гарантирует порядок загрузки |
| externals или CDN | Глобальный объект, предоставленный HTML или платформой | Одна контролируемая внешняя загрузка | Не включает jQuery в бандл и не проверяет URL CDN |
Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, добавьте глобальный экспорт в одном entry. Не смешивайте этот режим с CDN без причины. Иначе один бандл будет использовать пакет, а другой — внешний файл.
Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.
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В новом модуле зависимость остаётся видимой:
\nimport $ 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 такого доказательства не даёт.
Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода сделайте отдельный модуль-инициализатор и импортируйте его до плагина:
// 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 перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.
Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:
\nmodule.exports = {\n externals: {\n jquery: 'jQuery'\n }\n};\nТеперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:
<script src=\"https://cdn.example.test/jquery.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>\nURL в примере учебный. Не подставляйте его в рабочую страницу. Для production нужны зафиксированная версия, контроль доступности, политика безопасности и понятный план отказа. Если CDN недоступен, Webpack не сможет компенсировать это настройкой ProvidePlugin. Если браузер загрузит две версии jQuery, плагины могут зарегистрироваться в одном объекте, а приложение — работать с другим.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
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 |
$, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.externals.window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.
Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.
В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.
Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.
\n$, jQuery и window.jQuery.После переноса старого фронтенда в Webpack кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на странице с «правильным» порядком скриптов. Ошибка часто выглядит случайной: новый модуль импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в нескольких бандлах и релиз, который нельзя уверенно проверить одной сборкой.
Тезис простой: сначала нужно назвать потребителя jQuery, затем выбрать один способ доставки для каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin помогает старому коду, который обращается к свободным $ или jQuery. Глобальный объект нужен только тем потребителям, которые действительно читают window.jQuery. CDN и externals образуют другой контракт: Webpack больше не кладёт библиотеку в бандл и ждёт готового глобального объекта снаружи.
Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.
ProvidePlugin решает третий случай. Webpack замечает свободный идентификатор в модуле и автоматически подставляет импорт. Поэтому код с $(selector) может собраться без строки import. Это не запись переменной в глобальный объект браузера и не исправление порядка независимых тегов script.
В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.
\n| Способ | Что получает код | Когда подходит | Чего он не делает |
|---|---|---|---|
| Явный import | Локальный объект из графа Webpack | Новый код и код, который можно менять | Не создаёт window.jQuery сам по себе |
| ProvidePlugin | Автоматически подставленный импорт для свободного имени | Legacy-модули с $ или jQuery | Не чинит внешний script и не гарантирует порядок загрузки |
| externals или CDN | Глобальный объект, предоставленный HTML или платформой | Одна контролируемая внешняя загрузка | Не включает jQuery в бандл и не проверяет URL CDN |
Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, добавьте глобальный экспорт в одном entry. Не смешивайте этот режим с CDN без причины. Иначе один бандл будет использовать пакет, а другой — внешний файл.
Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.
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В новом модуле зависимость остаётся видимой:
\nimport $ 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 такого доказательства не даёт.
Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут window.jQuery. Для такого кода сделайте отдельный модуль-инициализатор и импортируйте его до плагина:
// 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 перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.
Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:
\nmodule.exports = {\n externals: {\n jquery: 'jQuery'\n }\n};\nТеперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:
<script src=\"https://cdn.example.test/jquery.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>\nURL в примере учебный. Не подставляйте его в рабочую страницу. Для production нужны зафиксированная версия, контроль доступности, политика безопасности и понятный план отказа. Если CDN недоступен, Webpack не сможет компенсировать это настройкой ProvidePlugin. Если браузер загрузит две версии jQuery, плагины могут зарегистрироваться в одном объекте, а приложение — работать с другим.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
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 |
$, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.externals.window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.
Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.
В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.
Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.
\n$, jQuery и window.jQuery.В переходном проекте после переноса старого фронтенда на Webpack разработчик открывает страницу с формой телефона: кнопка с inputmask перестаёт работать. В консоли появляется jQuery is not defined, $(...).inputmask is not a function или плагин загружается только на одной странице. Это учебный сценарий, но симптом проверяемый: модульный код импортирует jQuery, а legacy-файл ищет её в window. Цена ошибки — сломанная форма, дублированная библиотека в бандлах и релиз, который нельзя подтвердить одной сборкой.
Решение начинается с карты потребителей jQuery и границы каждого entry. Явный import связывает зависимость с графом модулей. ProvidePlugin подставляет экспорт модуля для свободных $ и jQuery, а отдельное правило для window.jQuery задаёт глобальный контракт в обрабатываемом Webpack-коде. CDN и externals работают иначе: библиотека остаётся вне бандла, и окружение страницы обязано предоставить её до запуска потребителя.
Модуль с import $ from 'jquery' получает значение из графа Webpack. Это локальная переменная модуля. Она не обещает, что отдельный <script> в HTML увидит window.$. Обратное тоже верно: наличие window.jQuery не добавляет зависимость в граф и не делает её доступной каждому исходному файлу без настройки.
Для свободных $ и jQuery работает ProvidePlugin. Webpack замечает идентификатор в модуле и автоматически подставляет экспорт пакета, поэтому код с $(selector) может собраться без строки import. Само правило для $ или jQuery не означает, что внешний тег script получит глобал. Если нужен именно window.jQuery, его задают отдельным правилом ProvidePlugin или явным bootstrap-модулем.
В проекте с несколькими entry не стоит начинать с единого глобального правила. Сначала составьте карту потребителей.
\n| Способ | Что получает код | Когда подходит | Чего он не делает |
|---|---|---|---|
| Явный import | Локальный объект из графа Webpack | Новый код и код, который можно менять | Не создаёт window.jQuery без отдельного присваивания |
| ProvidePlugin | Автоматически подставленный импорт для свободного имени | Legacy-модули с $ или jQuery | Правило для $ не управляет внешним script; порядок всё равно нужно проверить |
| externals или CDN | Зависимость вне бандла, которую предоставляет окружение | Одна контролируемая внешняя загрузка | Не включает jQuery в бандл и не проверяет URL, версию или доступность |
Для переходного проекта обычно работает связка: новый код импортирует jQuery явно, а ограниченный legacy-слой получает ProvidePlugin. Если плагин проверяет именно window.jQuery, выберите один явный способ: правило ProvidePlugin для глобального свойства или bootstrap-модуль до side effect плагина. Не смешивайте пакет и CDN в одном entry без причины: один бандл может использовать локальный пакет, а другой — внешний объект.
Ниже учебный пример. В нём два entry, один пакет jquery и legacy-код, который ещё использует свободное имя. Пример не измеряет размер бандла, не доказывает совместимость конкретного плагина и не заменяет проверку вашей версии Webpack и jQuery.
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В новом модуле зависимость остаётся видимой:
\nimport $ 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 само по себе такого доказательства не даёт.
Некоторые старые плагины не экспортируют функцию как модуль. Они выполняются сразу и ищут 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, тот же глобальный контракт можно описать конфигурацией:
\nnew webpack.ProvidePlugin({\n 'window.jQuery': 'jquery'\n});\nЭто правило следует проверять на собранном entry. Оно не загружает произвольный внешний script и не исправляет плагин, который запускается вне графа.
\nТакой порядок относится к импортам внутри одного entry. Он не управляет отдельным CDN-скриптом, который HTML загрузит позже. В браузере проверьте именно window.jQuery перед и после подключения плагина, а затем вызовите метод плагина на реальном элементе формы.
Внешняя загрузка имеет смысл, когда платформа уже отдаёт jQuery или несколько приложений должны использовать один URL. Тогда Webpack не должен одновременно включать пакет в тот же бандл. Пример для глобального объекта:
\nmodule.exports = {\n externals: {\n jquery: 'jQuery'\n }\n};\nТеперь import $ from 'jquery' в собранном коде означает обращение к внешнему jQuery. HTML обязан загрузить библиотеку раньше бандла:
<script src=\"/assets/vendor/jquery-3.x.y.min.js\"></script>\n<script src=\"/assets/legacy.js\"></script>\nПуть в примере обозначает зафиксированную версию, которую отдаёт ваш vendor или CDN. Не подставляйте неизвестный URL в рабочую страницу: для production нужны закреплённая версия, проверка доступности, политика безопасности и понятный план отказа.
\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 |
$, jQuery и window.jQuery. Отдельно отметьте модули, которые выполняются сразу при импорте.import $ from 'jquery' в новом коде. Добавьте ProvidePlugin только к переходному слою, который нельзя быстро изменить.window.jQuery, создайте один инициализатор и импортируйте его перед этим плагином.externals.window.jQuery, $.fn.jquery, метод legacy-плагина и обработчик пользовательского события.ProvidePlugin не превращает любой свободный идентификатор в безопасную архитектуру. Он скрывает зависимость в исходном файле и усложняет перенос модуля в другой сборщик. Используйте его как переходный слой и уменьшайте область действия.
Один объект jQuery не гарантирует совместимость. Плагин может требовать конкретную версию, поддерживать только старый API или конфликтовать с noConflict. Проверяйте реальный сценарий, а не только успешную компиляцию.
В серверном рендеринге и Web Worker нет обычного window. Код, который без проверки обращается к window.jQuery, должен выполняться только в браузерном entry. Это отдельное ограничение окружения, а не проблема Webpack.
Работу можно считать законченной, когда каждый entry имеет один документированный источник jQuery, сборка не содержит непреднамеренной второй копии, legacy-плагин получает тот же объект, что и приложение, а форма проходит реальный сценарий ввода. Дополнительно зафиксируйте проверку для страницы без legacy-кода: она не должна получать глобальную зависимость только потому, что она есть в соседнем бандле.
\n$, jQuery и window.jQuery.$ и jQuery, конфликт библиотек и последствия загрузки двух версий.