Как отдельно связать вендорные скрипты и требовать их по мере необходимости с Webpack?

173

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

Я пишу библиотеку JavaScript с несколькими модулями, которые могут зависеть или не зависеть друг от друга. Кроме того, jQuery используется всеми модулями, и некоторые из них могут нуждаться в плагинах jQuery. Затем эта библиотека будет использоваться на нескольких разных веб-сайтах, для которых могут потребоваться некоторые или все модули.

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

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

Пример : давайте представим, что моя библиотека имеет следующие модули:

  • a (требуется: jquery, jquery.plugin1)
  • б (требуется: JQuery, а)
  • c (требуется: jquery, jquery.ui, a, b)
  • d (требуется: jquery, jquery.plugin2, a)

И у меня есть приложение (рассматривается как уникальный входной файл), для которого требуются модули a, b и c. Webpack для этого случая должен сгенерировать следующие файлы:

  • комплект поставщиков : с jquery, jquery.plugin1 и jquery.ui;
  • комплектация сайта : с модулями a, b и c;

В конце концов, я бы предпочел использовать jQuery в качестве глобального, поэтому мне не нужно требовать его для каждого отдельного файла (например, я могу требовать его только для основного файла). А плагины jQuery просто расширили бы $ global в случае необходимости (это не проблема, если они доступны для других модулей, которым они не нужны).

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

bensampaio
источник
2
каково ваше решение? Вам удалось найти достойный подход. Если так, пожалуйста, отправьте это! спасибо
GeekOnGadgets

Ответы:

140

в моем файле webpack.config.js (версия 1,2,3), у меня есть

function isExternal(module) {
  var context = module.context;

  if (typeof context !== 'string') {
    return false;
  }

  return context.indexOf('node_modules') !== -1;
}

в моем массиве плагинов

plugins: [
  new CommonsChunkPlugin({
    name: 'vendors',
    minChunks: function(module) {
      return isExternal(module);
    }
  }),
  // Other plugins
]

Теперь у меня есть файл, который только добавляет сторонние библиотеки в один файл по мере необходимости.

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

plugins: [
  new CommonsChunkPlugin({
    name: 'common',
    minChunks: function(module, count) {
      return !isExternal(module) && count >= 2; // adjustable
    }
  }),
  new CommonsChunkPlugin({
    name: 'vendors',
    chunks: ['common'],
    // or if you have an key value object for your entries
    // chunks: Object.keys(entry).concat('common')
    minChunks: function(module) {
      return isExternal(module);
    }
  })
]

Обратите внимание, что порядок плагинов имеет большое значение.

Кроме того, это изменится в версии 4. Когда это будет официально, я обновлю этот ответ.

Обновление: изменение индекса поиска для пользователей Windows

Рафаэль Де Леон
источник
1
Я не знаю, было ли это уже возможно, когда я отправил свой вопрос, но это действительно то, что я искал. С этим решением мне больше не нужно указывать свой блок ввода поставщика. Большое спасибо!
Бенсампайо
1
isExternalв minChunksсделал мой день. Как это не задокументировано? Есть минусы?
Уэсли Шлеймер де Гоес
Спасибо, но измените userRequest.indexOf ('/ node_modules /') на userRequest.indexOf ('node_modules') для оконных
патчей
@ WesleySchleumerdeGeses это задокументировано, но без примера options.minChunks (number|Infinity|function(module, count) -> boolean):я пока не вижу обратной стороны.
Рафаэль Де Леон
2
Это не будет работать при использовании загрузчиков, так как путь к загрузчику также будет в module.userRequest(и загрузчик, вероятно, находится в node_modules). Мой код для isExternal():return typeof module.userRequest === 'string' && !!module.userRequest.split('!').pop().match(/(node_modules|bower_components|libraries)/);
cdauth
54

Я не уверен, полностью ли я понимаю вашу проблему, но, поскольку у меня недавно была похожая проблема, я постараюсь помочь вам.

Поставщик комплектации.

Вы должны использовать CommonsChunkPlugin для этого. в конфигурации вы указываете имя чанка (например vendor) и имя файла, который будет сгенерирован ( vendor.js).

new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity),

Теперь важная часть, теперь вы должны указать, что означает vendorбиблиотека, и вы делаете это в разделе ввода. Еще один элемент в списке записей с тем же именем, что и имя только что объявленного чанка (в данном случае «vendor»). Значением этой записи должен быть список всех модулей, которые вы хотите переместить в vendorпакет. в вашем случае это должно выглядеть примерно так:

entry: {
    app: 'entry.js',
    vendor: ['jquery', 'jquery.plugin1']
}

JQuery как глобальный

Была такая же проблема и решена с помощью ProvidePlugin . здесь вы определяете не глобальный объект, а какие-то помехи для модулей. то есть вы можете настроить его так:

new webpack.ProvidePlugin({
    $: "jquery"
})

И теперь вы можете просто использовать в $любом месте вашего кода - веб-пакет автоматически преобразует это в

require('jquery')

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

Я люблю webpack, но я согласен, что документация не самая лучшая в мире ... но эй .. люди говорили то же самое об Angular документации в начале :)


Редактировать:

Чтобы иметь чанки вендоров для конкретной точки входа, просто используйте CommonsChunkPlugins несколько раз:

new webpack.optimize.CommonsChunkPlugin("vendor-page1", "vendor-page1.js", Infinity),
new webpack.optimize.CommonsChunkPlugin("vendor-page2", "vendor-page2.js", Infinity),

а затем объявите разные внешние библиотеки для разных файлов:

