【问题标题】:Dynamically loading React components动态加载 React 组件
【发布时间】:2017-06-27 10:40:36
【问题描述】:

我正在考虑构建一个 Web 应用程序,人们可以在其中安装插件。我希望插件能够定义将呈现到页面的 React 组件,而无需在安装插件后重新编译主 JavaScript 包。

所以这是我正在考虑的方法:

  • 使用 webpack 将主 JavaScript 与 React 捆绑为 external library。
  • 让插件作者也使用 React 作为外部库来编译他们的组件。

这样,我只运行一个 React 实例。我可能也可以对其他一些常用的库做同样的事情。

那么问题是如何从服务器动态加载这些插件组件。假设我有以下组件:

class PluginRenderer extends React.Component{
  componentWillMount() {
    getPluginComponent(`/plugins/${this.props.plugin}/component.js`).then((com) => {
      this.setState({pluginComponent: com});
    })
  }

  render() {
    var Plugin = this.state.pluginComponent;
    return Plugin ? <Plugin {...this.props} /> : "Loading..."
  }
}

getPluginComponent 怎么实现?

【问题讨论】:

  • 为了实现这一点,您需要使用 NodeJS 进行服务器端渲染来访问文件系统,但我仍然不确定是否可行,请记住该组件仍然需要由 Babel 以某种方式编译,并且不会在运行时发生。
  • 是的,我知道组件仍然需要编译。编译后的源代码将用作静态资产。

标签: javascript reactjs webpack


【解决方案1】:

几个月前我在客户工作中也遇到了一个有趣的问题,我没有看到太多的文档方法。我们所做的是:

  1. 各个插件将是单独的 Webpack 项目,我们为此提供模板或生成项目模板的 CLI 工具。

  2. 在这个项目中,我们为核心应用程序中已经使用的共享供应商库定义 Webpack externals:React、Redux 等。这告诉插件不要将它们包含在包中,而是从变量中获取它们在window 我们在核心应用程序中设置。我知道,这听起来很糟糕,但这比让所有插件重新包含 1000 个共享模块要好得多。

  3. 重用external这个概念,核心应用也通过window对象向插件提供一些服务。最重要的是一个PluginService.register() 方法,你的插件在初始化时必须调用它。我们在这里反转控制:插件负责向核心应用程序说“嗨,我在这里,这是我的主要导出(如果是 UI 插件,则为组件)”。

  4. 核心应用程序有一个 PluginCache 类/模块,它简单地保存已加载插件的缓存(pluginId -> 导出的插件、fn、类等)。如果某些代码需要一个插件来呈现,它会向这个缓存请求它。这样做的好处是允许在插件未正确加载时返回&lt;Loading /&gt; 或&lt;Error /&gt; 组件,等等。

  5. 对于插件加载,此 PluginService/Manager 加载插件配置(我应该加载哪些插件?),然后创建动态注入的 script 标签来加载每个插件包。打包完成后,将调用步骤 3 中描述的 register 调用,并且您在步骤 4 中的缓存将包含该组件。

  6. 不要尝试直接从您的组件加载插件,而是从缓存中请求它。

这是一个非常高级的概述,与我们当时的需求密切相关(它是一个类似仪表板的应用程序,用户可以在其中动态添加/删除面板,所有这些小部件都作为插件实现)。

根据你的情况,你甚至可以用 &lt;Provider store={ theCoreStore }> 包装插件,这样它们就必须访问 Redux,或者设置某种事件总线,以便插件可以相互交互......有很多事情要弄清楚。 :)

祝你好运,希望它有所帮助!

【讨论】:

  • 您能否描述一下 2. 您将依赖项暴露给窗口的位置?我正在尝试完全相同,但我不知道如何。我现在正在掉头发,如果你提供一些示例代码会很高兴。
  • 嘿 @BlackHat 我只是在我的 index.js 中执行类似 window.React = React; window.redux = redux 的操作(实际上是在一个单独的文件中,用于准备 app reducer 等)。只需在您的主应用程序中导入您的依赖项并在窗口中重新导出。因为插件在最终运行时是异步加载的,所以它们已经准备好了 window.XXXX 变量。
  • 我们如何告诉插件使用来自 window.* 的导入而不是使用 require()?
  • @CharlieBrown 似乎我和你的思路一样,但停留在第 3 点。stackoverflow.com/questions/48447988/… 介意为我更详细地扩展这个或提供一个例子:)?
  • 我确实做到了!
【解决方案2】:

您可以导入一个 HOC 组件来执行此操作。组件作为微应用动态加载到您的主机应用程序中。

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

window.React = React;
window.ReactDOM = ReactDOM;

ReactDOM.render(<App />, document.getElementById('root'));

// app.js
import React from 'react';
import ReactDOM from 'react-dom';
import MicroApp from '@schalltech/honeycomb-react-microapp';

const App = () => {
  return (
    <MicroApp
        config={{
          View: {
            Name: 'redbox-demo',
            Scope: 'beekeeper',
            Version: 'latest'
          }
        }}
      />
  );
});

export default App;

这些组件在设计时未安装或已知。如果您有创意,使用这种方法可以更新您的组件,而无需重新部署您的主机应用程序。

https://github.com/Schalltech/honeycomb-marketplace#using-micro-apps

【讨论】:

    【解决方案3】:

    我提交了an alternative approach on a similar question。回顾:

    在您的应用上

    import(/* webpackIgnore: true */'https://any.url/file.js')
      .then((plugin) => {
        plugin.main({ /* stuff from app plugins need... */ });
      });
    

    在你的插件上...

    const main = (args) => console.log('The plugin was started.');
    export { main };
    export default main;
    

    查看更多详情on the other question's page。

    【讨论】:

      猜你喜欢
      • 2023-03-22
      • 2022-07-19
      • 1970-01-01
      • 2017-10-12
      • 1970-01-01
      • 2018-09-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-19
      相关资源
      最近更新 更多