我想分享如何捆绑一个充当插件主机的应用程序,以及它如何加载已安装的插件动态。
网上有几个人正在寻找解决这个问题的方法:
此处描述的解决方案基于@ sokra 2014年4月17日对Webpack问题#118的评论,并略微调整以便与Webpack 2一起使用。 https://github.com/webpack/webpack/issues/118
要点:
插件需要一个ID(或" URI"),它在后端服务器上注册,并且对应用程序来说是唯一的。
为了避免每个插件的块/模块ID冲突,将使用单独的JSONP
加载器函数来加载插件的块。
加载插件是由动态创建的<script>
元素(而不是require()
)启动的,让主应用程序最终通过JSONP
使用插件的导出回调。
注意:你可能会发现Webpack&#34; JSONP&#34;措辞误导,因为实际上没有转移JSON
,但插件的Javascript包含在&#34;加载器功能&#34;中。服务器端没有填充。
制作插件
插件的构建配置使用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'
},
...
}
由插件开发人员为插件选择一个唯一的ID(或&#34; URI&#34;)并使其在插件配置中可用。在这里,我使用变量pluginURI
:
// unique plugin ID (using dots for namespacing)
var pluginUri = 'com.companyX.pluginY'
对于library
选项,您还必须为插件指定唯一名称。生成JSONP
加载器函数时,Webpack将使用此名称。我从插件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
。
您的后端服务器必须提供每个已安装插件的分发文件。例如,我的后端服务器将插件的URI下的插件dist/
目录的内容作为第一个路径组件提供:
/com.companyX.pluginY/js/manifest.js
/com.companyX.pluginY/js/vendor.js
/com.companyX.pluginY/js/main.js
这就是为什么publicPath
在示例插件配置中设置为'/' + pluginUri + '/'
的原因。
注意:分发文件可以作为静态资源提供。后端服务器不需要进行任何填充(JSONP
中的&#34; P&#34;)。分发文件是&#34;填充&#34; 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)
}
助手:
function pluginIdent (pluginUri) {
return '_' + pluginUri.replace(/\./g, '_')
}
function pluginChunk (pluginUri, name) {
return '/' + pluginUri + '/js/' + name + '.js'
}