Первое, что вы делаете при преобразовании из require.js в webpack, это берете весь файл конфигурации require.js и конвертируете его в файл webpack.config.js.

На практике для нас это означало решение трех вопросов:

  1. Сообщите webpack, где вы храните свои файлы JS
  2. Огромный список paths в нашей конфигурации require.js
  3. Вся конфигурация shim

Разрешение пути к модулю

Первое, что вам нужно сделать, это указать webpack, где находятся ваши файлы JS. Require.js обычно может сделать это на основе тега <script>, который вы использовали для его настройки, или вы могли настроить его с помощью параметра baseUrl. Это очень просто настроить в webpack, добавив в конфигурацию следующее:

{
    resolve: {
        modulesDirectories: ['public/js']
    }
}

Если вы забыли установить, этот веб-пакет будет искать ваши файлы в каталоге node_modules или в каталоге с именем web_modules.

Перенос путей Require.js в псевдонимы webpack

Первоначально процесс преобразования действительно прост.

Начните с такой конфигурации require.js:

requirejs.config({
    paths: {
        "backbone": "lib/backbone-1.1.0",
        "jquery": "lib/jquery-1.10.2",
        "underscore": "lib/lodash.underscore-2.3.0",
        "jqueryUI": "lib/jquery-ui.min"
    }
});

И это переводится в следующую конфигурацию веб-пакета:

module.exports = {
    resolve: {
        alias: {
                "backbone": "lib/backbone-1.1.0",
                "jquery": "lib/jquery-1.10.2",
                "underscore": "lib/lodash.underscore-2.3.0",
                "jqueryUI": "lib/jquery-ui.min"
        }           
    }
}

Погрузчик для каждой прокладки

Следующее, что потребовало особого внимания, - это исправление конфигурации прокладки. Это было немного сложнее, чем казалось, потому что легко забыть, что на самом деле делают прокладки. Давайте вернемся к этому на мгновение.

Прокладка require.js принимает модули, несовместимые с AMD, и делает их совместимыми с AMD, заключая их в небольшой фрагмент кода, который извлекает соответствующие зависимости.

Давайте посмотрим, как это работает, на следующем простом примере (конфигурация require.js):

{
    shim: {
        "underscore": {
            exports: "_"
        },
        "backbone": {
            deps: ["jquery", "underscore"],
            exports: "Backbone"
        }
    }
}

Здесь мы применяем прокладки как для подчеркивания, так и для позвоночника. Для подчеркивания прокладка обернет библиотеку, а затем вернет значение переменной _ для любых сценариев, использующих ее в качестве зависимости. Магистральный случай несколько сложнее:

  1. Он обертывает библиотеку и экспортирует значение переменной Backbone.
  2. Он гарантирует, что при оценке магистральная сеть имеет доступ как к jquery, так и к underscore

Давайте посмотрим, как мы получим ту же настройку с помощью загрузчиков веб-пакетов:

{
  module: {
    loaders: [
      { test: /underscore/, loader: 'exports?_' }
      { test: /backbone/, loader: 'exports?Backbone!imports?underscore,jquery' }
    ]
  }
}

Концептуально она не сильно отличается от предыдущей версии. Основное отличие заключается в том, что вы настраиваете загрузчики в одну строку.

Несколько замечаний:

  1. Загрузчик веб-пакетов более динамичен, чем оболочка require.js, и на самом деле больше похож на плагин require.js.
  2. test - это регулярное выражение, которое соответствует полному пути к файлу, поэтому будьте осторожны, чтобы быть конкретным!
  3. Чтобы использовать эти загрузчики, вам необходимо установить их npm install exports-loader imports-loader

Практически все распространенные варианты использования, с которыми вы можете столкнуться, описаны в руководстве по веб-пакетам для модулей совместимости.

Интересная часть: NPM

Итак, теперь, когда вы перенесли всю свою shim и paths конфигурацию, позвольте мне предложить кое-что: удалить ее.

Теперь перейдите в NPM и установите underscore, jquery, backbone, react. Все, что вы используете, вероятно, тоже есть, и, скорее всего, будет работать сразу после установки без каких-либо специальных загрузчиков или aliasing.

В этом волшебство webpack. Если вы получаете зависимости от NPM, вам это не нужно!

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

