A plugin system for JS & React apps.
React Pluggable: A plugin system for JS & React apps
While React itself is a plugin system in a way, it focuses on the abstraction of the UI. It is inherently declarative which makes it intuitive for developers when building UI. With the help of React Pluggable, we can think of our app as a set of features instead of a set of components. It provides a mixed approach to solve this problem.
We at GeekyAnts have used React Pluggable for large & complex apps like BuilderX to add independent and dependent features over time, and it has worked wonderfully for us. Find out more on our official documentation.
In React, we think of everything as components. If we want to add a new feature, we make a new component and add it to our app. Every time we have to enable/disable a feature, we have to add/remove that component from the entire app and this becomes cumbersome when working on a complex app where there are lots of features contributed by different developers.
We are a huge fan of Laravel and love how Service Provider works in it. Motivated by how we register any service for the entire app from one place, we built a plugin system that has all your features and can be enabled/disabled with a single line of code.
React Pluggable simplifies the problem in 3 simple steps:
The O in SOLID stands for Open-closed principle which means that entities should be open for extension but closed for modification. With React Pluggable, we can add plugins and extend a system without modifying existing files and features.
React is inherently declarative which makes it intuitive for developers when building UI but it also makes extensibility hard.
React abstracts components very well but a feature may have more than just components. React Pluggable pushes you to a feature mindset instead of a component mindset.
Use npm or yarn to install this to your application:
npm install react-pluggable
yarn add react-pluggable
ShowAlertPlugin.tsx
import React from 'react';
import { IPlugin, PluginStore } from 'react-pluggable';
class ShowAlertPlugin implements IPlugin {
public pluginStore: any;
getPluginName(): string {
return 'ShowAlert';
}
getDependencies(): string[] {
return [];
}
init(pluginStore: PluginStore): void {
this.pluginStore = pluginStore;
}
activate(): void {
this.pluginStore.addFunction('sendAlert', () => {
alert('Hello from the ShowAlert Plugin');
});
}
deactivate(): void {
this.pluginStore.removeFunction('sendAlert');
}
}
export default ShowAlertPlugin;`
App.tsx
import React from 'react';
import './App.css';
import { createPluginStore, PluginProvider } from 'react-pluggable';
import ShowAlertPlugin from './plugins/ShowAlertPlugin';
import Test from './components/Test';
const pluginStore = createPluginStore();
pluginStore.install(new ShowAlertPlugin());
const App = () => {
return (
<PluginProvider pluginStore={pluginStore}>
<Test />
</PluginProvider>
);
};
export default App;
Test.tsx
import * as React from 'react';
import { usePluginStore } from 'react-pluggable';
const Test = () => {
const pluginStore = usePluginStore();
return (
<>
<button
onClick={() => {
pluginStore.executeFunction('sendAlert');
}}
>
Show Alert
</button>
</>
);
};
export default Test;
Sometimes a plugin has a UI component associated with it. You can implement this functionality by simply building a plugin of your own or using the default plugin provided by the package.
SharePlugin.tsx
import React from 'react';
import { IPlugin, PluginStore } from 'react-pluggable';
class SharePlugin implements IPlugin {
public pluginStore: any;
getPluginName(): string {
return 'Share plugin';
}
getDependencies(): string[] {
return [];
}
init(pluginStore: PluginStore): void {
this.pluginStore = pluginStore;
}
activate(): void {
this.pluginStore.executeFunction('RendererPlugin.add', 'top', () => (
<button>Share</button>
));
}
deactivate(): void {
//
}
}
export default SharePlugin;
You can add the inbuilt renderer plugin by importing and installing RendererPlugin
provided in the package.
App.tsx
import \* as React from 'react';
import { usePluginStore } from 'react-pluggable';
import {
createPluginStore,
PluginProvider,
RendererPlugin,
} from 'react-pluggable';
import SharePlugin from './plugins/SharePlugin';
import Test from './components/Test';
const pluginStore = createPluginStore();
pluginStore.install(new RendererPlugin());
pluginStore.install(new SharePlugin());
function App() {
return (
<PluginProvider pluginStore={pluginStore}>
<Test />
</PluginProvider>
);
}
export default App;
Test.tsx
import \* as React from 'react';
import { usePluginStore } from 'react-pluggable';
const Test = (props: any) => {
const pluginStore: any = usePluginStore();
let Renderer = pluginStore.executeFunction(
'RendererPlugin.getRendererComponent'
);
return (
<>
<h1>I am header</h1>
<Renderer placement={'top'} />
</>
);
};
export default Test;
Here are some examples of React Pluggable:
Javascript & React.
Although the naming of plugins, functions, or events is not an enforced rule, we recommend a few conventions to standardize things.
<plugin_name>.<function_name>
. Ex : Auth.authenticate.<plugin_name>.<event_name>
Ex: Auth.checking.Example :
AuthPlugin.tsx
import React from 'react';
import { IPlugin, PluginStore } from 'react-pluggable';
class AuthPlugin implements IPlugin {
getPluginName(): string {
return '[email protected]'; //line 2
}
getDependencies(): string[] {
return [];
}
public pluginStore: any;
init(pluginStore: PluginStore): void {
this.pluginStore = pluginStore;
}
authenticate = (credentials: object) => {
// checks the credentials and returns username if matches.
return { name: 'username' };
};
activate(): void {
this.pluginStore.addFunction(
'Auth.authenticate', //line 3
(credentials: object) => this.authenticate(credentials)
);
}
deactivate(): void {
this.pluginStore.removeFunction('Auth.authenticate'); //line 4
}
}
export default AuthPlugin;
We have seen in the class AuthPlugin that the name of the plugin is used several times. An alternate way is to define a variable that stores the name of the plugin and use that variable in the class wherever we want the plugin name.
AuthPlugin
import React from 'react';
import { IPlugin, PluginStore } from 'react-pluggable';
class AuthPlugin implements IPlugin {
private namespace = 'Auth';
getPluginName(): string {
return `${this.namespace}@1.0.0`; //line 2
}
getDependencies(): string[] {
return [];
}
public pluginStore: any;
init(pluginStore: PluginStore): void {
this.pluginStore = pluginStore;
}
authenticate = (credentials: object) => {
// checks the credentials and returns username if matches.
return { name: 'username' };
};
activate(): void {
this.pluginStore.addFunction(
`${this.namespace}.authenticate`, //line 3
(credentials: object) => this.authenticate(credentials)
);
}
deactivate(): void {
this.pluginStore.removeFunction(`${this.namespace}.authenticate`); //line 4
}
}
export default AuthPlugin;
Thank you for your interest in contributing to React Pluggable! Pull requests are welcome. Head over to Contribution Guidelines and learn how you can be a part of a wonderful, growing community.
For major changes, please open an issue first to discuss changes and update tests as appropriate.
Licensed under the MIT License, Copyright © 2020 GeekyAnts. See LICENSE for more information.