Main, Preload and Renderer in Electron and IPC
📒

Main, Preload and Renderer in Electron and IPC

Tags
JavaScript
Web Dev
Desktop Development
Cross-Platform
Published
July 10, 2024
Electron is a powerful framework that allows developers to build cross-platform desktop applications using web technologies like HTML, CSS, and JavaScript. One of the key features that make Electron so versatile is its ability to bridge the gap between web pages and the operating system through preload scripts. In this post, we’ll delve into what preload scripts are, how to use them securely, and how to set up inter-process communication (IPC) between the main and renderer processes.

Process Model

Electron’s architecture consists of two main types of processes: the main process and the renderer process. The main process is a Node.js environment with full access to the operating system, while the renderer process runs web pages and doesn’t have Node.js access by default for security reasons. To bridge these two environments, Electron uses preload scripts.
A preload script runs in the renderer process but has access to both the HTML DOM and a limited set of Node.js and Electron APIs. This unique capability allows developers to safely expose privileged APIs to the renderer process.

Creating and Using a Preload Script

To understand how to create and use a preload script, let’s walk through a simple example where we expose the versions of Chrome, Node.js, and Electron to the renderer process.
  1. Creating the Preload Script
    1. First, create a file named preload.js in your project directory:
      javascriptCopy code const { contextBridge } = require('electron'); contextBridge.exposeInMainWorld('versions', { node: () => process.versions.node, chrome: () => process.versions.chrome, electron: () => process.versions.electron });
      This script uses contextBridge.exposeInMainWorld to safely expose the versions object to the renderer process.
  1. Attaching the Preload Script to the BrowserWindow
    1. Next, modify your main.js to attach the preload script to your BrowserWindow:
      javascriptCopy code const { app, BrowserWindow } = require('electron'); const path = require('node:path'); const createWindow = () => { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } }); win.loadFile('index.html'); }; app.whenReady().then(() => { createWindow(); });
  1. Accessing Exposed Variables in the Renderer
    1. In your renderer.js, you can now access the versions object and display it in your HTML:
      javascriptCopy code const information = document.getElementById('info'); information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`;
      Ensure your index.html includes this script and has an element with the info id:
      htmlCopy code <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello from Electron renderer!</title> </head> <body> <h1>Hello from Electron renderer!</h1> <p id="info"></p> <script src="./renderer.js"></script> </body> </html>

Communicating Between Processes with IPC

Electron’s main and renderer processes have distinct responsibilities and cannot directly access each other’s APIs. However, they can communicate using IPC (Inter-Process Communication) through the ipcMain and ipcRenderer modules.
Here’s how to set up a simple IPC example:
  1. Modify the Preload Script to Include IPC
    1. Update preload.js to expose a ping function that sends a message to the main process:
      javascriptCopy code const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('versions', { node: () => process.versions.node, chrome: () => process.versions.chrome, electron: () => process.versions.electron, ping: () => ipcRenderer.invoke('ping') });
  1. Set Up the IPC Listener in the Main Process
    1. Modify main.js to handle the ping message:
      javascriptCopy code const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('node:path'); const createWindow = () => { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } }); win.loadFile('index.html'); }; app.whenReady().then(() => { ipcMain.handle('ping', () => 'pong'); createWindow(); });
  1. Send and Receive Messages in the Renderer
    1. In renderer.js, call the ping function and log the response:
      javascriptCopy code const func = async () => { const response = await versions.ping(); console.log(response); // prints out 'pong' }; func();

Summary

Preload scripts are a powerful feature in Electron that enable secure access to Node.js and Electron APIs from the renderer process. By following the steps outlined above, you can create a preload script, expose APIs to the renderer, and set up inter-process communication using IPC.
This setup ensures your Electron app can leverage the full power of the operating system while maintaining security best practices. Happy coding!
For more detailed explanations and additional functionalities, be sure to check out Electron’s official documentation on Process Sandboxing and Inter-Process Communication.