Чтобы добавить поддержку NPM в webpack, просто убедитесь, что это включено в конфигурацию вашего webpack:

{
    resolve: {
        modulesDirectories: ['public/js', 'node_modules']
    }
}

Перенос маршрутизатора

Сердце нашего приложения - этот модный роутер. Когда появляется новый маршрут, мы динамически извлекаем представление, связанное с этим путем, создаем его экземпляр и повторно визуализируем основное представление приложения с новой страницей. Работает очень хорошо. Основная навигация и все остальное являются частью основного приложения, но каждая вкладка на панели навигации имеет свой собственный отдельный пакет JS, и мы будем использовать его только по мере необходимости*.

Это была, безусловно, самая сложная часть, в основном из-за моего непонимания того, насколько различается техника разделения / связывания между webpack и require.js.

* Это немного изменилось для нас в последнее время, но, надеюсь, урок по-прежнему будет вам полезен!

Прощай, ручное разделение

Итак, мы много говорили о нашей конфигурации require.js. Теперь поговорим о нашей конфигурации r.js (оптимизатора require.js). Вся специальная информация, необходимая в дополнение к уже упомянутому, для создания нашей сборки javascript. Вот основная его часть:

{
    baseUrl: 'public/js',
    mainConfigFile: 'public/js/config.js',
    dir: '.build/js',
    modules: [
        { name: 'config' },
        { name: 'view/summary/index', exclude: ['config'] },
        { name: 'view/activity/index', exclude: ['config'] },
        { name: 'view/transfer/index', exclude: ['config'] },
        { name: 'view/wallet/index', exclude: ['config'] },
        { name: 'view/settings/index', exclude: ['config'] },
    ]
}

Большая часть этого файла связана со всеми создаваемыми нами пакетами. С помощью webpack мы можем это значительно упростить.

Сначала я решил заменить это концепцией входа в веб-пакет. Я потратил много времени на неправильный путь, беспокоясь о записях. Вероятно, тебе это не нужно.

Есть еще один подход, который сработал для нас лучше:

  • Использование async require() для создания точек разделения в нашем приложении, а затем разрешение webpack создавать пакеты автоматически.

Код маршрутизации: старый и новый

Вот суть старого кода require.js:

function handleRouteChange(path) {
    require(['/views/' + path], function(PageView) {
        app.setView(new PageView());
    });
}

Первое, что я попытался сделать при переносе этого в webpack, - просто оставить его. Вроде сработало! Проблема в том, что он создал только один пакет для всех представлений. Это означает значительно большую полезную нагрузку для браузера.

Вот наше улучшенное решение для веб-пакетов:

function loadPage(PageView) {
    app.setView(new PageView());
}
function handleRouteChange(path) {
    switch (path) {
        case 'settings':
            require(['/views/settings'], loadPage);
            break;
        case 'transfer':
            require(['/views/transfer'], loadPage);
            break;
        case 'wallet':
            require(['/views/wallet'], loadPage);
            break;
    default:
        // you could either require everything here as a last resort or just leave it..
    }
}

Каждый раз, когда вы используете require([]) или require.ensure() веб-пакет в стиле AMD, он увидит, что вы туда передали, и создаст пакет с всеми возможными совпадениями. Это отлично работает, если вы передаете один файл, но когда вы используете переменную, это может привести к объединению всей вашей папки просмотра. Вот почему вам нужно использовать что-то вроде оператора switch, чтобы убедиться, что вы объявляете свои точки разделения для каждого маршрута.

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

Требуется запись или асинхронность

Позвольте мне изо всех сил объяснить, когда вам нужен entry и когда вы хотите полагаться на webpack, который сделает разделение за вас:

  1. Используйте entry, если хотите добавить на страницу новый тег <script>.
  2. Используйте стиль AMD require([]) или require.ensure(), если вы хотите, чтобы веб-пакет динамически втягивал дополнительные модули.

Я рекомендую прочитать Превосходное руководство Пита Ханта по веб-пакету по асинхронной загрузке для получения дополнительной информации.

Проблема с нашим CDN

Одна из последних вещей, которые нас достали, - это наш CDN. В режиме локальной разработки все работало нормально, но когда мы добрались до нашей среды тестирования в реальном времени, мы заметили, что по умолчанию он пытается вытащить дополнительные пакеты с веб-сервера, а не с CDN, где наш JS фактически находится в производственной среде. require.js обрабатывает это автоматически, анализируя URL-адрес из <script> на странице. В Webpack нет этой магии. Если у вас есть нестандартное место, где вы хотите, чтобы оно вытащило дополнительные динамические бандлы, вам нужно указать это:

