The short answer? use the IPC Renderer to create a “pub/sub” event channel between the “main” and “renderer”.
This seems simple enough (if you know Electron buzz words), but there’s not a lot of great examples out there that explicitly show how to do this kind of stuff unless you dig.
I had to go through the Electron Discord to find a secret gist that finally had a proper example that worked for me.
ℹ️ I’ll be using electron-react-boilerplate throughout this article as the basis for any code. You can clone this project and follow along - all files I reference will be relative to that project.
Also note - I tried using Electron Forge and it didn’t work out of the box - requiring a lot of configuration to get it on par with electron-react-boilerplate. As someone who has built Electron apps before, I thought I knew how to use NodeJS.
I literally built an app that the user can input CLI commands and run them (using the exec method in the child_process module).
You can see in my project, I use NodeJS directly inside my React component. Normally this wouldn’t be possible - even in NextJS-land you’re forced to use special methods to fetch data from the server-side.
I figured Electron was different from frameworks like NextJS.
When I cloned the latest version of electron-react-boilerplate, I tried doing this again only to get an error about child_process being missing.
5 Reasons to use Nodejs with React for Web Development
Electron has 2 main processes: Main and Renderer. A “main” process that runs “server-side” - on the NodeJS platform. This process is responsible for the “backend” of the app, such as rendering the actual app window and piping the HTML inside — or speaking to native platform APIs (like making the actually close using Electron’s app.quit()).
Here we can use dependencies such as NodeJS APIs and Electron APIs, as well as any library that requires it to be server-side (like a SQLite adapter to read from a DB — const sqlite = require('sqlite')).
A “renderer” process runs the “frontend” of your app. This includes an HTML page to render, as well as any CSS or JS required inside of it.
We can also use libraries like React or Angular, since they’re also JS and render in an HTML page.
Here we can use any frontend dependencies that we install in our package.json, like Yup for validating form input (e.g.
import yup from 'yup). Both of these processes are often bundles separately, usually through a library like Webpack or Parcel.
The main.js file will run first, then run the renderer.js. Understanding the distinction between these two will help understand how to create a secure Electron app, similar to working with apps on the web, to avoid exploits like XSS.
So if you want to do something like query a DB, or open the native file system dialog — how does React run these commands on demand?
The answer is to use IPC in Electron.
This is a system that uses pub/sub events to transmit data to and from the “main” (or backend) to the “renderer” (or frontend).
Inside the main process (or main.js), we add a handle() method from IPC Main to “listen” for events (in this case blender:version):.
Also inside the main process, we pass in a preload.js script to load alongside the renderer process.
For example, we’d call window.electron.blenderVersion(). When we call that method, the IPC Renderer inside the preload script runs (or invoke()) the function we put in the main process. So the frontend uses the “API” you define in preload.js - and the backend uses the event names in preload.js (aka blender:version) to run the right function for the event.