Notes on Electron
For some time I have been intrigued by Electron. I got the basic idea that it is essentially a wrapper for a web application that can act as a cross-platform desktop application.
With that being said, I wanted to go on a journey to learn about building Electron Apps. During this journey, several things felt important for me to note. These were more specifically around the Electron processes and not so much the web technologies which may be used inside the Electron App.
Additional Resource
In this post I make use of a lot of extracts from the official Electron Documentation.
The code snippets below is for reference only and would require additional resource to get running. I created an Electron Apps - Starting Point wiki which contains more detail on these code snippets and can be followed to create a starting point for developing an Electron application with Vue 3, Vite, and Tailwind as the base.
Writing the wiki also resulted in a project demonstrating the IPC patterns discussed later on in this post.
Main Process
Every Electron app has a single main process, which serves several purposes:
- It acts as the application's entry point.
- It runs the Node.js environment, which provides access to Node.js built-ins and installed packages, as well as full operating system access.
- It creates and manages application windows with the
BrowserWindow
module by loading a web page in a separate Renderer process.
Rendered Process
The Renderer process spawns a BrowserWindow
which renders the web content; this includes all user interfaces and app functionality. The entry point for the Renderer process is an HTML file and should be written with the same tools, frameworks, and paradigms that would be used for a typical, or "modern-day", web application.
By default, a Renderer process does not have access to run Node.js built-ins or installed packages, nor does it have full operating system access, like the Main process does. If this access is enabled for the Renderer process, critical security issues are introduced.
Preload
As previously mentioned, the Renderer process, which is to say the user interfaces, does not interact with Node.js and Electron's native desktop functionality, these features are only accessible from the Main process.
A preload script contains code that executes in a Renderer process before its web content begins loading and is attached to the Main process in the BrowserWindow
constructor's webPreferences option.
The preload script runs within the renderer context, but is granted more privileges by having access to Node.js APIs through exposing arbitrary APIs in the global window that the web contents can then consume.
Simply put, the preload script shares a global Window interface with the Renderer process, but at the same time can access Node.js APIs in the Main process which provides context isolation.
Context Isolation
Context Isolation is implemented through the use of a contextBridge
module which exposes inter-process communication (IPC) to securely isolate privileged APIs from the rendered process loaded by the webContents
in the Main Process.
Inter-Process Communication
Inter-process communication (IPC) is used to perform many common tasks such as calling a native API from the UI or triggering changes in the web contents from native menus.
This communication occurs by passing messages through developer defined "channels" with the ipcMain
and ipcRenderer
modules.
This starting point application demonstrates the three different IPC patterns. Each of these patterns are implemented as Vue components.
Renderer to Main (one-way)
In this pattern, the preload script exposes an ipcRenderer.send
API through a function call to the renderer, which sends a message that is then received by the ipcMain.on
API in the Main process.
Renderer
Takes a form input or something similar in a method running in the renderer process and updates the window title in the main process.
Preload
The contextBridge
exposes the following API for the renderer to send on the set-title
channel.
Main
The Main process will call the setTitle
function when an event is received on the set-title
channel.
Renderer to Main (two-way)
Similar to the Renderer to Main (one-way) pattern, this pattern will wait for a result from the Main process by using an ipcRenderer.invoke
API, paired with ipcMain.handle
.
Renderer
A ping
button in the renderer process, when clicked, will fire off an event which will receive a pong
from the main process.
Preload
The contextBridge
exposes the following API for the renderer to invoke the ping
channel.
Main
The Main process will call the pong
function by handling the ping
channel.
Main to Renderer
The Main process can send a message to the Renderer process via its WebContents
instance. This WebContents
instance contains a WebContents.send()
method that can be used to send an event on a defined channel using the ipcRenderer.on()
API in the preload script.
Renderer
Open a file explorer dialog using the menu button to read the file's name and send it to the Renderer process to be displayed.
Optional: The renderer will respond back using a callback function.
Preload
The contextBridge
exposes the following API for the renderer to wait on the send-filename
channel.
Main
The Main process requires you to add application menu items with a click function call for the selectFile
function. This function sends the filename to the renderer on the send-filename
channel. The Main process will also wait for an event on the file-displayed
channel which the renderer will emit to using the callback.
If you enjoyed the post, please consider subscribing so that you can receive future content in your inbox :)
Psssst, worried about sharing your email address and would rather want to hide it? Consider using a service I created to help with that: mailphantom.io
Also, if you have any questions, comments, or suggestions please feel free to Contact Me.