Chapter 4. Binaries, Installers, and Updates
During development, an Electron application behaves just like a Node process. The Electron runtime, launched by executing Electron.exe
or Electron.app
, loads the entry point JavaScript file and only then starts behaving like “your” application. However, once you ship your application to users, the name Electron should never show up in any Windows task manager or macOS activity monitor. You will likely also want to change the icon and create a single installer file, ready to be downloaded by potential customers.
Shipping an Electron application therefore consists of two steps. First, the application needs to be packaged. In this process, a binary with the app’s name and icon is created for each platform, bundling up the whole application, including all JavaScript files and dependencies. Then, installers need to be created. In the spirit of Node.js development, the solution for both tasks comes in the form of npm modules. Traditionally, developers would use bite-sized modules to build custom build pipelines, combining modules such as electron-packager, electron-winstaller, or electron-installer-dmg into artisanal scripts. As interest in Electron grew and support for more distribution methods was added, creating a build pipeline from scratch became an increasingly daunting task. To solve that problem, the community has started work on a CLI tool called electron-forge. Its ability to support developers with automatic scaffolding, rebuilding of native modules, packaging, and creation of installers is amazing and has this guide’s recommendation.
On electron-builder
Readers with Electron experience may wonder why electron-builder is not being discussed here. While offering an impressive one-stop solution, electron-builder developer Vladimir Krivosheev did so at the cost of reimplementing all Electron dependencies instead of using those provided by the Electron maintainers (like, for instance, electron/windows-installer). This means that improvements and bug fixes introduced by the Electron developers in these auxiliary packages are not reflected in electron-builder. For that reason, this guide recommends electron-forge instead.
To install electron-forge, run npm i -g electron-forge
. Once installed, it can either scaffold a new project with the electron-forge init
command or import an exiting project with the electron-forge import
command. If you decide to scaffold a new project, it offers multiple templates—one for each major frontend framework. Popular ones are React, Angular 2, and Vue, but by default it will use a plain vanilla JavaScript template that does not include any additional dependencies.
Troubles with electron-forge
electron-forge is a powerful tool that is pushing both Node.js and npm to its limits and is not always compatible with the latest version of either. Should you run into any trouble, consult the readme in the GitHub repository as well as the list of known issues for help.
Packaging the Application
To package an Electron application, binaries need to be created for three platforms: Windows, macOS, and Linux. On Windows and Linux, you might decide to offer 64-bit binaries in addition to 32-bit ones. As a rule of thumb, you should always try to package an application on the target platform—though there are many reports of successfully building Windows binaries using Wine on a Mac, installing virtual machines for each target platform is usually easier, quicker, and guaranteed to actually result in working binaries.
For Windows, Microsoft offers free virtual machines for VirtualBox, Parallels, VMWare, and Hyper-V. If you do not have a preference, consider that VirtualBox is freeware and cross-platform. Alternatively, consider a service like AppVeyor, offering Windows-based continuous integration services in the cloud.
macOS is a bit trickier. macOS runs in a virtual machine, but Apple’s licenses usually require that an actual Apple computer is used. If you do not have an Apple machine available, the cloud is your only option. Travis CI offers free CI services for open source software and includes both macOS and Linux machines.
For Linux, solutions are plentiful. Ubuntu in a virtual machine is a safe choice, but so are the many cloud CI services offering automatic build services on Linux machines. If you want to try something new, the Linux Subsystem for Windows, which allows the installation of a command-line version of Ubuntu from the Windows Store, works too.
The actual packaging is done by a module called electron-packager, which can be either used directly from the command line or manually integrated into a custom build process. Using electron-forge, one simply has to call electron-forge package
to package the current architecture and platform.
Configuration
Even though electron-forge accepts command-line arguments, configuring all modules involved in the build process from a central location allows for easy reproducible builds. After running either the import
or init
command, your package.json file should contain a configuration skeleton (see Example 4-1)
Example 4-1. Configuration skeleton for electron-forge
"config"
:
{
"forge"
:
{
"make_targets"
:
{
"win32"
:
[
"squirrel"
],
"darwin"
:
[
"zip"
],
"linux"
:
[
"deb"
,
"rpm"
]
},
"electronPackagerConfig"
:
{},
"electronWinstallerConfig"
:
{
"name"
:
""
},
"electronInstallerDebian"
:
{},
"electronInstallerRedhat"
:
{},
"github_repository"
:
{
"owner"
:
""
,
"name"
:
""
},
"windowsStoreConfig"
:
{
"packageName"
:
""
}
}
}
The properties in the electronPackagerConfig
will be passed down to electron-packager (see Example 4-2). Its API is extensive and allows for fine-tuned control of all kinds of options, but developers should at least include the following critical metadata used by operating systems to determine the name, version, author, and copyright information of a binary.
appCopyright
(string)A human-readable copyright line used by both Windows and macOS.
icon
(string)Path to an icon file used to replace Electron’s default icon. Windows requires an
ico
file, macOS anicns
file. Provide a path without extension but have both file types at the path available.osxSign
(object)If you are in possession of an Apple Developer Certificate, electron-packager can sign the binary for you. For more information, see the documentation of electron-osx-sign.
Example 4-2. Basic configuration for electron-packager
"electronPackagerConfig"
:
{
"appCopyright"
:
"Copyright (C) 2017 O’Reilly Media, Inc."
,
"icon"
:
"static/icon"
,
"osxSign"
:
{
"identity"
:
"6RQE5FT22A"
}
}
Once configured, run npm run package
or the equivalent electron-forge package .
to package your application into a binary. On macOS, the result will be self-contained, meaning that the .app
bundle in the output folder is all that is required to run the application. On Windows, the resulting .exe
requires all files in the output folder to be present.
Code-Sign Your Applications
Both Windows and macOS include mechanisms to verify the signature of binaries, displaying warning dialogs to users if the authenticity of an application cannot be verified. This is not unique to Electron, but if this is your first foray into software development for the desktop, make sure to get an Apple Developer ID Certificate and a Microsoft Authenticode Certificate. See the Microsoft Introduction to Code Signing and the Apple Code Signing Guide for more information.
Creating Installers
Not all platforms require installers, but in the parlance of the Electron world, any way to bundle up the application is considered an installer. The list of possibilities includes the traditional Windows installer, packages for the Windows Store and Mac App Store, and Linux app packages. Multiple open source modules are available for each imaginable target, with the following supported by electron-forge:
zip
(all platforms)Creates a simple zip file containing the application and all required files.
squirrel
(Windows)Creates Squirrel.Windows installers, including both the classic
exe
installer as well as Microsoft’s modernMSI
format. Uses electron-winstaller.appx
(Windows)Creates a Windows
appx
package either for sideloading or submission to the Windows Store. Uses electron-windows-store.dmg
(macOS)Creates a dmg image. Uses electron-installer-dmg.
deb
(Linux)Creates a
deb
package for Ubuntu and other Debian-based distributions. Uses electron-installer-debian.rpm
(Linux)Creates an
rpm
package for Red Hat and other Fedora-based distributions. Uses electron-installer-redhat.flatpak
(Linux)Creates a
flatpak
package for the Flatpak distribution mechanism, which describes itself as “the future of application distribution.” Uses electron-installer-flatpak.
The templates included with electron-forge create Squirrel installers for Windows, zip
files for macOS, and deb/rpm
files for Linux by default. To configure which targets to create, use the make_targets
property inside the configuration in package.json
. Each module responsible for these targets can be configured individually—they all offer numerous options for fine-tuned control, but in practice only a few options are relevant to most developers. Once you have committed to Electron as a platform and have reason to deviate from the defaults, you can find all the configurable options in the documentation.
To create installers, run npm run make
or the equivalent electron-forge make
in a project’s directory. Creating installers will also automatically kick off packaging. Unless instructed to behave otherwise, electron-forge will create installers for the platform it is currently running on.
Enabling Automatic Updates
A quick chat with anyone who was involved in the development of earlier Internet Explorer versions reveals the tremendous importance of updates. Users are, at large, too lazy to update, and no mechanism will save you more often from yourself than Electron’s included automatic updating mechanism. It is based on the Squirrel updating framework, which combines two native implementations for Windows and macOS. Automatic updates for Linux are currently not supported. Squirrel is not Electron-specific, but Electron promotes Squirrel as the updating framework of choice.
Automatically updating an Electron application means that the application communicates with a server to determine whether a new update is available. If so, it needs to be downloaded, unpacked, and verified. The tricky part comes at the end: the application needs to replace itself with the new downloaded version. Electron offers an autoUpdater
module that reduces this complex process to a few lines of JavaScript.
Updating Is Hard
Automatic updates are complex and depend on many factors out of any developer’s control—operating system, internet connection, antivirus software, user interaction, and your update server all need to perfectly work together for an update to actually succeed. Many developers of Electron-based applications have reported that a small percentage of users will always be left behind each time an update is released. Many solutions are available, including the traditional warning dialog that prompts users to update. Whatever choice you make, be aware that Electron’s integrated updating mechanism is not perfect.
On both macOS and Windows, auto-updating requires that the application was previously installed in an update-ready way. On macOS, the currently installed version and the version to be downloaded need to be signed with the same Apple-provided developer certificate and the user needs to have sufficient write access to overwrite the application. On Windows, the update is done by the Squirrel Installer, so if the app was not installed with a Squirrel Installer, automatic updates are out of the question.
Providing an Update Server
Auto-updates are downloaded from a developer-provided server. Sadly, the two updater frameworks inside Electron’s auto updater (Squirrel.Mac and Squirrel.Windows) share nothing but the name and expect completely different endpoints. The macOS updater expects an API that returns JSON-formatted information about a possibly available update for a given version—in short, the server determines whether or not a new version should be downloaded. The Windows updater does not need an API at all; instead, it expects a so-called RELEASES
file that contains information about all available versions.
That is obviously a terrible developer experience, so the community sprung into action to bridge that gap and multiple turnkey server solutions exists today. One of the first solutions was Nuts by Gitbooks.io, a fully automated server package that uses GitHub Releases as the source for new releases. The newest and possibly easiest solution is Zeit’s Hazel, a lightweight and ready-to-deploy framework that also uses GitHub Releases. If GitHub Releases are not an option for you, consider electron-release-server. It lacks the corporate sponsorship keeping Nuts and Hazel in good shape, but offers far more flexibility.
If none of those solutions work for you, know that the Squirrel.Mac framework has recently started discussing the support of file-based updates. In the very near future, an update server framework might not be necessary at all—you can already host Squirrel.Windows updates entirely on a CDN, and a file-based updating solution for Squirrel.Mac would remove the need for any server-side logic.
Implementing the Update Logic
In Electron’s JavaScript, auto-updating is an almost trivial process. Once the auto-updater is given a feed
url, it can immediately go to work and does not require any additional configuration. The updater will emit events for each finding that it makes, but even in its most basic implementation, updates will be downloaded automatically and installed whenever the user quits the application.
The autoUpdater
module is only available in the main process, but communication with it is possible from any renderer using the remote
module. The four events emitted are checking-for-update
, update-available
, update-not-available
, and finally update-downloaded
. Once the update is downloaded, Electron’s auto-updater will install the update automatically as soon as the app quits. To do so programmatically, developers can call autoUpdater.quitAndInstall()
(see Example 4-3).
Example 4-3. Basic implementation of Electron’s auto-updater
const
{
autoUpdater
}
=
require
(
'electron'
)
autoUpdater
.
setFeedURL
(
'https://my-update-server.com'
)
autoUpdater
.
on
(
'update-available'
,
()
=>
{
// An update is available! If desired, this
// information could be surfaced to the user
})
autoUpdater
.
on
(
'update-not-available'
,
()
=>
{
// An update is not available! If desired, this
// information could be surfaced to the user
})
autoUpdater
.
on
(
'update-downloaded'
,
()
=>
{
// At this point, the update is ready and will
// be installed when the app restarts. To do so
// immediately:
autoUpdater
.
quitAndInstall
()
})
autoUpdater
.
checkForUpdates
()
Once an update becomes available, it will typically take a few weeks for users to update. To determine how long it will take a typical user to receive a new version, consider how often the application is opened, how long it is usually open for (if it is closed before the update is downloaded, it will have to restart the download the next time it is opened), and what kind internet connection your users typically have.
App Stores as an Alternative to Automatic Updates
Electron apps can be submitted to both the Mac App Store and the Windows Store. While a bit of work is required to become a registered developer with each store, their respective update-mechanism is unmatched by any custom updating framework. Baked into the operating system, updates can be downloaded without the application running, and because they are installed by the operating system, chances for failures are dramatically reduced.
Of course, there are plenty of arguments for using Squirrel. Electron Apps are only supported in the Windows Store from Windows 10 with the Anniversary Update on, and the Mac App Store requires apps to run in the macOS sandbox.
Get Introducing Electron now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.