Migrate existing Web React App to Desktop App with Electron

Rostyslav Moroziuk
8 min readApr 13, 2021

What is Electron?

Electron is a framework for building desktop apps with web technologies. We can build cross-platform applications and run them on all platforms (macOS, Ubuntu, Linux, or Windows) just by using JavaScript. Electron is being adopted by big organizations such as Microsoft, Facebook, Twitch, so we can be sure, that this framework can cover all our needs in desktop apps development. Thanks to Electron, you can focus on your application instead of the technologies that you need to use.

Why did we decide to integrate Electron into our existing React app?

We wanted to get a desktop application without any changes to our existing codebase for the web application. We still could use HTML5 Notifications API to send notifications from the Renderer process. Also, we could proceed using our existing shortcuts listeners and basically, still considered our application to be a web application.

In this article, we will go through a small tutorial on how to integrate Electron into React app.

Let’s code!

Let’s create a basic and very common React application with react-router-dom and Typescript.

You can skip steps to create our basic app and fetch it from here: https://github.com/RostikMoroziuk/react-electron

Run the below command to create a React app:

npx create-react-app <app_name> --template typescript

Please, notice, that we need to install dev dependencies in devDependencies section in our package.json file, otherwise our dependencies will be included in the production build and that will increase the bundle size. Also, it’s strongly recommended to use yarn instead of npm to avoid some unexpected issues with building and running the Electron app.

Let’s add a router to the app:

yarn add react-router-domyarn add --dev @types/react-router-dom

We can remove created files from src folder and add new ones. Once our packages are installed, we can implement routing. We gonna add two pages: Home and About

Now we are done with creating a basic React app, so your project structure should look like this:

React Basic App

Integrate Electron and run a desktop application

We need to install dependencies to run the app with Electron. Let’s go to a folder with a project in your terminal and run the next command:

yarn add --dev electron concurrently wait-on

We’ll use packages concurrently and wait-on to run the application in development mode, locally.

Ok, once we are done with installing dependencies, we can go to coding. Create a folder electron on the root level of the project and add a file main.ts there. In this file, we create a window on a full working area:

Please, visit this link to find more BroswerWindow properties: https://www.electronjs.org/docs/api/browser-window#new-browserwindowoptions

Now we need to edit package.json to run the app with Electron, so open package.json file and add script electron:start:

"scripts": {  "start": "react-scripts start",  "build": "react-scripts build",  "test": "react-scripts test",  "eject": "react-scripts eject",  "electron:start": "concurrently \"yarn start\" \"wait-on http://localhost:3001 && electron .\""}

Also, we need to add “main” field to specify the entry point for the Electron application with the path to a file with a main process (in our case it’s electron/main.ts).:

"name": "react-electron","version": "0.1.0","private": true,"main": "electron/main.ts",

Congrats! Now we are ready to run the existing app both in a browser and with Electron. Let’s try it. Open your terminal and run yarn electron:start

Working Electron + React app

As you can see, the script yarn electron:start runs the app in a browser and once the app opens on localhost:3000 it’s also wrapped with Electron and opens on as a separate desktop application. You can see the same application in a browser and in a separate window.

What about debugging?

You can debug Electron app, as your web app, without difficulty. Electron uses Chromium under the hood, so you can assume Electron app to be like any other web app and use debugging tools that you use in the browser. You can open DevTools and access Elements, Console, Network, and other tabs, which you use every day in web programming.

DevTools in Electron

You can try to edit any component in the src folder and see that React Fast Refresh works correctly both in the browser and in Electron.

React Fast Refresh with Electron

Ok, now we knew that the app has the same behavior in a browser and in Electron. Accordingly, we still can interact with Electron app as with any other web app opened in a browser. We can use the same shortcuts, open links, etc. You can try to open Home page or About page in a new tab: hold down the Command key (Ctrl on Windows) and click on the link — the link will be opened in a new window.

If it’s not your intended behavior, (кома) then you can easily prevent that. All you need — is just to add the below code in electron/main.ts file:

mainWindow.webContents.on('new-window', (event, url) => {  event.preventDefault();  mainWindow.loadURL(url);});

