Electron + React + Python (Part 3) — Boilerplate 1/3

Pipe Runner
Project Heuristics
Published in
8 min readOct 8, 2019

We’ll, this took me a while to complete as I was a bit caught up with life. Anyway, let’s get started…

You could always just clone my repository from GitHub and skip straight to Test Drive 2 of this article…

But I would like you to know how the boilerplate was prepared. To be honest, I referred 2 articles to begin with, did a lot of experimentation and there you go, a fully functional boilerplate. If you would like to experience the full walk through, then feel free to read them:

Now my turn…

Step 1: Create a project using CRA (Create-React-App)

Firstly, if you don’t have CRA on your machine, start by installing it first. As you know, this npm module needs to be installed globally to be used as a command line tool.

npm install -g create-react-app

The -g flag installed the module globally.

When done, create a new project using the CRA module.

create-react-app new_electron_project

Here use whatever name you would like your project to have in place of new_electron_project.

Step 2: Add Electron as development dependency

Now you would need electron as a development dependency in your project. Why development dependency, you might ask? The reason being that electron-builder (our packaging tool) will take care of adding all electron related stuff into our final build, hence we don’t need to put electron in it by ourselves.

npm install --save-dev electron

This will take some time, so make yourself a cup of coffee if you would like. Or a better use of time would be to read about Electron IPC modules because in the upcoming sections of the article, we will rely on it heavily.

Step 3: Add a few more development dependencies

Before we can move further with cooking up our boiler plate, we will need 2 more development dependencies.

npm install --save-dev wait-on
npm install --save-dev concurrently

wait-on

This npm module is used to wait for a process to finish execution. This allows us to wait for a command to complete so that we can execute another command that basically relies on the results of the previous command. The best part about this module is that we can even wait for a URL to get launched and then execute a command that depends on that URL. The use case will become clear when we modify the command scripts.

concurrently

This npm module is just used to execute multiple command simultaneously. This will also play a key role while modifying our command scripts.

Alright, we are set… Let’s take a look at the folder structure that I have planned out. Keep in mind that this folder structure is important and if you misplace things, it is highly likely things will not turn out as expected.

.
├── background_tasks
├── node_modules
├── package.json
├── package-lock.json
├── public
├── README.md
├── scripts
├── src
└── utils

From the folder structure I have created, you may have noticed three new directories in the boilerplate — background_tasks, scripts and utils. They are not really important right now, but in the coming articles I’ll show a working demo where we will make use of this project structure.

You may be wondering why I have created these directories — all of this will make sense when I explain the production build pipeline, which will be covered in the following article. So for now, you may wanna ignore the details regarding project structure.

So it is totally up to you whether or not you would like to create these directories right now.

Moving on…

Step 4: Modifying package.json

Look for the key “scripts” in package.json. It will look something like this…


... some code here
"scripts": {
"start": "react-scripts start",
... some code here "eject": "react-scripts eject"
},

... some code here

We will start modifying things here. I like keeping things very neat, so let’s make sure the commands that we add make total sense.

During development, there are 2 things that will be happening:

  1. The react development server will be started and the pages will be hosted on http://localhost:3000/
  2. The electron shell will be launched which during development, will render whatever is being served on http://localhost:3000/ (Listening on a particular URL will need further modification; for now we are just focused on opening the electron shell)

I hope this makes sense so far. So let us first add two commands, that serve the purpose individually.

"react-start": "BROWSER=NONE react-scripts start",
"electron-start": "DEV=1 electron .",

Get rid of “start” and replace the above two commands in its place. DEV here is a nodejs environment variable that we are using to keep track of whether or not we are in the development environment. You may need some further reading to understand that.

Other than the 2 keys in scripts, we also need to add 1 more key as a sibling of “scripts”.

"scripts": {
...some code here
},
"main": "public/electron.js",

The “main” key tells the electron app about its entry point.

An entry point is basically a file when is executed before everything else.

If you have ever worked with C++, it is more of a file that contains the main() function. But our entry point file is missing, so our next step would be to add one.

Note: Before you move to the next section, this is how your package.json should look like by now:

package.json

Step 5: Adding an entry point for the app

Every app needs to have an entry point, be it a React app, be it a NodeJS server, or be it a C++ application; an electron app is no exception.

We start by adding a new file named electron.js under the public directory of our app structure.

You might ask why in public, why not in src. There is a reason to that and I would explain this when we talk about production build. For now, trust me and follow the steps.

Now let’s add the following code in electron.js:

electron.js

This is nothing unusual and is in fact taken from the electron code provided by github. A few things to point out here are:

const { app } = electron;
const { BrowserWindow } = electron;

The app instance is used to setup callbacks on different states of the app; in our case we setup callbacks on “closed”, “activate”, “ready” & “window-all-closed”. The documentation of electron covers each and every one of them in detail and the code we are using is pretty easy to understand.

Our point of interest is “ready”; which basically tells electron that our app resources are ready, we are all set to show the UI. The then calls the createWindow function as a callback. Let’s take a look at this function.

function createWindow() {
const startUrl = process.env.DEV
? 'http://localhost:3000'
: url.format({ pathname: path.join(__dirname, '/../build/index.html'),
protocol: 'file:',
slashes: true,});
mainWindow = new BrowserWindow();
mainWindow.loadURL(startUrl);
process.env.DEV && mainWindow.webContents.openDevTools();

mainWindow.on('closed', function() {
mainWindow = null;
});
}

Here the DEV environment variable plays a key role as it tells what should be taken up as the startUrl for the new window we are about to create. Now if you recall, during development we start the electron shell by a command, along with which we passed DEV=1. Now you can access this variable using process.env.DEV anywhere in your app and tell whether it is running in production or development. Pretty neat, huh?

If it is indeed in development then we just set startUrl to point to the URL that is being served by the React development server, else we point it to index.html in the build directory (more on this when we talk about production build).

After that we make use of the BrowserWindow class, create a new instance and assign it to mainWindow which happens to be created as a global variable to protect it from being garbage collected.

If you need more details on what each line is doing, give the original source a read:

And with that we are now ready to launch our app…

Test Drive 1

Now if you really want to see them in action open your terminal and cd into the project directory and run the following:

npm run react-start

Note: Wait for the react server to start

Then…

npm run electron-start
Two separate terminal sessions to run the two commands
Your output should look like this…

Improving our command scripts

So our two commands start the app nice and smooth, but it is really tedious to do this every time we have to restart the app. Also the fact that we need to wait for the webpack server to deploy http://localhost:3000/ before we can start our electron shell is an added headache. So what do we do about it? — Automation.

"start": "concurrently 'npm run react-start' 'wait-on http://localhost:3000/ && npm run electron-start'",

Note: Before you move to the next section, this is how your package.json should look like by now:

updated package.json

Test Drive 2

Now all you need to do is run a single command.

npm start

It will take care of:

  • deploying the react development server
  • waiting for it to server contents on http://localhost:3000/
  • then launching the electron shell in one smooth motion.
A single terminal instance this time…

All thanks to concurrently and wait-on modules that we added in the beginning of this article. This is also a great example of how 3rd party libraries can help in improving development cycle.

What now?

In the next article we will add python to the mix and I’ll show you how to make it work seamlessly with the react interface. Right now you can play around with the code and develop a react interface like any other web app. It will work without a problem in the electron shell.

If you missed the previous article, do give it a read.

Next up:

--

--

Pipe Runner
Project Heuristics

Software Engineer at Postman | “Coder by profession, Artist by passion” | Stopped writing on Medium and moved to https://piperunner.in/blog