entry: {
    page1: ['entry.js'],
    page2: ['entry2.js'],
    "vendor-page1": [
        'lodash'
    ],
    "vendor-page2": [
        'jquery'
    ]
},

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

Михал Маргиэль
источник
Большое спасибо за ваш ответ. Это был лучший подход, который я видел до сих пор, но, к сожалению, он по-прежнему не решает мою проблему ... Я проверил ваш пример, и файл vendor.js будет по-прежнему содержать весь код из jquery и jquery.plugin1, даже если они не требуются ни одним из моих модулей. Это означает, что в конце они всегда будут загружены в браузер. Если у меня будет много плагинов jquery, это приведет к очень большому файлу, даже если используется только половина из них. Нет ли способа включить 'jquery.plugin1' в комплект поставщика, только если это необходимо?
Бенсампайо
спасибо, так что я тоже кое-что узнал :) Я обновил свой ответ созданием нескольких вендоров. Может быть, теперь это будет лучше для вас.
Михал Маргиэль
4
Проблема с этим решением состоит в том, что оно предполагает, что я знаю, каковы зависимости для каждой страницы. Но я не могу предсказать, что ... jQuery должен быть включен только в комплект поставщика, если этого требует один из модулей, используемых на странице. Указав, что в файле конфигурации он всегда будет в комплекте поставщика, даже если он не требуется ни для одного модуля, используемого на странице, верно? По сути, я не могу предсказать содержимое пакетов поставщиков, иначе у меня будет адская работа, потому что у меня нет только двух страниц, у меня есть сотни ... У вас есть проблема? Любые идеи? :)
bensampaio
Я понимаю, что вы говорите, но я не вижу в этом проблемы. Если вы используете новую библиотеку на странице, просто добавьте ее в списки библиотек поставщиков для этой страницы. Это всего лишь несколько символов. В любом случае, в вашем решении вы должны сделать это, указав loader. Если вы не знаете, на каких страницах будет использоваться ваш вновь созданный модуль, - тогда позвольте плагину CommonChuncks автоматически извлекать общие библиотеки из ваших модулей.
Михал Маргиэль
Как я могу установить контекст отдельно для файлов вендора?
harshes53
44

Если вы заинтересованы в автоматическом объединении ваших скриптов отдельно от вендоров:

var webpack = require('webpack'),
    pkg     = require('./package.json'),  //loads npm config file
    html    = require('html-webpack-plugin');

module.exports = {
  context : __dirname + '/app',
  entry   : {
    app     : __dirname + '/app/index.js',
    vendor  : Object.keys(pkg.dependencies) //get npm vendors deps from config
  },
  output  : {
    path      : __dirname + '/dist',
    filename  : 'app.min-[hash:6].js'
  },
  plugins: [
    //Finally add this line to bundle the vendor code separately
    new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.min-[hash:6].js'),
    new html({template : __dirname + '/app/index.html'})
  ]
};

Вы можете прочитать больше об этой функции в официальной документации .

Freezystem
источник
4
Обратите внимание, что vendor : Object.keys(pkg.dependencies) это не всегда работает и зависит от того, как собран пакет.
Маркиф
1
Ты всегда зависишь от того, как ты package.jsonнастроен. Этот обходной путь работает в большинстве случаев, но есть исключения, когда вам придется выбрать другой путь. Может быть интересно опубликовать свой ответ на вопрос, чтобы помочь сообществу.
Freezystem
17
Мне это нравится. Это заставило меня немного пописать.
cgatian
3
обратите внимание, что он даже будет включать пакеты, которые вы, возможно, даже не используете в своем коде ... из-за того, что Object.keys(pkg.dependencies)будет связывать все !!!! пусть говорит, что там есть куча загрузчиков ... да, это будет включено !!! так что будьте осторожны ... разделите осторожно, что такое devDependency и что такое зависимость
Рафаэль Милевски
1
@RafaelMilewski зачем вам грузчики dependencies?
Брюки
13

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

entry: {
  bundle1: './build/bundles/bundle1.js',
  bundle2: './build/bundles/bundle2.js',
  'vendor-bundle1': [
    'react',
    'react-router'
  ],
  'vendor-bundle2': [
    'react',
    'react-router',
    'flummox',
    'immutable'
  ]
},

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor-bundle1',
    chunks: ['bundle1'],
    filename: 'vendor-bundle1.js',
    minChunks: Infinity
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor-bundle2',
    chunks: ['bundle2'],
    filename: 'vendor-bundle2-whatever.js',
    minChunks: Infinity
  }),
]

И ссылка на CommonsChunkPluginдокументы: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

Алекс Федосеев
источник
Я считаю, что проблема с этим решением та же, что и у Михала. Вы предполагаете, что я знаю зависимости от поставщиков для bundle1 и bundle2, но я не знаю ... Представьте, что у вас есть 200 пакетов, вы хотите указать все это в файле конфигурации? Используя ваш пример, он reactдолжен присутствовать в комплекте поставщика только в том случае, если это явно требуется для bundle1 и bundl2. Мне не нужно указывать это в файле конфигурации ... Имеет ли это смысл? Любые идеи?
Бенсампайо
@ Anakin вопрос в том, почему вы хотите объединить 200 инструментов поставщиков в отдельный файл. Я бы только объединял общие инструменты в отдельный файл, а оставшиеся оставлял с пакетами проекта.
Maxisam
@ Анакин Я думаю, что я имею дело с той же проблемой, поправьте меня, если я не прав? stackoverflow.com/questions/35944067/…
pjdicke