Решение: загружать независимо скомпилированные пакеты Webpack 2 динамически

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

  1. И приложение, и плагины поставляются в комплекте с Webpack.
  2. Приложение и плагины компилируются и распространяются независимо.

В сети есть несколько человек, которые ищут решение этой проблемы:

Описанное здесь решение основано на комментарии @sokra от 17 апреля 2014 года о проблеме Webpack № 118 и немного адаптировано для работы с Webpack 2. https://github.com/webpack/webpack/issues/118

Основные моменты:

  • Плагину нужен идентификатор (или "URI"), по которому он регистрируется на внутреннем сервере и который является уникальным для приложения.

  • Во избежание коллизий ID блоков / модулей для каждого плагина, индивидуально JSONP Функции загрузчика будут использоваться для загрузки кусков плагина.

  • Загрузка плагина инициируется динамически созданным <script> элементы (вместо require()) и пусть основное приложение в конечном итоге потребляет экспорт плагина через JSONP Перезвоните.

Примечание. Вы можете найти неверную формулировку Webpack "JSONP", поскольку JSON передается, но Javascript плагина обернут в "функцию загрузчика". Заполнение не происходит на стороне сервера.

Сборка плагина

Конфигурация сборки плагина использует Webpack output.library а также output.libraryTarget опции.

Пример конфигурации плагина:

module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/' + pluginUri + '/',
    filename: 'js/[name].js',
    library: pluginIdent,
    libraryTarget: 'jsonp'
  },
  ...
}

Разработчик плагина должен выбрать уникальный идентификатор (или "URI") для плагина и сделать его доступным в конфигурации плагина. Здесь я использую переменную pluginURI:

// unique plugin ID (using dots for namespacing)
var pluginUri = 'com.companyX.pluginY'

Для library Также вы должны указать уникальное имя для плагина. Webpack будет использовать это имя при создании JSONP Функции загрузчика. Я получаю имя функции из URI плагина:

// transform plugin URI into a valid function name
var pluginIdent = "_" + pluginUri.replace(/\./g, '_')

Обратите внимание, что когда library опция установлена ​​Webpack получает значение для output.jsonpFunction опция автоматически.

При сборке плагина Webpack генерирует 3 дистрибутивных файла:

dist/js/manifest.js
dist/js/vendor.js
dist/js/main.js

Обратите внимание, что vendor.js а также main.js обернуты в функции загрузчика JSONP, чьи имена взяты из output.jsonpFunction а также output.library соответственно.

Ваш сервер должен обслуживать файлы дистрибутива каждого установленного плагина. Например, мой внутренний сервер обслуживает содержимое плагина dist/ каталог под URI плагина как первый компонент пути:

/com.companyX.pluginY/js/manifest.js
/com.companyX.pluginY/js/vendor.js
/com.companyX.pluginY/js/main.js

Вот почему publicPath установлен в '/' + pluginUri + '/' в примере конфигурации плагина.

Примечание. Файлы распространения могут быть использованы в качестве статических ресурсов. Внутренний сервер не обязан делать какие-либо дополнения ("P" в JSONP). Файлы дистрибутива "дополняются" Webpack уже во время сборки.

Загрузка плагинов

Основное приложение должно получать список установленных плагинов (URI) с внутреннего сервера.

// retrieved from server
var pluginUris = [
  'com.companyX.pluginX',
  'com.companyX.pluginY',
  'org.organizationX.pluginX',
]

Затем загрузите плагины:

loadPlugins () {
  pluginUris.forEach(pluginUri => loadPlugin(pluginUri, function (exports) {
    // the exports of the plugin's main file are available in `exports`
  }))
}

Теперь приложение имеет доступ к экспорту плагина. На этом этапе исходная проблема загрузки независимо скомпилированного плагина в основном решена:-)

Плагин загружается путем загрузки его 3 блоков (manifest.js, vendor.js, main.js) по порядку. После загрузки main.js будет вызван обратный вызов.

function loadPlugin (pluginUri, mainCallback) {
  installMainCallback(pluginUri, mainCallback)
  loadPluginChunk(pluginUri, 'manifest', () =>
    loadPluginChunk(pluginUri, 'vendor', () =>
      loadPluginChunk(pluginUri, 'main')
    )
  )
}

Вызов обратного вызова работает путем определения глобальной функции, имя которой равно output.library как в конфиге плагина. Приложение получает это имя из pluginUri (так же, как мы уже делали в конфиге плагина).

function installMainCallback (pluginUri, mainCallback) {
  var _pluginIdent = pluginIdent(pluginUri)
  window[_pluginIdent] = function (exports) {
    delete window[_pluginIdent]
    mainCallback(exports)
  }
}

Чанк загружается путем динамического создания <script> элемент:

function loadPluginChunk (pluginUri, name, callback) {
  return loadScript(pluginChunk(pluginUri, name), callback)
}

function loadScript (url, callback) {
  var script = document.createElement('script')
  script.src = url
  script.onload = function () {
    document.head.removeChild(script)
    callback && callback()
  }
  document.head.appendChild(script)
}

Helper:

function pluginIdent (pluginUri) {
  return '_' + pluginUri.replace(/\./g, '_')
}

function pluginChunk (pluginUri, name) {
  return '/' + pluginUri + '/js/' + name + '.js'
}

0 ответов

Другие вопросы по тегам