解决方案:动态加载独立编译的Webpack 2包

时间:2017-04-02 02:09:54

标签: javascript webpack

我想分享如何捆绑一个充当插件主机的应用程序,以及它如何加载已安装的插件动态

  1. 应用程序和插件都与Webpack捆绑在一起
  2. 应用程序和插件是独立编译和分发的
  3. 网上有几个人正在寻找解决这个问题的方法:

    此处描述的解决方案基于@ 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.libraryoutput.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.jsmain.js包含在JSONP加载程序函数中,其名称分别取自output.jsonpFunctionoutput.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.jsvendor.jsmain.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'
    }
    

0 个答案:

没有答案