software development blog
June 20, 2023

what *is* electron?

It can be difficult and daunting to start programming for a platform without understanding its essence -- its core -- its absolute basic construction. There are lots of tutorials out there that explain what electron *does*, and how to *use* it. Instead, this text aims to open the hood of electron and explain its core workings in enough detail to provide developers just starting out with a better understanding of what electron *is*.

high level description

At the highest level of understanding, electron is an executable program that interprets and runs javascript files. (Just like Node.JS ("node") does.) However, unlike node, electron can also launch minimally adorned browser windows on the local machine that can render html and javascript to provide a graphical user interface. Those browser windows tend to just have a title bar and maybe a main menu, but mainly a blank client area where the web page masquerading as a desktop application gets rendered.

The executable program itself is called electron.exe in Windows. In MacOS the executable file is called Electron, and in Linux it's electron. After running npm install electron, the electron executable is placed in one of the following locations, next to all of its helper files:

  • in Windows: <your project>/node_modules/electron/dist/electron.exe

  • in MacOS: <your project>/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron

  • in Linux: <your project>/node_modules/electron/dist/electron

Go ahead and run that executable at the command line yourself by giving it a test .js file to interpret and run.

# open a terminal and navigate to the electron executable's folder
# (the Windows executable is shown here)
cd <yourProject>/node_modules/electron/dist

# create a test.js file in that folder containing:
console.log('Hi!');

# then run the script using your platform's electron executable:
electron.exe test.js

# you should see:
Hi!

app deployment

When you want to deploy your app, you'll need to include the electron executable, all of its helper files, and your script(s) in your distributable install executable. You'll rename the deployed electron executable to your application's name and embed/assign your app's icon to it so it looks like your application. You'll then arrange to install all of that into a destination folder on the user's machine, and create a launching icon/script/executable that runs the renamed electron app with your main entry point script as a parameter. (for example, test.js above)

going deeper

Once we've gotten a feel for electron simply being an executable that runs javascript files, we can start to think about what's inside that executable. At the highest level, what's inside the electron executable is some electron specific code and data, in addition to both node and chromium.

inside

Since electron contains both node and chromium inside of it, we need to first understand the essence--the core--the basics of both of those technologies. If you'll indulge me, before continuing, please jump down two rabbit holes by first understanding these:

1) What *is* Node?

  • Node is an executable that runs javascript files. Unlike the javascript normally found in browsers, node adds the ability for javascript to access the OS's file system and a host of other services normally forbidden to browsers.
  • Its main purpose is to be used as a web server, listening for connections, taking http requests, and returning dynamic html/javascript results.
  • Of course node can also be used for any general purpose. For example, a little app that processes a list of images could be made. It would have to be a command line tool using parameters and have a text-only UI.
  • The node executable itself contains native code for your OS, and the V8 javascript engine borrowed from the chromium souce code for parsing, interpreting and executing the javascript.

2) What *is* CEF? (the "Chromium Stuff")

  • CEF is a set of binary libraries that contain only the core elements of the chromium browser. It stands for Chromium Embedded Framework.
  • It mostly contains a rendering engine that takes html and javascript and produces the output of an html page. It also allows user input into the page for interacting with the elements.
  • It also contains the developer tools frame that contains the element inspector, javascript console and all the rest of the developer goodies found in Chrome and Chromium.
  • To use CEF by itself, you would create a native application for your OS, have it load the CEF libraries and use the CEF API to place web browser frames in one or more of your app's windows that your app can control.
  • The developers of electron have done just that: they've created a native app that contains an embedded node environment, and on top of that, it loads and interacts with the CEF library for allowing web browser frames to exist in one or more native OS windows.
  • The CEF library requires that each application using it launches separate "renderer" processes for each web browser frame active in the application. These renderer processes do the actual work of interpreting the html and javascript code. The CEF API internally launches these renderer processes and handles their lifetimes.
  • The reason the renderers are launched separately from the main process is that parsing, interpreting and executing html and javascript is dangerous to a process' health. Because of the complexity of running javascript, html renderers are extremely complex, and history shows they can be somewhat unstable. If a renderer crashes, since it is always in its own process, it won't take down the entire application--whether it be the chromium browser itself, or an application using CEF.
  • Note that the renderer processes can simply be additional copies of your app's executable in memory--just running in a different mode: a "renderer mode".

