Build to both electron.js and browser targets using webpack

As we all know, tooling and infrastructure can lead to a lot of sunk time. When a codebase is mostly the same between various platform builds of an application, you are probably best off with a unified approach.

Think it sounds crazy? Other engineers agree with me! Here’s a quote from an article on the Facebook Enginering Blog from September 2015:

One lesson we learned was that working across separate iOS and Android code repositories is difficult, even with lots of tools and automation. When we were building the app, Facebook used this model, and all of our build automation and developer processes were set up around it. However, it doesn’t work well for a product that, for the most part, has a single shared JavaScript codebase. Fortunately, Facebook is moving to a unified repository for both platforms — only one copy of common JavaScript code will be necessary, and syncs will be a thing of the past.

Motivation

When I dove deep into my first webpack-powered electron + react project, I saw how close I was to a browser bundle. An almost identical web client was an MVP requirement, so I chose to modify the scaffold I started with to make this happen readily.

In this article I will show you the minimal config necessary to make this happen. There is a github repo of the final result that you can check out as you follow along.

(This doesn’t include hot reloading, tests, anything really beyond what is needed to demonstrate this feature of webpack. If you want to start with a more comprehensive scaffold, there are plenty if you search github for ‘webpack electron’ and add the equivalent customizations mentioned below.

Personally, I found chentsulin’s electron-react-boilerplate to be an excellent starting point for my electron/web + react project, and was able to modify it to do all of this quite readily.)

Project Setup

Lets start with our minimum requirements:

$: npm install -g webpack webpack-dev-server electron-prebuilt // if you haven't already
$: npm install --save-dev minimist webpack-target-electron-renderer

You can also install the electron-prebuilt globally

Basic Electron Webpack config

This is a very very basic config for electron. It only has one build target and doesn’t have any special logic yet.

Here we use the webpack-target-electron-renderer project for a more full-featured electron target than the ‘atom’ option. It expects us to pass the entire options object as a parameter.

Then, we set up a project scaffold as such:

src/index-electron.html
src/index.js // blank for now
index.js // when we call electron . this will be the main process
package.json

Our /index.js file looks like this:

All we do here is create a simple electron window that opens the specified index-electron.html file:

<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8">
<title>Hello Electron React!</title>
</head>
<body>
<script src=”../build/bundle.js”></script>
</body>
</html>

Lets introduce our first script to package.json:

“scripts”: {
“electron”: “webpack && electron .”,
}

Now when we run ‘npm run electron’ the application builds with webpack and runs inside an electron application

Specifying the ‘web’ target

What I love most about webpack is that it brings the best of both gulp and grunt together — highly programmatic (gulp) and very opinionated (grunt).

By specifying webpack — option value, you can override any option in your webpack.config.js file. This will make our multi-target builds a piece of cake.

“scripts”: {
“electron”: “webpack && electron .”,
“web”: “webpack -—target web && webpack-dev-server --target web”
}

Now if we run npm run web, build/bundle.js will be browserified and built using the default web target for webpack, overriding the electron target we set up earlier.

Making the webpack build multi-target aware

Now, lets get crafty with webpack and set it to build to multiple target directories. This is great for making sure we always know what kind of build output we are loading up, and that we can run builds of both targets at the same time.

Let’s update webpack.config.js

First, we introduce minimist so that we can parse the webpack cli args we pass before webpack even gets to them. This gives us the ability to set the ‘isWeb’ variable, which we can use to set a different options.output.path depending on which argument is passed.

Note: We don’t need to do anything special with options.target because the webpack — target web flag is already taking care of that behind the scenes.

src/index.html should look like this:

<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8">
<title>Hello Electron React!</title>
</head>
<body>
<script src=”../build/web/bundle.js”></script>
</body>
</html>

And src/index-electron.html should be updated accordingly, to specify the new build/electron/bundle.js script path.

Utilizing Environment Detection

Now in index.js we want to make sure the application knows which environment it is running in. In electron, we call this the ‘renderer’ process, and in the browser, well we would just call this the browser runtime.

In order to call native electron functionality, the javascript application will need to know whether that’s even possible! The webpack — target web build won’t even complete if we call require(‘ipc’) or other built-in electron modules, for example.

Luckily, because we are in node.js-land, there is a tiny module for that!

npm install --save is-electron-renderer

Then we will make index.js look like this:

console.log(
‘is this running in electron.js?: ‘,
require(‘is-electron-renderer’)
);

is-electron-renderer will detect whether the global process reflects the appropriate information to determine whether this is the renderer process. This will also work in the main process (./index.js above).

Run it!

npm run electron
Your electron developer tools should show this

Now, for the web version:

npm run web

Visit http://localhost:8080/webpack-dev-server/src in your browser, and you should see something like this:

Further Considerations

Limitations of this particular approach:

  • Singular application — though we can use the environment detection to even do conditional routes, etc, it really becomes a singular application built for multiple environments, rather than an application ‘toolkit’ of common libraries, utilities and components used to build unique applications for each target. There are many good patterns for this and this is a really fun thing to dream about.
  • Fixed web or electron targets — You couldn’t have multiple web targets and/or electron targets with this approach. I once worked with an ember + phonegap scaffold that made this possible and it added a lot of complexity (and power).

If you want to take it and run with it, webpack makes it almost effortless to get much more nuanced with multi-platform development builds.

Show your support

Clapping shows how much you appreciated Ad Harmonium’s story.