最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Dynamically loading React components - Stack Overflow

programmeradmin2浏览0评论

I'm thinking about building a web application, where people can install plugins. I'd like plugins to be able to define React components that will be rendered to the page, without recompiling the main JavaScript bundle after installing the plugin.

So here's the approach I'm thinking of:

  • Bundle the main JavaScript with React as an external library, using webpack.
  • Have plugin authors compile their components with React as an external library as well.

This way, I'm only running one instance of React. I could probably do the same with some other frequently used libraries.

The problem then, is how to dynamically load these plugin components from the server. Let's say I have the following component:

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..."
  }
}

How could getPluginComponent be implemented?

I'm thinking about building a web application, where people can install plugins. I'd like plugins to be able to define React components that will be rendered to the page, without recompiling the main JavaScript bundle after installing the plugin.

So here's the approach I'm thinking of:

  • Bundle the main JavaScript with React as an external library, using webpack.
  • Have plugin authors compile their components with React as an external library as well.

This way, I'm only running one instance of React. I could probably do the same with some other frequently used libraries.

The problem then, is how to dynamically load these plugin components from the server. Let's say I have the following component:

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..."
  }
}

How could getPluginComponent be implemented?

Share Improve this question asked Jun 27, 2017 at 10:40 bigblindbigblind 12.9k14 gold badges70 silver badges128 bronze badges 2
  • In order to achieve this you'd need server side rendering with NodeJS to access the file system and I am still not sure if it would be possible, bare in mind that the component would still need to be compiled by Babel somehow and that does not happen on runtime. – Foxhoundn Commented Jun 27, 2017 at 11:16
  • Yes, I'm aware the component would still need to be compiled. The compiled source would be served as a static asset. – bigblind Commented Jun 27, 2017 at 11:18
Add a comment  | 

3 Answers 3

Reset to default 13

It's an interesting problem I also faced some months ago for customer work, and I didn't see too many document approaches out there. What we did is:

  1. Individual plugins will be separate Webpack projects, for which we provide either a template or a CLI tool that generates project templates.

  2. In this project we define Webpack externals for shared vendor libraries already used in the core application: React, Redux, etc. This tells the plugin to not include those in the bundle but to grab them from a variable in window we set in the core app. I know, sounds like sucks, but it's much better than having all plugins re-include 1000s of shared modules.

  3. Reusing this concept of external, the core app also provides some services via window object to plugins. Most important one is a PluginService.register() method which your plugin must call when it's initialized. We're inverting control here: the plugin is responsible to say "hi I'm here, this is my main export (the Component if it's a UI plugin)" to the core application.

  4. The core application has a PluginCache class/module which simply holds a cache for loaded plugins (pluginId -> whatever the plugin exported, fn, class, whatever). If some code needs a plugin to render, it asks this cache for it. This has the benefit of allowing to return a <Loading /> or <Error /> component when a plugin did not load correctly, and so on.

  5. For plugin loading, this PluginService/Manager loads the plugin configuration (which plugins should I load?) and then creates dynamically injected script tags to load each plugin bundle. When the bundle is finished, the register call described in step 3 will be called and your cache in step 4 will have the component.

  6. Instead of trying to load the plugin directly from your component, ask for it from the cache.

This is a very high level overview which is pretty much tied to our requirements back then (it was a dashboard-like application where users could add/remove panels on the fly, and all those widgets were implemented as plugins).

Depending on your case, you could even wrap the plugins with a <Provider store={ theCoreStore }> so they have to access to Redux, or setup an event bus of some kind so that plugins can interact with each other... There is plenty of stuff to figure out ahead. :)

Good luck, hope it helped somehow!

There is a HOC component that you can import to do this. Components are dynamically loaded as micro apps into your host application.

// 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;

The components are not installed or known at design time. If you get creative, using this approach you could update your components without needing to redeploy your host application.

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

I presented an alternative approach on a similar question. Recapping:

On your app

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

On your plugin...

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

See more details on the other question's page.

发布评论

评论列表(0)

  1. 暂无评论