Developing desktop applications with Electron and React

The source code for this article is located on GitHub.

There are so many existing web technologies that allow developers to create web applications efficiently and as quickly as possible. Today, many of the web applications we use are built with libraries such as React, AngularJS, Ember.js, and the list goes on.

Building UI for web is almost effortless; you can have something beautifully going with some CSS and JavaScript here and there.

On the contrary, developing a desktop application can get quite involved. There are entire applications and libraries dedicated to building desktop GUIs including Qt, Gtk, Java’s Swing and AWT, and so on. These are great tools, but often codebases become cumbersome to deal with, and creating a fluid design takes a lot of work.

Why do GUI applications need to be so complicated to develop? What if we could somehow use our existing web technologies to create desktop apps just as easily as web apps? Enter Electron.

Electron is a relatively new library, developed by GitHub for Atom.io, that enables developers to leverage the power of web technologies and use them to develop desktop applications. How does it work? Well, Electron basically exposes a webkit environment through Chromium, the browser engine behind Google Chrome, and let you write code to render your application.

In this tutorial, I will be going over what you need and how to write a functional Electron app with React. Specifically, we will be:

  • Setting up Electron and React
  • Writing code (JavaScript and LESS) to render your app
  • Using webpack to transpile ES6 code and for hot-reload
  • Packaging your Electron application

To begin, first make sure you have relatively recent versions of Node.js and npm installed (I actually use io.js to build my applications). Create a new directory, and start everything up by creating a new package.json:

{
"name": "my-electron-app",
"version": "0.1.0",
"main": "main.js",
"devDependencies": {
"babel": "^5.6.10",
"babel-core": "^5.6.11",
"babel-loader": "^5.2.2",
"css-loader": "^0.15.1",
"electron-packager": "^4.1.3",
"electron-rebuild": "^0.2.2",
"less": "^2.5.1",
"less-loader": "^2.2.0",
"node-libs-browser": "^0.5.2",
"style-loader": "^0.12.3",
"webpack": "^1.9.12",
"webpack-dev-server": "^1.9.0"
},
"dependencies": {
"electron-prebuilt": "^0.28.3",
"react": "^0.13.3"
}
}

These are the latest versions of node modules that I’ve been using, feel free to update to your needs, but make sure you update any breaking API changes.

webpack is a node module that compiles Node.js code into browser-executable JavaScript. webpack-dev-server allows a browser to reload whenever you change your code.

The babel-* modules will transpile ES6 code and JSX into ES5. This is a really cool feature as it can all be automated in real-time.

The *-loader modules basically tell webpack how to compile A into B so the browser can read it. In our case, we’re using LESS, and webpack will translate it all into CSS.

React.js is a great frontend JavaScript framework that simplifies action and data management and lets you focus more on building your UI. Because Electron essentially runs a browser environment, we can use any browser-friendly node modules to render our application!

Finally, electron-prebuilt is a library that has everything needed to run your Electron application in development. Because Electron runs on io.js, another package electron-rebuild is needed to rebuild modules downloaded from npm on io.js. electron-packager is a great utility module that makes packaging cross-platform applications insanely easy.

Don’t forget to run:

npm install

In Electron apps, there’s a main JavaScript source file where everything starts, specifically the main process. This handles everything like window creation and setting up listeners and actions to handle from renderer processes. In our case, create a main.js in the root directory:

var app = require('app');
var BrowserWindow = require('browser-window');
require('crash-reporter').start();
app.on('window-all-closed', function() {
if (process.platform != 'darwin') {
app.quit();
}
});
app.on('ready', function() {
mainWindow = new BrowserWindow({width: 1360, height: 800});
  mainWindow.loadUrl('file://' + __dirname + '/public/index.html');
  mainWindow.openDevTools();
  mainWindow.on('closed', function() {
mainWindow = null;
});
});

This is a fairly simple example of an Electron app, it’s almost exactly the same from the Electron tutorial, which is located here for those who might want to read more (and should).

Simply put, we’re creating a new application by defining its startup and shutdown lifecycle methods. The code is fairly self-explanatory, but it is highly recommended to learn of all the other things Electron is capable from the docs.

Our mainWindow is loading an HTML file to display. This is done inside a new renderer process aside from the main process to be able to efficiently handle “multiple” webpages, or windows.

