Pluggable Electron is a framework to build Electron apps that can be extended by other parties.
Pluggable Electron allows an Electron app to include extension points in the code. Plugin developers can then write extensions - in the form of npm packages - that can be inserted into these extension points.
The framework includes the tools necessary to manage the whole life cycle of plugins, for example writing, installing, uninstalling and updating plugins, and creating and triggering extension points.
The framework uses inversion of control and dependency inversion principles for this.
As of version 0.6.0 the largest development effort seems to be done. I will try to keep the interface as steady as possible from now on but as always, changes can happen.
Version 0.6.0 has however included quite a number of breaking changes to ensure all the necessary interfaces and features are now included. For people migrating from an earlier version, please see the release notes for how to update your code.
To include Pluggable Electron, follow these simple steps:
This package should be installed inside an Electron project. Please create that first.
Add Pluggable Electron in your project as a dependency to your project
npm install pluggable-electron
Below you will find a quick start guide on how to set Pluggable Electron up. A full guide can be found in the wiki
The framework is built around the concepts of Extension points and Plugins
Extension points are added to your renderer code.
Execute extension point where you want to provide plugin developers with the possibility to extend your code. This can be done as a handover, parallel execution, or serial execution. These options are explained here.
// renderer/your-module.js
import { extensionPoints } from "pluggable-electron/renderer"
// ... Your business logic ...
const extendMenu = extensionPoints.execute('purchase_menu', purchaseMenu )
// extendMenu will contain the result of any extensions registered to purchase_menu
To determine the extensions that need to be executed by an extension point, it needs to be possible for plugins to register to an extension. This is done by creating activation points which are the points where plugins are activated. On activation, a plugin can register functions or objects to extension points (see below). Creating an activation point requires the activation point manager to be set up.
There can be different strategies for activating the plugin, like:
- Activating all plugins during startup - one point during the app startup for synchronous extensions and one after startup for async extensions
- Activating the relevant plugins just before an extension point is triggered.
See the API for the activation point manager here.
// renderer/index.js
import { setup, activationPoints } from "pluggable-electron/renderer"
// Enable the activation points
setup({
// Provide the import function
importer: async (pluginPath) => import( /* webpackIgnore: true */ pluginPath)
})
// insert at any point
activationPoints.trigger( 'init' )
// but before related extension points are triggered.
A plugin is an npm package with activation points added to the package.json.
// package.json
{
...
"main": "index.js",
"activationPoints": [
"init"
]
...
}
The main file of this plugin should include a function for each of the activation points listed in the package.json. This function will be be triggered by the activation point and be passed an object containing a function to register extensions to extension points by default. An extension can be a callback or object returned to the register method.
// index.js
export function init (extensionPoints) {
// Mock function for adding a menu item
const yourCustomExtension = (varFromExtensionPoint) => {
// your extension code here.
// varFromExtensionPoint is provided as a parameter when the extension point is executed;
// purchaseMenu in the example above.
}
// Register to purchase_menu extension point
extensionPoints.register( 'purchase_menu', 'extension-name', yourCustomExtension )
}
Plugins are managed in the main process but can be installed from the renderer or the main process. In this setup we will use the renderer. This requires the initialisation of the plugin facade in the renderer using a preload script. Doing everything from the main process is described in the API documentation.
Once installed, the plugins should be loaded on every startup.
// main.js
const pe = require( "pluggable-electron/main" )
...
app.whenReady().then(() => {
//Initialise pluggable Electron
pe.init({
// Function to check from the main process that user wants to install a plugin for security
confirmInstall: async plugins => {
const answer = await dialog.showMessageBox({
message: `Are you sure you want to install the plugins ${plugins.join(', ')}`,
buttons: ['Ok', 'Cancel'],
cancelId: 1,
})
return answer.response == 0
},
// Folder to save the plugins to
pluginsPath: path.join(app.getPath('userData'), 'plugins')
})
...
})
// preload.js
const useFacade = require("pluggable-electron/preload")
useFacade()
// renderer/your-install-module.js
import { plugins } from 'pluggable-electron/renderer'
// Get plugin file from input and install
document.getElementById( 'install-file-input' ).addEventListener( 'change', (e) =>
plugins.install( [e.target.files[0].path] )
)
// renderer/index.js
import { setup, plugins, activationPoints } from "pluggable-electron/renderer"
// Enable the activation points
setup({
importer: async (pluginPath) => import( /* webpackIgnore: true */ pluginPath)
})
// Get plugins that have been installed previously
// and register them with their activation points
plugins.registerActive()
// insert at any point after the plugins have been registered
activationPoints.trigger( 'init' )
Now the yourCustomExtension
function in the plugin will be executed when the execution point purchase_menu
is triggered.
Pluggable Electron provides a host of functions to support the full plugin lifecycle and some alternative workflows. A more detailed description of the full lifecycle, as well as a detailed API documentation can be found in the wiki.
A demo project using Pluggable Electron can be found here: https://github.com/dutchigor/pluggable-electron-demo. Also check out the with-vue branch to see an example with Vite and Vue. This example contains a few catches to be aware of when using packaged frontend framework so I recommend checking this out for any such framework.
See the open issues for a list of proposed features (and known issues).
Contributions are what make the open-source community such an amazing place to be, learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
See Changelog
Distributed under the MIT License. See LICENSE
for more information.
This project is maintained by Igor Honhoff. Feel free to contact me at igor@flarehub.io Project Link: https://github.com/dutchigor/pluggable-electron