DarkRiDDeR12 мин

JavaScript. jQuery в Webpack: глобальная зависимость без скрытого порядка

JavaScript

Старая страница видит `$`, а новый модуль получает пустое значение. Цена ошибки — либо дублированная библиотека в каждом бандле, либо плагины, которые работают только из-за случайного порядка подключения.

Оригинальный материал правильно начинает с установки пакета и `ProvidePlugin`. В редактуре важно разделить две задачи: дать старому коду совместимое глобальное имя и оставить импорт явным в новых модулях. Если смешать их в одном правиле сборки, после смены entry-файла ошибка проявится только на части страниц.

JavaScript. jQuery в Webpack: глобальная зависимость без скрытого порядка: схема границ проверки
Иллюстрация показывает границу между симптомом, техническим механизмом и проверяемым действием.

Что сохраняем из исходной заметки

Webpack является одним из самых мощных и гибких инструментов для сборки фронтенд-проектов. Иногда необходимо включить в проект Webpack одну из самых популярных JavaScript библиотек jQuery.

Для начала необходимо установить jQuery из репозитория npm командой:

npm i jquery

либо (если используем менеджер пакетов Yarn):

yarn add jquery

Чтобы jQuery стал доступным в глобальной области видимости в «бандле» (собираемом пакете, от bundle) можно использовать ProvidePlugin (см. официальную документацию https://webpack.js.org/plugins/provide-plugin/):

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    }),
  ]
};
import $ from 'jquery';
 
global.jQuery = $;
global.$ = $;

Также jQuery можно подключить в проект Webpack через CDN с помощью плагина html-webpack-externals-plugin (https://www.npmjs.com/package/html-webpack-externals-plugin):

module.exports = {
  plugins: [
    new HtmlWebpackExternalsPlugin({ // optional plugin: inject cdn
      externals: [
        {
            module: 'jquery',
            entry: 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js'
        }
      ],
    }),
  ]
};

После чего можно использовать jQuery библиотеки, подключая их следующим образом:

require("inputmask/dist/inputmask/jquery.inputmask.js");

Итоговая схема подключения

Для старого проекта jQuery в Webpack лучше подключать явно: установить пакет, импортировать его в точке входа и отдельно решить вопрос с глобальными переменными. ProvidePlugin удобен, когда в модулях встречаются свободные идентификаторы $ или jQuery. Но если старый плагин лезет именно в window.jQuery, одного ProvidePlugin может быть мало — нужно положить jQuery в window самостоятельно.

import $ from 'jquery';

window.$ = $;
window.jQuery = $;

После этого можно добавить ProvidePlugin, чтобы не писать импорт в каждом файле:

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),
  ],
};

Порядок подключения имеет значение. Сначала в entry-файле импортируем jQuery и кладём его в window, потом импортируем старые плагины, которым нужен глобальный объект. Если плагин подключается через require, делаем это после установки window.jQuery.

import './jquery-global';
import 'inputmask/dist/jquery.inputmask';
import './app';

Проверка простая: в браузерной консоли должны существовать window.$ и window.jQuery, а в собранном bundle не должно быть двух разных копий jQuery. Если проект новый, лучше не тащить jQuery без необходимости. Если проект старый и плагины уже написаны под jQuery, такая схема делает зависимость явной и предсказуемой.

Механизм без лишних обещаний

Webpack строит граф модулей из import и require. Глобальная переменная не входит в этот граф как обычная зависимость, поэтому старый плагин может работать только при дополнительном правиле ProvidePlugin или явной записи в window.

ProvidePlugin подставляет импорт в местах, где встречается идентификатор. Это не делает jQuery глобальной для любого скрипта, загруженного отдельно через HTML. Для такого скрипта нужен один согласованный entry и одна точка экспорта.

Если библиотека уже приходит с CDN, её следует объявить external и проверить, что глобальное имя появляется раньше потребителя. Две независимые копии jQuery дают разные объекты и ломают плагины, которые сравнивают `$.fn` или регистрируют обработчики.

Минимальный воспроизводимый пример

Ниже — маленькая проверка, которую можно запустить или адаптировать в отдельном тестовом окружении. Значения демонстрационные; проектные идентификаторы, пути и версии нужно заменить своими и сохранить рядом с результатом.

const webpack = require('webpack');

module.exports = {
  entry: {
    legacy: './src/legacy-entry.js',
    modern: './src/modern-entry.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    })
  ]
};

// В новом модуле зависимость остаётся видимой.
import $ from 'jquery';
export function mount() {
  return $.fn && $.fn.jquery;
}

Матрица диагностики

Слой: рабочая матрица проверки
СлойЧто проверяемТипичная ошибка
Новый модульЕсть `import $ from 'jquery'`Зависимость спрятана в window
Старый плагинПотребитель получает тот же объектСозданы две копии jQuery
EntryБиблиотека загружена до потребителяПорядок зависит от HTML
CDNexternal и глобальное имя согласованыБандл ожидает модуль, а получает URL

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

  1. Определить, какие файлы используют import, а какие обращаются к `$` или `window.jQuery`.
  2. Оставить один способ доставки jQuery для каждого entry: пакет или external, но не случайную смесь.
  3. Добавить ProvidePlugin только для legacy-кода и проверить итоговый граф сборки.
  4. Запустить страницу с реальным старым плагином и убедиться, что объект jQuery один.
  5. После миграции каждого потребителя удалить лишнее глобальное правило и зафиксировать это в тесте сборки.

Ограничения и безопасный следующий шаг

ProvidePlugin не исправляет порядок независимых `