Let's continue talking about electron…

now we can really go deeper

Since electron uses the CEF (Chromium Embedded Framework), it must have a main process, and multiple renderer processes, one for each browser frame in your application. The electron executable itself contains code for both a main process and the renderer processes packed inside of it. When you ran electron.exe test.js from the first section of this text, you've only activated one part of electron: officially called the "main process" in most electron tutorials. However, a better description for the "main process" would be: the electron executable running in "main mode".

main mode

Main mode, which is the default running mode of the electron executable, establishes a minimal UI process in the OS (with no actual windows showing yet) and is used as a command center to launch and support any renderer processes you decide to have. When electron wants to render and display a web page (which represents a GUI window in your app), it must first create a native OS window with an empty client area, then launch a renderer process to provide content for that client area. A "renderer process" is what most electron tutorials call the electron executable when it's running as a renderer. What actually happens is that the "main process", launches another instance of the electron executable, but this time with special command line parameters that put the new process into "renderer mode".

renderer mode

A renderer process uses its chromium stuff to do all the complex work of parsing and executing html and javascript in order to render a view of the page contents, and allow interaction. Just remember that the renderer is a non-visual process, that is, it has no access to the gui, instead, it passes everything it renders to the main process for display. The main process is only in charge of establishing a minimal window on the screen, complete with the OS's title bar and a blank client area where the rendered content "video" will be placed. The renderer process is actually in charge of creating the "video" content for the client area of that window. See the next paragraph for the meaning of "video". Note that the main process is also in charge of the window's main menu for Windows and Linux, and the system-wide main menu in MacOS.

The visual result of the renderer process drawing an html page gets projected into the client area of the main process's native OS window like a video. When the user interacts with things in the client area (i.e. on the "video"), the keyboard and mouse input is redirected to the renderer to enable interactivity. (All the javascript for the page also runs in the renderer process.)

You can think of this behavior as being similar to using a remote desktop app on your pc. The "video" of what's on the remote desktop is piped over a network and displayed on your local screen, and any local mouse and keyboard activity is then redirected over the network back to the remote desktop to make it appear interactive.

renderer window

When I first started learning about electron, I was always annoyed that renderer processes didn't have access to their own window's title and menu -- after all, they are all living in the same window right?? Once I understood the "remote desktop/video" style relationship between the client area of a window and the rendered page, it made perfect sense: The window itself, its title, and its menus are all owned by the main process and have absolutely no connection to the "video" of the rendered page going on in the window's client area.

example app: a simple calculator

Let's take a look at some basic code. These three files make up a simple calculator electron app.

Download the example code below.

// mainEntryPoint.js contains this:
const { app, BrowserWindow } = require('electron');
const path = require('path');

app.on('ready', async () => {

  let window = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      backgroundColor: '#000',
      darkTheme: true,
      show: true
    }
  });

  window.loadURL('file://' +  path.join(__dirname, '/rendererEntryPoint.html'));
});
<!-- rendererEntryPoint.html contains this: -->
<html>
  <head>
    <script src="rendererEntryPoint.js"></script>
    <style> body { color: white; } </style>
  </head>
  <body>
    <h1>Calculator</h1>
    <div class="label">Formula</div>
    <input id="formula" legnth="50"><div id="result"></div>
    <button id="calculate">calculate</button>
  </body>
<html>
// rendererEntryPoint.js contains this:
document.addEventListener('DOMContentLoaded', () => {
  document.querySelector('#calculate').addEventListener('click', ()=>{
    let result = eval(document.querySelector('#formula').value);
    document.querySelector('#result').innerText = '= ' + result;
  });
});

unzip the example code to a folder, cd to that folder, then start the calculator electron application like so:

  • in Windows: <path to electron's dist>/electron.exe mainEntryPoint.js

  • in MacOS: <path to electron's dist>/Electron mainEntryPoint.js

  • in Linux: <path to electron's dist>/electron mainEntryPoint.js

As mentioned earlier, this will launch the electron process in main mode, and then interpret and run mainEntryPoint.js. The first thing mainEntryPoint.js does is call new BrowserWindow(). That call will do two important things:

