Web Development

How to develop apps in Electron using React

Closeup of backlit laptop keyboard

This post will cover how to set up Electron to work with React and seamlessly transition between production and development environments.

Getting Electron Setup

The first thing to do is to set up an Electron project. The fastest way to do this is to clone the Quick Start repository. This will bootstrap a basic application.

# Clone this repository
git clone https://github.com/electron/electron-quick-start
# Go into the repository
cd electron-quick-start
# Install dependencies
npm install
# Run the app
npm start

We now need to install some extra dependencies that will make the development process much easier. These will enable us to use Webpack and Babel to transpile our React code to run in Electron. Run the following command:

npm install --save-dev babel-cli webpack webpack-dev-middleware webpack-hot-middleware babel-preset-react babel-preset-es2015 babel-loader babel-core babel-polyfill express babel-preset-react-hmre

Next, let’s add some nice-to-haves for our SASS integration:

npm install --save-dev node-sass css-loader sass-loader style-loader

We now have our development dependencies downloaded and ready for use.

Setting up Webpack

Let’s setup our Webpack to run a development server and to transpile our code for production.

Create a file called webpack.config.js in the root of your project and put the following code in it:

const path = require('path');
const webpack = require('webpack');

const port = process.env.PORT || '8080';

const config = {
  context: __dirname,
  entry: [
    'babel-polyfill',
    path.resolve(__dirname, './renderer.js'),
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
  ],
  target: 'electron-renderer',
  output: {
    filename: 'renderer.bundle.js',
    path: __dirname + '/bundle',
    publicPath: `http://localhost:${port}/bundle/`,
    libraryTarget: 'commonjs2'
  },
  module: {
    loaders: [
      { test: /\.js$/,
        loader: 'babel-loader',
        include: [
          path.join(__dirname, 'src'),
          path.join(__dirname, 'renderer.js')
        ],
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react', 'react-hmre']
        }
      },
      { test: /\.scss$/, loader: 'style-loader!css-loader!sass-loader' },
      {
        test: /\.json$/,
        loader: 'json-loader'
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
  ]
};

module.exports = config;

This file allows us to serve a development version of our JavaScript via a local server and to create a bundled version of our app for production.

Create Development Server

We will be creating a standalone development server to send our requests. This could be done with some configuring and webpack-dev-server but I prefer to use the webpack-dev-middleware because I feel it gives more control. Create a file called server.js in your root directory and add the following code:

var path = require('path');
var webpack = require('webpack');
var express = require('express');
var config = require('./webpack.config');

var app = express();
var compiler = webpack(config);

const port = process.env.PORT || 8080;

app.use(require('webpack-dev-middleware')(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath,
  hot: true,
  inline: true,
}));

app.use(require('webpack-hot-middleware')(compiler));

app.get('*', function(req, res) {
  res.sendFile(path.join(__dirname, 'index.html'));
});

app.listen(port, function(err) {
  if (err) {
    return console.error(err);
  }
  console.log(`Listening at http://localhost:${port}/`);
})

Referencing Development Server

The next step is to import our application bundle from the development server when in develoment mode.

Open the index.html page and find the code:

<script>
// You can also require other files to run in this process
require('./renderer.js')
</script>

Replace this with the following code:

<script>
    // You can also require other files to run in this process
    // install babel hooks in the main process

    var bundlePath = './bundle/renderer.bundle.js';

    if(typeof process !== 'undefined' && process.env.NODE_ENV === 'DEV') {
	   const port = process.env.PORT || 8080;
      bundlePath = `http://127.0.0.1:${port}/bundle/renderer.bundle.js`;
      var bundleScriptEl = document.createElement('script');
      bundleScriptEl.src = bundlePath;
      bundleScriptEl.async = true;
      document.currentScript.parentNode.insertBefore(bundleScriptEl, document.currentScript);
    } else {
      require(bundlePath);
    }

  </script>

We are telling the app that if it is in DEV mode that it should create a script tag that references our development server. If the code is not in DEV mode, then it should import a bundled version of the JS.

I add the following script to the index.html page to clarify which mode I’m in:

<script>
  if (process && process.env.NODE_ENV === 'DEV') {
    document.title += ' (DEV MODE)';
  }
</script>

This will simply append (DEV MODE) to the title of your Electron app.

Setting Up Scripts

Having our code set up to run in either environment is badass but how do we start the dev server as well as start our electron app? That’s where a great NPM package called concurrently comes in handy. This will allow us to run multiple processes at the same time. Install it with the following command:

npm install --save-dev concurrently

Now we can use this in our scripts to start the application. Open the package.json and add the following to the scripts section:

"start": "NODE_ENV=DEV concurrently \"babel-node server.js\" \"sleep 2 && electron .\"",
"bundle": "webpack",
"watch": "NODE_ENV=DEV babel-node server.js",
"start:prod": "webpack && electron ."

AWESOME! Now we can run npm start and we should get a webpack dev server as well as our app started up.

Setting Up React in Our Project

The next step is to add React so we can begin developing our app.

Let’s install the React dependencies so we can actually render our JSX.

npm install --save react react-dom

The next step is to create a place to render the app. Open index.html and add the following element inside the <body> tag:

<div id="app">
  Loading...
</div>

Now we need to create our React renderer to fill this area with our app. Edit the file renderer.js and add the following code:

// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.

const React = require('react');
const ReactDOM = require('react-dom');

import App from './src/app';

ReactDOM.render(<App />, document.getElementById('app'));

Lastly, create a file called src/app.js and fill it with the following code:

import React from 'react';

export default class App extends React.Component {
  render() {
    return (<div>
      Hello World!
      <a href="https://willowtreeapps.com/careers">Come work with me.</a>
    </div>);
  }
}

Happy App’ing!


1200x628-banners-Single-Page-Web-App

Single Page Web Apps: Why building a web app means less… and more than it used to

The number of modern web technologies available to software engineers is mind boggling....

Read the article