Open this link to find more events for controlling a BrowserWindow https://www.electronjs.org/docs/api/web-contents#instance-events

Build for distribution

There are three tools you can use for building and packaging your apps for distribution:

Electron Builder and Electron Forge uses Electron Packager under the hood.

In my opinion, Electron Builder looks more preferable because of good documentation and high configurability. It supports building for Linux, macOS, Windows. You can easily sign your application. As a bonus, it supports Auto-Updates from the box. You can use Electron Builder to create distribution builds for different OS from the one machine. It also has Docker images to create Windows and Linux builds from any OS.

Ok, let’s take Electron Builder and try to create distribution builds. As a first step, install our building tool:

yarn add --dev electron-builder

Now, we need to specify options for distribution build. Add next code to your package.json file:

The homepage path for Electron must be specified too. Electron uses this path to know, where are our CSS and JS files.
You need to use your own appId and productName. Here we’ve added only the required fields essential to build the app. You may need to specify win or linux field instead of mac , if you don’t use macOS. You can find more possible options here https://www.electron.build/configuration/configuration

There is a small issue with building the app. We specified a homepage as ./ which is correct for Electron app, but for the web production build, we require to use / . So, let’s adjust our scripts to use different homepages for Electron app and simple web app:

"scripts": {
...
"build": "PUBLIC_URL=/ react-scripts build", "build-electron": "react-scripts build"
...
}

Also, we need to add a script to build a distribution app. Open a scripts section in your package.json file and add this command:

"electron:build": "yarn build-electron && electron-builder build --publish never"

Your package.json file should look like this:

Yep, we almost here. We have to specify the start URL for our app.
First of all, we need to add a new dependency to determine, whether the app is running in development or production mode:

yarn add electron-is

Now open main.ts file in your code editor and replace startURL . Probably, you don’t want to support DevTools in the production build, so we will remove them.

Now we’ve done with configurations for distribution build. But we still have to make one more step to our react-router . The problem is with using BrowserRouter in our web application. It doesn’t work with file-based environments which Electron is. That is why you will see a white screen instead of an application.

Electron app with BrowserRouter

So, what is a solution here? We need to use BrowserRouter for web application and HashRouter for the Electron.

Let’s add a new utilsfile with a function to check, whether the app is running in Electron environment:

We also need to update our index.tsx file to use the correct router history depending on environment:

Ok, now we can run electron:build script:

yarn electron:build

This script will generate a dist folder with an executable file of our app. It also will create a DMG file for macOS:

Electron distribution folder

As I’ve mentioned previously, you can build distribution files for Windows and Linux from your Mac machine. It’s possible if you don’t use any platform-related modules (In other words, it’s always possible if you just wrap your existing web app with Electron). To build the app for different platforms we need to add a new script to our package.json file

"electron:build:all": "yarn build-electron && electron-builder build --publish never -mwl"

This script will generate executable files for macOS, Windows, Linux:

As you can see, electron-builder created setup files for different OS. Let’s try to run the setup file on Windows:

Windows installer
React Electron App on Windows OS

The app, built on macOS, is successfully installed and launched on a Windows machine. Can it be easier? I think no, electron-builder made all work instead of a developer with just a few simple configurations.

Code signing

It’s possible not to sign the application but it’s not a recommended practice. macOS will block this app and will require a lot of additional steps for its installation. Windows will require one more additional confirmation step. Also, there is no possibility to distribute the app without signing through App Store and Windows Store.

To sign the macOS application you need to follow the next steps:

  1. Enroll in the Apple Developer Program https://developer.apple.com/programs/enroll/
  2. Generate two certificates: Developer ID Application and Developer ID Installer
  3. Launch Keychain, and go to ‘My Certificates’. Find these certificates and highlight all of them: Cmd+click, right click, and export as p12.
  4. Include this p12 file to build
  5. Add environment variables to build machine https://www.electron.build/code-signing

Conclusion

So, now you have done it. Congrats!!! We’ve successfully integrated Electron into our React app.

You can find a code for this tutorial here: https://github.com/RostikMoroziuk/react-electron/tree/electron

--

--