How To Make A Great Electron App

Electron is excellent.

There's a long history of ways to package HTML and Javascript into an installed desktop app. The result usually feels like a web app detached from the rest of the OS.

Electron makes it easy to do better.

Electron exposes lots of deep OS integrations thru simple Javascript APIs, so you can have a single clean codebase instead of having to code against three different C++ and Objective C libraries for Windows, Linux, and Mac.

Using npm and electron-prebuilt, you can also keep your build simple and clean. No node-gyp, no native compilation at all. Things that are a pain in most environments, like installers and automatic updates for multiple platforms, are easy here.

Feross and I used Electron to make WebTorrent Desktop recently. We were surprised by Electron's quality and attention to detail.

Here's a list of things you can do to make your Electron app feel native and pro.

(If you're new to Electron, check out the Quick Start. First things first! This post is for people who already know Electron, but want to make their apps even better.)


The List

  • Dock and tray integration
  • Notifications
  • Menus
  • Shortcuts
  • Drag and drop
  • Crash reporting
  • Signed installers for all three platforms
  • Automatic updaters for Mac and Windows
  • Fast startup
  • One-step build

WebTorrent Desktop implements 10 / 10.

How does your app score?


Dock and tray integration

On Windows and Linux, you can minimize to tray.

(You can do it on Mac too, but you probably don't need to since Mac has the dock.)

This is great for running in the background or running automatically on system startup.

If you're making a decentralized app, you probably want to do this to keep your network healthy.


On a Mac, integrate with the dock.

Show a progress bar when the user might be waiting for something to finish.

Show a badge when work finishes while your app is in the background.

Caveat: only some Linux distros support the tray correctly. Check that you're on one of them--otherwise, your users will have no way to quit your program if you hide the window and your tray icon doesn't show up. See checkElectronTraySupport for a workaround.


Notifications

Desktop notifications work on all three platforms. They're really easy to use.

Stay concise. Don't go over 256 characters, or your message will be truncated on Mac OS.

Here's an example with custom sounds: a satisfying "ding!" whenever a file finishes downloading.

Play sounds using the normal web audio API. You'll want to preload them. Here's a nice way to do that.


Menus

Electron gives you nice declarative menus on all three platforms.

You can use them in lots of places: context menus, dock icon menus, tray menus. Most are optional but the one you'll always want to implement is the window menu.

Follow each platform's conventions for what goes where. For example, if you have Preferences, Mac users will expect to click YourApp > Preferences while Windows users expect Window > Preferences and Linux users expect File > Preferences.

If you have a button for something, give it a menu item anyway. Two advantages: it makes your keyboard shortcuts discoverable, and it makes actions searchable under Help > Search on a Mac.

See it in action here: menu.js.


Shortcuts

Electron supports two kinds of shortcuts: menu shortcuts and global shortcuts. 

Menu shortcuts are great. New users can click around and learn what's available. Power users can use your app very efficiently.

Follow each platform's keyboard shortcut conventions. Electron makes this easy: for example, you can specify "CmdOrCtrl+O" as the accelerator for Open, and it'll be Cmd+O on Mac and Ctrl+O on Windows and Linux.

Global shortcuts work even when your app is not focused. For example, if you're running WebTorrent Desktop in the background, playing an audiobook, while using Chrome in the foreground, you can still use the play/pause button on your keyboard (F8 on Mac) to control WebTorrent.


Drag and drop

If you want to let users drag files into your app, you'll need to handle three separate cases.

When someone drags files onto the window of your running app, you'll get the regular HTML5 drag-and-drop events.

When someone drags files onto the icon while your app is running, you'll get a special Electron on-file event.

When someone drags files onto the icon while your app is not running, the OS will run your main process with special command-line arguments. You'll have to handle those.


Crash Reporting

Electron has built-in Crashpad support so that you can get a report when a process crashes.


You might also want to be notified of uncaught Javascript exceptions. You can do this:

  • In the main process with process.on('uncaughtException')
  • In the renderer process using window.onerror

Your server will need an API endpoint to save the crash reports. Check out the WebTorrent website code for an example of how to make one.


Signed Installers

You must sign your installers. Otherwise, you'll get a scary full-page red warning on Windows that says your app is "untrusted", and modern Macs in their stock configuration will refuse to run your app altogether.

Here's a build script that does this for Mac and for Windows.

Getting certs:

To get a Mac signing certificate, sign up for an Apple Developer account. It costs $100 a year.

To get a Windows signing certificate, we recommend Digicert. The documentation for Windows app signing is surprisingly bad. If you go with the wrong vendor, they'll ask you to mail them notarized paperwork. That makes it a slow and annoying process to get the cert. Digicert is easier: they just send you a password via Certified Mail, you go to the post office, show your ID to pick it up, and bam, you get your signing certificate.

You do not have to go thru the Mac App Store, unless you want to. If you do, your app will be sandboxed and you may have to change the UX slightly to accommodate the extra restrictions and permission prompts.

You definitely don't need the Windows App Certification Kit. WACK is wack, and also kind of obsolete.

Consider starting an organization to own your project's domain and certs. It looks a lot more legit if a user downloads your app and sees "Do you want to run this file? ... Publisher: Webtorrent LLC", than if they see "Publisher: Jim Bob". There are other advantages as well. In California, starting an LLC costs just a few hundred dollars and a few hours of time.

Keep your signing certificates safe. At a very minimum, they must never be sent via email or checked into a Github repo, even a private one. In fact, certs should never ever be online at all. Store them offline, passphrase-protected. Back them up onto a thumb drive, preferably an encrypted thumb drive, and keep it safe.

Once you get your first million users, your auto updater is basically a botnet with a million nodes. With great power comes great responsibility.


Automatic Updaters

Your app is getting better every week. Remember Flash back in the day, nagging you to Please Upgrade To The Latest Version? Don't be that guy.

Ever since Chrome popularized autoupdaters eight years ago, users have come to expect software to just continuously get better and fix bugs automatically.

Writing your own reliable auto updater is hard. Fortunately, Electron has already integrated with Squirrel, which makes it easy.

Squirrel only works on Windows and Mac.

For Linux, I recommend checking for updates as you would on the other two platforms, and simply popping up a notification if a new version is available:

Here's a bit of code that checks for updates on all three platforms: updater.js

Your server will need an API endpoint to tell the app which version is the latest. This can be really lightweight. You can offload the heavier work of hosting binaries to Github Releases.

Here's our server code for the updater API.


One-Step Build

16 years ago, a smart guy named Joel Spolsky invented the Joel Test for whether a software project has its act together.

#2 on his list: Can You Make A Build In One Step?

Yes, you can! Electron makes it pretty easy to automate your build. And you can do it without any fancy tools like Grunt or Bower.

Check out WebTorrent Desktop's build script. With one command, npm run package, we can:

  • Run the linter and tests
  • Package the app for all three platforms
  • Create signed installers for Mac and Windows*
  • Create binary deltas for the auto updater

* (Almost. Right now we still need to do the Windows code signing on a separate Windows machine, but there's a bug that should be fixed in the next few weeks that will allow us to build an entire release in a single command on a Mac.)


Fast Startup

You want your app to start quickly and smoothly. If it doesn't, it won't feel native.

Check out Spotify, for example. After clicking the dock icon, the window takes a long time to appear. Once it does, it first flashes grey, then some DOM elements appear, then the style changes, then more elements appear. Each time, it reflows, so the elements bounce around.

It feels like a web page loading over slow internet, not like a native app. (Spotify's UI is built with HTML and Javascript, but it doesn't use Electron.)

Make your app load quickly.

Step 1. Measure

Right at the start of our main process index.js, we call console.time('init')

Then, once the window (renderer process) has started and sends us an IPC message saying it's ready, we call console.timeEnd('init')

That gives us a bottom-line number to get as low as possible: the total startup time.

Step 2. Get your DOM right the first time

If you use functional reactive programming, this i easy. What you see is a function of your state object. The state object should be correct and ready to go the first time you render your DOM---otherwise, the DOM might have to change immediately and your app first renders, and the elements will jank around.

In our case, WebTorrent Desktop loads a JSON config file before the first render. This only adds a few milliseconds to our couple-hundred-millisecond startup time.

Step 3. Defer loading of big modules

We bisected using console.time() calls to find out which requires() were taking the longest, and cut our startup time almost in half by loading those lazily. They are loaded either the first time we need them or five seconds after app startup, whichever comes first.

Step 4. Colors and CSS

Make sure your window background color, which electron sends down to the OS, matches your CSS background color. Otherwise, you'll see flashes of white when the app is starting and again when you resize the window quickly.

---

Now we're already doing a lot better than a lot of apps. The window shows up quickly and with the correct background color, then a fraction of a second later the UI shows up.

One last improvement: by adding a CSS fade-in, the window shows up and the UI smoothly but quickly fades in, instead of popping up suddenly. Try it both ways---we think this feels better:


Conclusion

1. Make It Native

When on Mac, your app should look and feel like a Mac app. When on Windows, it should feel like a Windows app.

2. Make It Fast

Measure your startup speed. Keep it well under a second.

3. Keep It Simple

Your users don't care if you're using Flux and Redux and React and Bower and Grunt and Less and Coffeescript. Plain npm, plain Javascript, and plain CSS go a long way. Electron supports require() natively, so you don't need Browserify.

WebTorrent Desktop uses no preprocessors at all and no build system except npm. Spend your energy on things that give your users pleasure!

Bruce Lee said it best--

The height of cultivation always runs to simplicity. 

Art is the expression of the self. The more complicated and restricted the method, the less the opportunity for expression of one's original sense of freedom.
To me a lot of this fancy stuff is not functional.

Happy Hacking!



58 responses
Awesome advices! Got many promotion experiences.
Awesome tutorial on electron , i have ever seen.
Regarding "Step 4. Colors and CSS," the recent addition of the "ready-to-show" window event now makes it so that you can show your window after a single frame of the page has been rendered, therefore preventing the white flash. This doesn't prevent the resizing discolouration though.
@Evrim sweet, we'll use "ready-to-show" in WebTorrent Desktop!
How exactly should i use electron-app.css ? I have prefsWindow = new BrowserWindow() in my app.js which renders prefs.html and uses prefs.js How can I cause prefsWindow to fade in and fade out on keybinding (I am using globalShortcut 'Esc' for this and doing prefsWindow.show() and prefsWindow.hide(), but want to see fde in fade out)
i would like to integrate a datatable on my electron app (like this http://www.simonecelia.it/datatable.php) but i can't make ajax works... got the persistence on an embedded tingoDB
Can You Give Me Source Code.
This page worth of getting zoomed 140%...BTW good post, keep it on!
bug should be fixed in a few weeks huh? going on over a year LOL https://github.com/electron/windows-installer/i... i don't think its a good idea to cross compile on a machine that is running different native binaries. You can always run windows and linux in a vm appliance or use a CI SaaS like travis-CI and appveryor.. great article thanx ;)
Hey this is a great article, helpful for me. Still I have a silly problem. In my case when I build the electron app, I figured out that the cursor changes. I think this changes to default cursor KDE (x11) type. Can you help me out. Again Thank you for this article really helpfull.
Such a valuable resource for anybody entering into Electron development. And you can tell by your code snippets (and the rest of the open source repos on Github) that you write exceptional code. Clear and concise. Thanks so much for sharing.
A new recommendation for Linux environments, you should implement Snap as a new Packaging system that makes autoupdates, so it's similar (but better) than Windows and macOS approach.
46 visitors upvoted this post.