1) create a native window in the OS belonging to your main process. (In Windows for example, this establishes a new window using the Win32 API and gets a window handle (HWND) which electron keeps track of internally.)

2) and most importantly, launch another instance of electron, but this time in renderer mode, for rendering your UI. It does this by passing special command line parameters to your new electron process. Then some interprocess communication methods are enabled internally to relay the messages back and forth between the main process and the renderer process.

Then the call to window.loadURL(... tells your electron process running in renderer mode to open rendererEntryPoint.html via an inter process communication message internally. The renderer process then begins to parse and render the html, and therefore interpret and run the javascript inside of rendererEntryPoint.js, because it's referenced as an external script by rendererEntryPoint.html.

The native window that was created and is owned by your main process has a client area. That client area will display the results of your renderer process' output. You can think of this as seeing a "video" of what your renderer created. As you move the mouse over the client area of the window, and click on controls therein, the movements and clicks get sent from the window's client area (owned by your electron.exe running in main mode), to your electron.exe running in renderer mode, to allow the events attached to the DOM to run and for the app to actually do something useful for the user.

The bottom line is that when you are creating an electron app with one UI window, you actually have to create two separate applications, each running in its own process: one will use an instance of electron running in main mode, the other will use an instance of electron running in renderer mode. If you want to add an additional UI window, then you have to create a third renderer application running in its own process, and so on. All of these separate processes will communicate with each other via interprocess communication methods and together they constitute your overall "application".

a closer look at the processes

If you take a look at the process list for your running example app, you can see that there are actually 3 or 4 instances of the electron executable running. To tell the main process apart from the renderer process, and other helper processes, enable the feature in your OS's process list that shows the command line with all of the parameters that launched each process.

process list

main/renderer "video" relationship: prove it

By the way, if you want to see proof of the "video" relationship between the main process and the renderer, you can purposely kill the instance of electron running in renderer mode for the example app and see what happens. Open your operating system's list of running processes. Then enable the feature that shows the command line that launched each process. Find and end the instance of the renderer process denoted by the "--type=renderer" parameter in the command line that launched it.

kill renderer

try it in chrome!

You can try the same thing with Chrome. Find the renderer process for a Chrome instance by looking at the command lines in your list of running processes and ending that process. (The actual renderer process responsible for the "video" in the Chrome window is a little harder to find because Chrome launches a separate renderer for every extension you use, in addition to a number of helper processes.)

aw, snap

security

Earlier in the document there was mention of being able to optionally activate and deactivate the "node stuff" in an instance of electron running in renderer mode. If there is a possibility of your renderer ever browsing the public internet, you must deactivate the "node stuff" in that renderer.

For example, if you've made a photo management app that allows the user to browse the web and grab photos from any website to add to the local photo library, the renderer window that allows web browsing must not allow node to be active. It should also turn on context isolation as well. If the user visits a nefarious website that is able to detect that node is active within that browser, it can exploit any of the powerful low level OS functions that node offers and have full read/write access to all of the data on the user's hard drive--and even worse--the bad site can install and execute any malware it wants into that user's OS.

If you are positive that the code inside the renderer will only ever be code that you (the developer of the app) has allowed there, you may enable node in the renderer. For example, it would be ok if you and only you, (the developer) point the browser to a web page on the public internet that you control. In that case, even though the content is coming from the public internet, you are in control, so it would be ok to allow the node stuff to be active. Obviously, you'll need to be careful and make sure that the user has no way to jump out of your page and into the public internet by accident.

/* if your browser window (renderer) could possibly ever be used to browse the public internet, you must turn *off* node stuff, and turn *on* context isolation. */

let window = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false,   // <-- turn off "node stuff" to browse internet
      contextIsolation: true,   // <-- turn on context isolation to browse internet
      backgroundColor: '#000',
      darkTheme: true,
      show: true
    }
  });

final notes

An interesting aspect of electron is that since both the "node stuff" and "chromium stuff" use V8, Google's Javascript interpreter, there is only one copy of it in the electron executable that both the "node stuff" and "electron stuff" share. See: How many copies of V8?

other posts
a sqlite date trick
personal internet security
what *is* electron?
firewall ip filtering overview
javascript binary data
practical http caching
modern css concepts
my favorite vscode extensions
try import helper for vscode
node coding handbook
the case for electron.js