Решение: загружать независимо скомпилированные пакеты Webpack 2 динамически
Я хотел бы поделиться, как связать приложение, которое действует как хост плагина и как оно может динамически загружать установленные плагины.
- И приложение, и плагины поставляются в комплекте с Webpack.
- Приложение и плагины компилируются и распространяются независимо.
В сети есть несколько человек, которые ищут решение этой проблемы:
- Многопроектная сборка и динамическая загрузка модулей с помощью веб-пакета
- Загрузка готовых пакетов веб-пакетов во время выполнения
- Как выставить объекты из пакета Webpack и добавить внешние библиотеки в скомпилированный пакет?
- Динамический Требуется
https://github.com/webpack/webpack/issues/118
Описанное здесь решение основано на комментарии @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'
}