For most of us, the desktop is really just a place to use the browser to access web-based content, use Microsoft Office, or play games. Depending on your job/hobby, there may be some designer, communication, and developer tools thrown in there. Despite the narrow role the desktop and desktop apps play, this narrow role is extremely deep with a large audience that relies on it:
If you are an app developer, how do you target the desktop? The desktop isn't a singular unit. At the top category, it is made up of Windows, macOS, and Linux. Going one level deeper, you have multiple versions of Windows and macOS and numerous flavors of Linux distros. Building an app for each of these platform variants isn't easy or cheap. Each platform has its own quirks in how you build for it, and these quirks show up in language optimizations, API availability, toolchain preferences, and so on. Unless you were part of a well resourced team, building an app that worked cross-platform across multiple desktop operating systems was a non-starter. That was the case for a very long time...until this little upstart framework called Electron came along:
Electron made and continues to make writing cross-platform desktop apps easy. The apps you build using it are written entirely in HTML, CSS, JS, and a related ecosystem of tools. It is officially maintained by Github but has a very active community behind it. Today, some of the most popular desktop apps (many you probably use) are entirely written in Electron such as Atom, Visual Studio Code, Slack, Microsoft Teams, Github Desktop, Whatsapp Desktop, Figma Desktop, and much more. In many ways, Electron revitalized the desktop development space that had long been left by the wayside as mobile development was getting all the attention.
In this article, we will look at both sides of the Electron story. We will look at what makes Electron so popular and well-loved. We will also look at some of the controversy around Electron and why everyone isn't exactly singing its praises.
Onwards!
At the most basic level, Electron is a full-blown app building framework that allows you to build desktop apps using web technologies. What makes it stand out are a few big things:
In the following sections, we'll dive a bit deeper into some of these points.
If the best way to appreciate a culture is by sampling its food (or by talking to its people...who knows), then the best way to learn about a framework is by focusing on the developer experience. The way you would build an Electron app is very similar to how you would build a modern/progressive web app today. You can use Visual Studio Code, Atom, Vim, Emacs, Visual Studio, or whatever your code editor of choice happens to be. Your favorite client-side libraries like React, Redux, Angular, lodash, ramda, etc. will work. You can even use the Chrome Developer Tools to debug and inspect what your Electron app is doing:
Part of the reason why the workflow is so familiar is because, at its core, an Electron app is really just a web app. Sort of. One major difference is in how an app in Electron is architected.
Typically, we would imagine the starting point for a web-based app to be an HTML page. That's not the case with Electron apps. In an Electron app, the starting point for your app is a JavaScript file that acts as a controller for what your app will do. The code that lives here is responsible for, among many other things, loading some HTML to display on screen. The way the HTML is displayed is through a very WebView-like creature known as a BrowserWindow.
If we had to visualize what we just talked about, our Electron app architecture would loosely look as follows:
You have your Operating System at the bottom, the Electron app runs on top of it with our controller script and BrowserWindow inside it. If you prefer looking at code instead of diagrams, you are in luck! The following code snippet, taken from the Electron documentation, mimics our above diagram and shows how our starting / controller script calls a BrowserWindow and loads an HTML file into it:
const { app, BrowserWindow } = require('electron')
function createWindow () {
// Create the browser window.
let win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
win.loadFile('index.html')
}
app.on('ready', createWindow)
Most real-world apps will be far more complex and contain more code in the controller than what is visible here. They will also have many BrowserWindow objects with each responsible for a part of an app's overall UI:
The reason why we are spending so much time on the BrowserWindow is because it is a a pretty big deal whose implementation and use cases make it one of the more important members in the Electron Framework's API collection. After all, whenever you see any app specific UI displayed in an Electron app, you can be certain that a BrowserWindow is backing it:
Also, as we will cover in a few moments, there is a very popular component or two that powers everything the BrowserWindow object does. Here is a hint: It starts with Chrome and ends with ium.
I can spend all day talking about BrowserWindow, but for both our sakes, I won't. One thing to note is that there is a whole lot more that we can discuss in terms of how Electron works and some of the other APIs that are also important. We covered the big rocks here, but to learn more about some of the smaller rocks that revolve around how to develop for Electron and go far deeper than what we've covered, check out the official documentation.
So far, we have just been looking at the basic mundane tasks that Electron supports. There is a very non-mundane task that Electron supports, and that is allowing your web code to call into native APIs:
Traditional web apps, for a variety of security-related reasons, can't access native APIs. The browser sandbox prevents them. Electron breaks that cycle. Electron removes that security boundary the browser sandbox enforces and gives your web code the freedom to easily call native APIs. These APIs are exposed to Electron (commonly) via node modules in a platform-agnostic way so that you don't even have to worry about what the Windows or macOS or Linux equivalent of an API might be. For example, below is the code to use the native menu:
const { remote } = require('electron')
const { Menu, MenuItem } = remote
const menu = new Menu()
menu.append(new MenuItem({ label: 'MenuItem1', click() { console.log('item 1 clicked') } }))
menu.append(new MenuItem({ type: 'separator' }))
menu.append(new MenuItem({ label: 'MenuItem2', type: 'checkbox', checked: true }))
window.addEventListener('contextmenu', (e) => {
e.preventDefault()
menu.popup({ window: remote.getCurrentWindow() })
}, false)
Notice that the code isn't showing how to instantiate a menu for Windows or macOS or Linux differently. There is just one Menu object you code against. The end result is platform appropriate menus that look and behave like they should.
Having the ability to call native APIs allows Electron apps to meet any native app eye-to-eye when it comes to features. This ability is a double-edged sword. While our Electron app has the ability to be extremely powerful, on par with a native app, it does also expose users to security issues if our web code gets compromised in any way. In the words of the great philosopher Optimus Prime, With great power, comes great responsibility.
One of the more challenging parts of building desktop apps is getting the installer and updater working properly. Getting the app on a user's machine is not as easy as just navigating to a URL like with a traditional web app...or, if you have a time machine, an app that uses ClickOnce or a Java Installer. Fortunately, this is an area that Electron has you fully covered. Building installers for Windows, macOS, or Linux is as easy as running a simple command. You also have the ability to push updates to your app and take the important next step of notifying users that an update is available.
For an example of this in the wild, the following screenshot shows the Github Desktop app notifying me to upgrade to a newer version:
Getting back to the installation front, Electron packages a few things that end up getting deployed and installed on users' machines:
If you look at this list, the first two items will probably make a lot of sense. What may be a bit surprising is the last two items where Electron bundles up Chromium and NodeJS:
If you've wondered at any point how Electron manages to render web content and allow our web code to access native APIs and work cross-platform from a single codebase, you can place a lot of the credit on Chromium and NodeJS. Chromium represents our rendering layer and what BrowserWindow relies on for getting our HTML, CSS, and JS to run. NodeJS is the glue that our JavaScript uses for being able to run outside of the browser sandbox and access native APIs. It also provides you with great extensibility and access to the large ecosystem of node modules that simplify your app development greatly.
By bundling both Chromium and NodeJS as part of the app installer, your Electron app has a known and stable version of both components that you know your app will work against. This makes testing much simpler. If there was any other arrangement where the Electron app downloaded the latest version of Chromium and NodeJS during installation or had users install the latest version manually, there is no guarantee that the app will work since both Chromium and (to a much lesser degree) NodeJS introduce breaking changes between versions all the time.
Tying all of what we've seen so far together, it is safe to say that Electron greatly simplifies desktop app development. That is doubly true if you are building a desktop app that is designed to work across platforms. That is triply true if you are also a web developer, for your existing skills and tools naturally just work in the Electron world. If Electron didn't exist, you might be stuck building WebView based apps that you'll need to maintain multiple codebases for, test separately, and do a lot of busywork that makes cross-platform development a total drag.
Many of the popular apps we use on the desktop are based on Electron. Developers, for the most part, like building Electron apps. This is definitely true when you compare the additional work developers like you would have to do to build a desktop app for multiple platforms otherwise. Despite all of this, why is there a certain undercurrent of frustration when it comes to Electron? We'll look at some of the reasons next.
If your computer is strapped for memory, an Electron app is not your friend:
Running Chromium has a cost, and the payment is via sweet SWEET memory. Even though Chromium is a lightweight version of the Chrome browser, it is still a browser - a component whose lines of code and complexity rival that of the most complex apps ever written.
A full-fledged browser component is also heavy on the file size front. NodeJS isn't a lightweight either. Bundling both Chromium and NodeJS into each app adds a large fixed cost (~130MB) to how much storage space you need. You can see this by inspecting the Frameworks folder that is a part of the installed contents for any Electron app:
You don't get economies of scale with shared components like Chromium and NodeJS when you install multiple Electron apps. Each Electron app installed on the machine will have its own copy of Chromium and NodeJS. Even if multiple apps share the same Chromium and NodeJS version, for the sake of simplicity, each app lives in its own silo with everything it needs provided for it locally. That can't help with the file size problem. If all of this isn't enough, depending on how you configure the updater, there may even be N-1 or N-2 versions of your app on disk as well.
Earlier, we talked about how Electron allows your web code to access native APIs by removing the security guardrails that exist in the browser to prevent this access from working. This is a good thing to make your Electron apps more powerful and capable of doing the sorts of things only native apps could do. The downside is that poorly written or malicious code has the potential of doing a lot of harm. Electron does allow you to specify on a per BrowserWindow basis whether the web code loaded inside it needs system-level API access or not:
This level of control ensures that well informed developers can minimize any potential security risks. Poorly informed developers can maximize any potential security risks just as easily by allowing all BrowserWindow objects access to everything. As with many things on the desktop, the burden falls onto the end-user to keep themselves safe. A big reason is that desktop apps are largely unregulated by app stores and other mechanisms you see in the (ironcally) better developed app stores for mobile devices.
A slightly more complicated security problem occurs from Electron's speed at which it adopts new builds of Chromium. Chromium doesn't nicely just work automatically with Electron. A lot of work happens to ensure all of the various integration points Electron needs with Chromium continue to work, for the Chromium APIs that Electron uses change frequently with each new Chromium release. Because of the amount of work needed to maintain parity with each Chromium release, Electron tends to be a bit behind in adopting the latest and greatest Chromium builds. On the surface, this doesn't seem like too much of a problem. The problem arises when we put on our security lens:
Google constantly patches Chromium with security fixes that it or others have identified. Each new build of Chromium typically contains security fixes in addition to feature enhancements. When an Electron app in the wild is running on an older version of Chromium where the latest security fixes aren't applied, attackers could exploit security loopholes that have already been fixed in the latest Chromium versions.
If you are an operating system vendor like Apple or Microsoft or a Linux distro, there is this desire to have desktop apps be built in a way that respects your platform's look and feel:
This is partly for consistency with other apps that already exist on the platform. This is partly just for vanity.
Electron apps commonly provide the same look and feel across all platforms. While it is possible to detect the OS you are on and provide a different visual experience, most Electron apps don't do that. That isn't because of technical challenges. Many Electron apps do use specialized native components for some better under-the-hood functionality depending on the platform they are running on and APIs they have access to. The bigger reason is that having a consistent app experience is a good thing. Imagine if Visual Studio Code or Slack or Microsoft Teams looked different in each platform they ran in. The cost of training teams would multiply, talking about features in a consistent way would be difficult, your documentation screenshots would need to be special-cased for each platform, and so on. These concerns are especially valid in the enterprise/business environments where Electron apps are most prevalent in.
Electron has a lot of great things going for it, but it also has some shortcomings. If you are an end-user, all you really want is for an app that solves your problems and does so in a way that doesn't bring your machine to a crawl. To meet this desire, the ideal solution is for you to build a native app that is optimized for each platform you want to deploy for. The reality is this: building native desktop apps is difficult, especially when going cross-platform. The simplest way to solve this is by having an app building framework that introduces a middle layer that you code against. The middle layer takes care of platform inconsistencies, and all you focus on is developing the user experience and functionality. This solution is what Electron brings to the table. By going with a middle layer based on Chromium and NodeJS, system resource usage does go up. Compatibility and ease-of-development also go up. Which of these two extreme needs you optimize for is something that you as a developer will need to consider, but you can't go terribly wrong using Electron.
Just a final word before we wrap up. If you have a question and/or want to be part of a friendly, collaborative community of over 220k other developers like yourself, post on the forums for a quick response!
:: Copyright KIRUPA 2024 //--