React dynamic plugins loading
What if you want to write some React component that can be a chart that can be plugged into a remote react application. The remote application does not need to pull your source code and rebuild again in order to use your code.
What ever I described above can be largely achieved with plain javascript by simply fetching a script and evaluating it in browser.
How is it different from react lazy loading?
Ans. React/angular kind of libraries do a lot of magic by statically linking. If you look up some dynamic loading and code splitting tutorial, you will find that entire code is statically parsed and mostly webpack does the code splitting based on routes or whereever dynamic imports are used ()=> import(‘./YourPlugin’)
is used.
You can use ReactDOM.render(‘<YourPlugin/>’, some dom elm)
inside your main application. But this becomes two separate applications with no code sharing. In this case the bundle sizes of your plugins will contain entire react code so it will be big.
I am going to show an approach that will build the plugin code into minimal lib without any library or any redundant code. You can even use https://babeljs.io/repl to transpile your plugin code without any react dependency.
Lets first see how to load and render the plugin.
loadPlugins() {
fetch("./dist/"+this.state.pluginName+".plugin.js")
.then((resp) => resp.text())
.then((resp)=>{
this.state.components.push(this.state.pluginName);
this.pluginRegistry[this.state.pluginName] = eval(resp).default;
this.forceUpdate();
});
this.forceUpdate();
}render() {
return (
<div>
<h2>Dynamic Components Loading</h2>
<input type="text" onChange={ (e) => this.state.pluginName = e.target.value} placeholder="Plugin Name D1,D2,...D4 etc"/>
<button onClick={this.loadPlugins.bind(this)}>Load</button>
<div>
{this.state.components.map((componentId) => {
let Component = this.pluginRegistry[componentId];
return <Component>{componentId}</Component>;
})}
</div>
</div>);
}
The above snippet loads the plugin’s compiled js code using window.fetch()
api and then eval()
that code. We may be able to inject dynamic script tags or use some loader like requirejs here.
The plugin code is simple and straightforward
import React from 'react';export default class D2 extends React.Component {constructor(){
super();
console.log('D2 loading')
}render () {
console.log('D2 rendering')
return <div>{this.props.children}</div>
}};
To make the plugin code not package react, react-dom along with it. We have to compile it as a library using webpack. Note that libraryTarget is ‘window’ which means React will be expected to be available in window object.
var path = require('path');
var APP_DIR = path.resolve(__dirname, 'src');module.exports = {
mode: 'development',
entry: {
D1: './src/D1.jsx',
D2: './src/D2.jsx',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].plugin.js',
library: 'D1plugin',
libraryTarget:'window'
},
externals: {
react: {
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
window: 'React'
}
},
module : {
rules : [
{
test : /\.jsx?/,
include : APP_DIR,
loader : 'babel-loader'
}
]
}
};
The main lib react and react-dom are loaded as script tags.
<!DOCTYPE html>
<html lang="en">
<head>
<script src='https://unpkg.com/react@15.3.2/dist/react.js'></script>
<script src='https://unpkg.com/react-dom@15.3.2/dist/react-dom.js'></script>
</head>
<body>
<script async src="dist/main.bundle.js" type="text/javascript"></script>
<div id="output"></div>
</body>
</html>
The complete is code is here.
To run the code.
Git clone https://github.com/samarjit/react-dynamic-plugin
Run http-server in the react-dynamic-plugin folder.
Open the index.html in browser, and run main.load()
in console to load the application. Then Enter ‘D1’ in the text field and click ‘Load’.
The complete source code is available in this project.