Create a new directory public/ inside the root project directory and create an index.html:

<!DOCTYPE html>
<html>
<head>
<title>My Electron-React app</title>
</head>
<body>
<div id="content"></div>
</body>
<script src="http://localhost:8080/webpack-dev-server.js"></script>
<script src="http://localhost:8080/built/bundle.js"></script>
</html>

We will be hosting a webpack-dev-server on port 8080, and it provides us hot-code reload functionality whenever it sees that the code its watching changes. The bundle.js is basically where all of our React and everything else code goes.

Now we actually have to set up our webpack configuration. In the root project directory, create a webpack.config.js:

var webpack = require('webpack');
module.exports = {
entry: {
app: ['webpack/hot/dev-server', './javascripts/entry.js'],
},
output: {
path: './public/built',
filename: 'bundle.js',
publicPath: 'http://localhost:8080/built/'
},
devServer: {
contentBase: './public',
publicPath: 'http://localhost:8080/built/'
},
module: {
loaders: [
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.less$/, loader: 'style-loader!css-loader!less-loader'}
]
},
 plugins: [
new webpack.HotModuleReplacementPlugin()
]
}

Basically, we’re telling webpack it should watch for changes with webpack-dev-server, and that the start of our JavaScript code will be at javascripts/entry.js (It’ll watch for any changes in entry.js and any of the files it includes, except for those that are ignored).


So, up to now we’ve set up all the basic components to run our Electron app. What about our React code and our LESS? Let’s do that now!

Create a new directory javascripts in our root project directory, and create an entry.js:

require('../less/main.less');
'use strict';
import React from "react";
React.render(<div className="myDiv">Hello Electron!</div>, document.getElementById('content'));

This is also fairly simple code, and I don’t want to start writing too much React and not focusing on the awesome functionality we get.

As you can see, we’re actually including our LESS folder to be part of our JavaScript bundle (webpack will automatically compile it). Then we’re including React ES6-style and rendering a div JSX-style.

In the root project directory, create a new directory less/ and create a main.less inside it:

@body-bg: #000;
@myDiv-fg: #f00;
body {
background-color: @body-bg;
}
div.myDiv {
color: @myDiv-fg;
}

Of course, you have access to all the other awesome LESS features such as @import and mixins and whatnot, but I’m not going to go over them there.

Because we’re including our LESS code for webpack to process as well, whenever we change any LESS, our app inside Electron will also reload!

So, instead of running all the commands we need by hand, let’s create some npm scripts to do it for us. Include the following in your package.json:

"scripts": {
"start": "./node_modules/electron-prebuilt/dist/Electron.app/Contents/MacOS/Electron .",
"watch": "./node_modules/.bin/webpack-dev-server",
"electron-rebuild": "./node_modules/.bin/electron-rebuild"
}

For the start command, each operating system has its own location for the Electron runtime. I use OS X, but for Windows and Linux, find your Electron runtime here.

So, to get all this started, open up a terminal, navigate to your project directory, and run:

npm run-script watch

This starts our webpack-dev-server to watch and reload our code. Open up a new terminal, navigate to the project directory, and run:

npm start

And voila! You should have a window open, running React inside to display that div we wrote, along with all the styling we put in. The Chrome Dev Tools you might be familiar with are also open! Pretty cool, huh?

But, that’s not the end of it! Go back to your source code and change some of the JavaScript or LESS we wrote, and you should see the app automatically refreshes our code!


Most Electron apps will come upon the time when it needs access to the file system, or ipc or some other node module, like fs, which we totally have access to. The problem is that we’re having webpack compile our code, which is meant to be executed in a browser environment. While we do have a browser environment, we also have a Node.js environment where we can run modules Electron exposes as well as modules like fs.

The renderer process has access to modules like ipc and fs, but our webpack-compiled code does not. The important thing to know is that the renderer process also executes our JavaScript. So we need some way to tell webpack to ignore modules exposed by Electron and Node.

Fortunately, it offers an ignore plugin, so just add to the plugins array in your webpack.config.js:

new webpack.IgnorePlugin(new RegExp("^(fs|ipc)$"))

That will tell webpack to ignore trying to require any modules fs or ipc. And instead, the renderer process will be able to execute the node code.

That’s all there is for this article! I hope you’ve learned something new!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.