__webpack_public_path__ = document.body.getAttribute('data-js-path') + '/apps/';

Более подробную информацию можно найти здесь: https://github.com/webpack/docs/wiki/configuration#outputpublicpath

Если бы мы использовали webpack для обработки всего процесса сборки, мы могли бы позволить webpack применять хэш автоматически, но поскольку мы все еще используем его только для нашей сборки JS, нам нужно обрабатывать это программно. Вероятно, одна из моих наименее любимых вещей в настройке. Не очень сложно, просто немного неприятно вспоминать.

Размер и время сборки

В этот момент мы были в восторге от всего. Это действительно работало, но мы заметили, что размеры файлов были немного больше, может быть, даже намного, чем наши старые сборки require.js.

Введите причины отображения

webpack --display-reasons - одна из самых полезных вещей, которые когда-либо предоставлялись инструментами сборки.

Запустите webpack с этим флагом, и вы увидите:

  1. Каждый модуль, который был добавлен в ваш пакет
  2. Сколько места он занимает
  3. Имя файла и формат модуля, в который он включен

Вот частичный результат недавней сборки:

Это кладезь информации. Какие модули занимали больше всего места? Как это попало в этот комплект? Все ответили с этой информацией!

Примечание. По умолчанию все файлы в NPM исключаются из вывода. Если вы хотите добавить это, просто добавьте флаг --display-modules в свой запрос (как указано выше), и теперь вы можете увидеть каждый файл, для которого требуется jquery (node_modules сокращается до ~) или другие общие модули. Это довольно круто, и это помогло нам найти небольшую проблему с moment.js.

Moment.js и плагин Ignore

Когда мы впервые начали этот процесс, мы заметили, что moment.js загружает 900 КБ файлов. В уменьшенном виде это все еще было около 100 КБ. Немного покопавшись, мы наткнулись на статью о stackoverflow: Как предотвратить загрузку локалей Moment.js с помощью Webpack.

Поскольку теперь вы находитесь в среде CommonJS, Moment считает, что можно безопасно загрузить всю информацию о своем языковом стандарте. Предполагается, что вы работаете на сервере, а не связываете JS с чьей-то системой Windows XP. К счастью, есть плагин для веб-пакетов, который легко избавится от лишних запросов.

{
    plugins: [
        new webpack.IgnorePlugin(/^\.\/locale$/, [/moment$/]), // saves ~100k
    ]
}

Исходные карты

Это также может привести к тому, что ваши сборки будут казаться огромными. Они великолепны и действительно могут помочь с отладкой, но убедитесь, что вы знаете, что с ними делаете, и не учитываете их в общем размере пакета. Большинство браузеров загружают их только в том случае, если открыта консоль разработчика, поэтому они не влияют на ваших клиентов.

Однако, если вы установите тип исходной карты на eval или eval-source-map, исходные карты будут включены непосредственно в ваш JS. Не используйте этот тип исходной карты в производственной сборке. Вот наша конфигурация разработки:

{
    devtool: 'source-map'
}

В документации по веб-пакету есть целый огромный раздел, посвященный всем различным типам параметров исходной карты, которые вы можете использовать здесь:
http://webpack.github.io/docs/configuration.html#devtool

Минификация

Чтобы добавить минификацию с помощью uglify js, все, что вам нужно сделать, это добавить этот конфиг:

{
    plugins: [
        new webpack.optimize.UglifyJsPlugin({minimize: true})
    ]
}

Заключение

Это был долгий и легендарный путь от require.js к webpack, но в конечном итоге вся команда пожинает плоды. Одна из лучших функций, о которых не упоминалось в этой статье, - это превосходный babel-loader, который упростил использование функций ES6 в нашем коде сегодня. Вся экосистема и поддержка webpack просто фантастические. Надеюсь, это небольшое руководство поможет вашей команде избежать препятствий, с которыми мы столкнулись. Если вам нужна помощь, не стесняйтесь оставить мне сообщение в твиттере или заглянуть в отличный чат для веб-пакетов на gitter. Хорошего дня!