Подтвердить что ты не робот

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

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

  • Оба приложения и плагины связаны с Webpack
  • Приложение и плагины скомпилированы и распределены независимо.

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

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

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

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

  • Чтобы избежать столкновений идентификаторов блоков/модулей для каждого плагина, для загрузки блоков плагинов будут использоваться отдельные функции загрузчика 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 соответственно.

Ваш сервер backend должен обслуживать файлы дистрибутива каждого установленного плагина. Например, мой серверный сервер обслуживает содержимое каталога плагинов 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 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'
}
4b9b3361