eimer.js (Electron Interprocess MEssage Router) is an inter-process communication (IPC) library for electron.js applications, which greatly reduces the amount of boilerplate code needed to interact between different processes.
It is written in TypeScript and comes with minimal dependencies. It has been developed for modern versions of Electron.js (>= 28).
Even simple Electron applications are naturally split into two different processes (main and renderer), which have to be linked through a "preload script". Exchanging data and calling functions across these process boundaries is already non-trivial and the interfaces provided by Electron make it difficult to keep a growing code base maintainable.
As applications grow in functionality and complexity, even more processes become involved - multiple windows on the rendering side, each potentially with web workers or iframes and the main process can start worker threads or spawn utility processes which themselves can spawn more processes and threads.
All of the different process-like concepts in Electron have slightly different APIs for inter-process communication and while MessagePorts can be used to unify these concepts, even these are limited to point-to-point communication between at most two processes at a time.
- Transports unify different IPC APIs under a single, common interface
- ipcMain, ipcRenderer, MessagePort, window.postMessage, node:worker_threads
- Message Bus transparently forwards IPC messages across all connected processes
- Publish/Subscribe pattern to broadcast and listen to events across processes
- Remote Procedure Call (RPC) pattern to invoke methods in different processes with a promise-based interface
We'll assume that you already have an Electron.js project that is written in TypeScript and it can be compiled / started.
If not, check out the examples directory! It also contains examples that show how to implement a basic counter with RPC and publish/subscribe and how to extend that to multiple windows or worker threads.
npm install eimerNote that the following examples have been stripped down for brevity. Add best practices and coding style on your own. Also, you can find full source code, including build configurations, in the examples directory.
The Main Process sets up an instance of eimer.js which uses Electron's ipcMain to communicate with the renderer process. It also registers a simple "Hello World" RPC Handler. Every instance of Eimer has a unique "node id", which is passed to its constructor.
import { app, BrowserWindow, ipcMain } from 'electron';
import { Eimer, EimerIpcMainTransport } from 'eimer';
const eimer = new Eimer('main');
eimer.registerRpcHandler('hello', async () => 'world');
app.whenReady().then(() => {
const mainWindow = new BrowserWindow({
width: 800, height: 600, webPreferences: { preload: __dirname + '/preload.js' }
});
eimer.connect(new EimerIpcMainTransport(ipcMain, mainWindow.webContents));
mainWindow.loadFile('index.html');
mainWindow.webContents.openDevTools();
app.on('window-all-closed', () => { app.quit() });
});The purpose of the Preload Script is to set up communication between the main process and the "browser" window, without exposing too many APIs of the main process in the browser context.
We set up another instance of eimer.js in the preload script, which simply forwards messages. It uses Electron's ipcRenderer to communicate with the main process and window.postMessage to communicate with the browser window.
import { ipcRenderer } from 'electron';
import { Eimer, EimerIpcRendererTransport, EimerBrowserTransport } from 'eimer';
const eimer = new Eimer('preload');
eimer.connect(new EimerIpcRendererTransport(ipcRenderer));
eimer.connect(new EimerBrowserTransport(window, { targetOrigin: '*' }));The Browser Window sets up an instance of eimer.js, which uses window.postMessage to communicate with the preload script.
import { Eimer, EimerBrowserTransport } from 'eimer';
const eimer = new Eimer('renderer');
eimer.connect(new EimerBrowserTransport(window));
eimer.callRpc<string>('hello').then(value => { console.log('hello ' + value) });- The Preload Script as well as the Renderer / Browser Code run inside separate browser environments and need to be separately compiled and bundled with the eimer.js library.
- This is not specific to eimer.js but a general pitfall of Electron Applications
- You can add extra build scripts to the
package.jsonthat rely on esbuild (npm install esbuild)"scripts": { "build": "tsc && npm run build:preload && npm run build:renderer", "build:preload": "esbuild src/preload.ts --bundle --outfile=dist/preload.js --external:electron", "build:renderer": "esbuild src/renderer.ts --bundle --outfile=dist/renderer.js", }
- Be sure to call the
npm run build:preloadand thenpm run build:rendererscripts in your build process! - Additional options for esbuild (build:preload and build:renderer) that could be useful
--platform=browser--target=es2020--format=cjs
- Check out the examples directory for complete projects including build configurations.
This library is in pre-beta stage.
Existing functionality mostly works and is well tested. However, performance is not optimized at all (for example, all messages are broadcast to all connected processes, the ids of all messages ever sent is kept in memory indefinitely) and features are limited (security and extensibility need improvement).
Also the documentation is lacking.
Therefore, this library cannot be considered ready for production use just yet.
