Upgrading Electron from 8.2.0 to 24.0.0

I've migrated my Electron app from Webpack to Vite. This migration solved the problems that both electron-webpack and electron-webpack-quick-start caused. The next is to upgrade the version of Electron. Because the boilerplate electron-webpack-quick-start limits the version of Electron to 8.2.0, I can upgrade Electron from 8.2.0 to 24.0.0.

A challenge: contextIsolation defaults to true

To upgrade Electron significantly, I have to take breaking changes into consideration. Electron 12.0 requires the hardest breaking changes: Default Changed: contextIsolation defaults to true. This is one of the biggest breaking changes in Electron. contextIsolation: true is important for security. But, most of the old Electron apps don't support this breaking change.

The reason is that contextIsolation: true requires a lot of modifications. contextIsolation: true also forces us to set nodeIntegration: false. This means Node.js modules don't work in the renderer process. And contextIsolation: true disables the @electron/remote module which allows the renderer process to access Electron APIs in the main process.

Even Atom, which was born as a killer app of Electron, didn't support contextIsolation: true. Now Pulsar takes over the source code of Atom, but does not yet support contextIsolation: true.

So, it is a big challenge.

Understanding Inter-process communication of Electron

After reading the official document of Electron, there were four key points to understand inter-process communication.

1. Relationships among the processes

Electron has three types of process. Main, preload, and renderer. Inter-process communication means the main process communicates with the renderer process through the preload process. The main is separate from the renderer. The preload is attached to the main as a script.

Relationships among the processes in Electron

2. Global objects are isolated from the renderer process

Context isolation is a powerful feature for security. It exposes only the APIs in the preload script. Even global objects like Array.prototype.push or JSON.parse are isolated from the renderer process and cannot be modified from the renderer process.

3. Enable Context Isolation

In practice, that means that global objects like Array.prototype.push or JSON.parse cannot be modified by scripts running in the renderer process.

3. Two-way IPC needs async/await

Two-way IPC needs async/await. IPC spends a bit more time than direct access. Without async/await, the original call just gets a Promise.

3. Build the renderer process UI

const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => {
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})

4. contextBridge exposes Node APIs

Node APIs are not exceptions in context isolation. The contextBridge can give our renderer access to Node APIs.

const { contextBridge } = require('electron')
const crypto = require('crypto')
contextBridge.exposeInMainWorld('nodeCrypto', {
  sha256sum (data) {
    const hash = crypto.createHash('sha256')
    hash.update(data)
    return hash.digest('hex')
  }
})

Implementing Inter-process communication of Electron

I used the vite-electron-builder boilerplate when migrating my Electron app. Because the boilerplate already supported Electron 24.0.0, I added and modified the code for IPC together with debugging. Two issues I got stuck on happened below.

  • ERROR: The symbol "contextBridge" has already been declared
  • Latency of IPC

ERROR: The symbol "contextBridge" has already been declared

After adding the code for IPC, I got the error message 'The symbol "contextBridge" has already been declared.'

[vite:esbuild-transpile] Transform failed with 1 error:
index.cjs:13:7: ERROR: The symbol "contextBridge" has already been declared

The symbol "contextBridge" has already been declared
11 |      getCode: (path) => ipcRenderer.invoke('get-code', path)
12 |  });
13 |  const {contextBridge} = require('electron');
   |         ^
14 |

The cause was the module unplugin-auto-expose which was imported in vite.config.js of vite-electron-builder boilerplate . The module was a kind of DSL for IPC and required contextBridge.

Plugins for automatic exposeInMainWorld. Easily export your exposed api from preload to renderer.

I removed this module to solve the conflict.

Latency of IPC

The latency happened by adding the code for IPC. The direct access to Node API from the renderer process was faster than IPC. The access to Electron APIs through @electron/remote was also faster than IPC. So I had to change the flow including IPC because of the latency. Additionally, I also reduced the API calls via IPC.

Finally, implementing IPC was done. Both contextIsolation: true and nodeIntegration: false didn't cause any errors. Read it easy now supports Electron 24.0.0!