BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Presentations Desktop Applications in Electron: Pro Tips and Tricks

Desktop Applications in Electron: Pro Tips and Tricks

Bookmarks
53:45

Summary

Paul Betts talks about some common pitfalls that many developers new to Electron fall into, especially people with a web background who are new to Desktop development. He also talks about some tricks and libraries to use to make great app experiences for users.

Bio

Paul Betts is a developer at Facebook, where he works on improving developer productivity using ML in large codebases. Previously, he was the lead developer of the Slack Desktop application, the first large-scale Electron application outside of GitHub to ship to users. Before that, he was a developer on the GitHub Desktop project working primarily on the GitHub for Windows product.

About the conference

Software is changing the world. QCon empowers software development by facilitating the spread of knowledge and innovation in the developer community. A practitioner-driven conference, QCon is designed for technical team leads, architects, engineering directors, and project managers who influence innovation in their teams.

Transcript

Betts: Hi my name is Paul, @paulcbetts on GitHub and Twitter. How's the conference going? Is it going well? Is it the end of the day? Everyone's exhausted and want to go drink, and I am in the way of that. But I have a few things to tell you about. And hopefully it will be interesting. If not, you can just throw the tomatoes. It's fine. I haven't been up here and giving talks in a while so I’m a little rusty. We'll see. We'll see how it goes. Cool.

So like Neha said, I worked on Slack Desktop, which was the first electron app outside of- Atom Electron was originally not this separate project. It was really just this library to host Atom the text editor. How many people have used Atom, the text editor before? It's pretty good. It's not bad. But then they realized, I was like, “well, this is really great tool to build this desktop application, but we think it can be a really great tool to build all kinds of desktop applications.” So Electron is a way to build desktop applications that run on Mac and Linux and Windows PCs using web technologies. So we don't have to use things like Cocoa or WPF or Windows Forms; these things from the 90s. We can use web technology and reuse a lot of the pieces we've used to build our websites, to build desktop applications. And that's really cool because it means that we can do interesting desktop-y things like, open users’ files and documents and stuff like that, and show notifications and kind of do things that desktop apps can do. But we can do them in less than the bazillion years it will take you to write WPS and Coco apps. So that's cool.

Who knows who Raymond Chan is? Raymond Chan is this famous developer at Microsoft. He's a prolific developer, and he has this habit of answering every question that could possibly be given to him. He's on a million mailing lists, and he just responds to them always because he wants to help people. He knows the answer to a question and he can answer that question and help him out. And this compulsion to answer all these questions is called Raymond Chan Syndrome, and I have Raymond Chan Syndrome, unfortunately. So I spent a lot of time in Atom community Slack and these other places trying to answer questions and help people out and stack overflow and stuff like that. And so I noticed some trends, when people are writing their first application in Electron. Here are a few things I noticed people are doing, and it can be done a little better. We've got some suggestions on how to improve them. So here are a few things I noticed people doing on Electron apps that make users mad, right?

Memory Usage Matters

So one thing that's really important with Electron apps is that memory usage matters. We'll put it in big, big, big words. Electron gets, in my opinion, a little unfair rep about using too much memory. People get a little grumpy about it. They're like, “oh my, you know, A95.exe only used one MB of memory, and it did all this stuff. Why can't Electron apps in 2018 use that?” It's mostly nonsense, but they see a number and they get mad about it because of this.

So this is every conversation I've ever had about Electron memory usage. Them, "I'm so mad, I'm posting on Twitter”, or “mad people were on Twitter.” That's where they live. So I'm like, "Okay, okay, I understand, that sounds really frustrating. What's the commit charge?" And so commit charge is this a unit measure that's the percentage of RAM that's being used by programs that's not cache memory. It's not free. It's actually in use. And I'll have them look up that number and they'll say, "Oh yeah, it's 45%."

And this is the emotion that comes to me when I hear that. The RAM isn't doing anything. Why would we not use it? It's good to use. RAM that does nothing is not helping you to move faster. And so Chromium, in general, has taken the tack that we would rather have 60 frames per second all the time, than have delays and junk and scrolling problems in order to save memory. Which in 2018 is a pretty good choice in my opinion. Might as well use the memory in order to make a better experience.

But on the other hand … So that complaint is kind of nonsense. But as Electron developers and Electron app developers, we can do something about this. We should try our best to actually lower memory usage as much as we can and not use it for no reason because we want to respect our users and respect their memory usage. And the problem is, the people who actually get hit by memory usage problems, will never be able to describe it to you. They're like somebody who's a lawyer or in finance and they're really good at lawyering and financing and they don't care about computers, right? They pay us to care about computers, and so they're not going to able to describe this problem to us when they're actually out of memory and their computer actually grinds to a halt. They're just going to be like, “my computer grinds to halt, and this sucks, and this is a bad day.”

Load Less Stuff

So we should do something about this. And the answer is load less stuff. It's so easy. So there's a few big things that consume a lot of memory. And one of them that- I'll talk about ones that are maybe not as obvious as you would think. So we'll talk about DOM Elements. So DOM Elements are any HTML tag, right? Any of the contents of the HTML tags. So for example, the text in between paragraphs, that's a DOM node, but it is a not a DOM Element. That takes a lot, right? And a lot of times those tags have associated information that takes a lot of memory. And those aren't super obvious.

So for example, if you create a DIV that is 20,000 by 20,000 pixels. You set the width and height explicitly, right? And you put some text in the middle. That DIV, the actual contents ii pretty small, right? It's just a DIV. But if you remember back to Jenna's talk- who went to Jenna's talk before us? It was really good, right? So she talked about the compositing and painting rule. And so when it goes to take that huge DIV, it has it create a giant bitmap in memory, right? That bitmap, even though the DIV was maybe 30 bytes of texts, that composited layer is really huge. And so this really comes into effect- of course, if you remember, I worked at Slack, right? So Slack consists of a message pain where you just scroll and scroll and scroll and scroll. And if you looked at the layers- they have fixed this since then- back in the day, if you looked at the layers, you'd see a giant 30,000 Pixel layer. That takes a lot of memory. They would also do this amazing trick that was back in the old days where they're like, “we want to hide an element, but we're from the JQuery era, we don't trust hide and displayed done. What we're going to do is we're going to set it at Pixel negative 20,000, 20,000.” And what that immediately does is created a giant layer because it's got to put it at the top off screen. So things like that can contribute to memory usage.

And so in general, we want to map the number of DOM Elements, the number of DOM Nodes and stuff like that to the stuff we see on the screen, right? We don't want to have a bunch of DOM Elements and Nodes hanging around, that is unrelated to stuff that the user isn't seeing, right? So like long scroll areas. Images are really big, right? So if we have giant images, that's going to cause our memory usage to be really big. And we'll go over a few ways to deal with that.

And the other one is much more straightforward, and things that developers think of as JS Heap, right? JS Heap is all the objects you allocate in your JavaScript, all the libraries that you load, all the code that you import when you start your application. So that one's a little bit more straightforward. In general you want to take a look at that Nodes number and just make sure it's not crazy high, right? If you're getting around the 40,000, 50,000, 100,000 range, then you might have something you can debug or get rid of. This you can get to by going to the performance tab in Dev tools, checking memory, and then take a profile. Just scroll around or do something, and then you'll see that Nodes thing. Yeah, that's super useful. And it's really subtle. So I wanted point out because a lot of people are just like, “Nodes, okay.”

So the easiest way in 2018 to solve this problem about too many HTML Elements, is use React or Vue and virtualizing lists. Who knows what a virtualizing list is? Like React Virtualized, etc. It means that … I'll just explain. So you have a list of 10,000 messages, but on screen you might only see 20, right? Instead of constructing the associated DOM Elements for 10,000 items, we're going to construct maybe 40, right? And as the user scrolls around, we're going to stuff in the real values and put them on screen. So essentially it kind of does that work of only having the DOM Elements on screen that you need. It does it for you. And so a common library for this, it's called React or Virtualized. It's really useful. Anytime you have a really long list, you want to use React Virtualized because you don't want to have that all on the DOM. It uses a lot of memory.

Cool. So here's an important part. So a lot of this advice applies to all websites, you could use it for any web application. This is kind of specific for electrons. So electron is interesting because it's kind of like a different environment than the web because you're loading everything locally. So it means some of the trade-offs are a little different, right? Like you're like, “oh, it's like I have a network that's super, super fast and loads at one gigabyte a second,” which is kind of true. But it means that everything you're loading on startup in terms of JavaScript parse, which normally isn't as big a deal because network always is way bigger than parse time, parse time is now a big deal. So for example, if you load up rxjs- which we used a lot in Slack Desktop- if you load the full version, it can take up to 1.5 seconds of parse time, just the JavaScript evaluating that giant file and then figuring out what to do with it, before either application loads.

And so parse time in anything in the startup path is the amount of time between the user clicking your app, and it opening. So that's a huge, important thing. You want that to be really fast because desktop apps feel like desktop apps when you double click on them and they open; when they don't take forever. So even though we've got this amazingly fast network and we can load all this stuff really fast, the flip side is that we really care about initial paint. And so anything that gets in the way- the libraries you load on startup will definitely get in the way of that. So it's also bad for memory usage because Node modules never get unloaded. So once you load them they're stuck. There are some clever hacks you can do to kick it out of memory, but those are taking over the Node module system. In general, libraries, once you load them, they're loaded.

So we can see that the good news is that this is really easy to catch. All you've got to do is go into the performance pane and refresh while recording and you'll spot it right here. You can see module.require, module load, so we're stepping through the Node module system. And if you look in top-down you can see what modules are taking the most amount of time. And so that makes it easy to say like, oh, I'm loading, all of Moment.js”, and Moment.js includes like all these crazy time zone table data, that's really huge and maybe you don't need that, right?

So these kinds of things are really easy to do. They don't take a lot of time, but a lot of Electron developers I find just don't think of it. And so to make a great desktop app, we should think of these things. We should think of memory usage, and we should think of a startup performance. And the good news is Chrome DevTools are really great, they're some of the best developer tools in the world for debugging and like profiling and stuff like that. So that's cool. So we've got the tools, we should use them.

Use the Heap Profiler

Use the Heap Profiler. So if we go on the memory tab in Chrome DevTools, it'll tell us all kinds of stuff. It'll tell us what we want to take snapshots of, like memory usage, and we can see a certain amount of memory. This is a snapshot of literally a blank Electron app, Electron storage app. So we can see that the vast majority of memory usage is compiled code, which makes sense because there's nothing else. And so, but we can see by type what's interesting in here. We can see a delta snapshot so we can take a snapshot and then maybe use our app or a load of feature or something like that. And then take another snapshot and see what changes, because it is useful for tracking down memory leaks and stuff like that. It will tell you … This is a little hard to understand or dig through why something is still in memory. So it has this thing called retainers which are the things that are keeping it in memory. Remember JavaScript is a garbage collected language. So the reason that something has a memory is because something else is referencing it. So you have to dig through and see, oh, this is being held on to by window, right. And window never gets unloaded. So this can be a little bit of some magic, but I'm sure that there's lots of good talks on it. But in general, this will tell you Node memory that you're using, like a loading Node libraries. It will tell you about your application memory. It'll tell you about most things that are interesting. So we're going to drive this number down.

Don’t Run Stuff in the Main Process

Cool. So performance is cool, memory usage is cool. Another thing that I see a lot of new Electron developers, they're often coming from a web developer worlds. They're not used to native development. Chrome and Electron have this concept called the multi-process model, right? So your program in an Electron app starts off as a Node script, right? Like there's no DOM, there's no HTML, it's just a Node script that's running. But you have this magic class called a browser window, right? And so when you create this classical browser window, just like new browser window, you will create an entirely separate process that loads an HTML page, and that HTML page is going to have DOM and CSS and it'll run its own JavaScript and kind of do all the things that you'd expect and it represents like a single window that's open, right?

And so the vast majority of Electron apps just through kind of coincidence or UI design, open a single window. It's like Slack desktop opens a single window. And most chat apps open a single window. That's not required. It's just happens to be that way. So most simple Electron applications are going to have two processes, two Node scripts running at the same time or two JavaScript environments. It's going to have the main process and what's called the render process. And so the render process is the one that can create DOM elements and stuff like that. And the main process is the one like our startup one. And so people see that model and they're like, “oh, main render, front end and back end”. And that's a really easy mistake to make because it seems like that's how it should be, but it's not, it's not at all. So we don't want to run stuff in the main process.

The Main Process Is for Orchestration

But what about … No. So the main process is not the backend, it's not a background thread. People want to treat it as the background process that I can run stuff in the background and do all this really intensive code work. It's not the server. So the main process is simply for orchestration. We don't want to do anything in that. We just want to have the main process tell other processes what to do. As a main process actually does a whole bunch of stuff inside Chromium, for example, the main process handles all the network requests, right? So when you make a network request, it gets sent to from the render to the main process. The main process actually does the network request, comes back with the data and sends it back to the render process. The reason it does this is so that it means that in Chrome, the render process, essentially the contents of a tab, never have access to directly make network across or any … It's because of the sandboxing, right? So we want that render process, the one that's actually executing normal content to have as little privileges as possible.

Of course, in Electron we throw that all out the window. Render process can do whatever they want. It's neither here nor there. But this is the important part, that running code in the main process means that every other process grinds to a halt basically, because Chrome is always sending these messages to make network requests to do things, the main process owns the actual native window, right? So like you create a window on macro, it's like an NS Window and windows, it's a 132-HWind. The main process owns that. And so you do a resize, right? And so it's got to signal via IPC, by sending messages back and forth, to tell the render, hey, “your size used to be this number, now it's that number. You need to redraw.” So Chromium has all kinds of these messages that it sends back and forth between these two processes that you don't see that you don't have to worry about.

But the problem is that when your main thread is busy doing stuff that like, you calculated the digital pie or you're running this thing, you're running a server, it can't do any of that stuff. And more importantly, if a render process is waiting for a response, it can't do anything either. So spinning on the main thread, the main process blocks all of the render processes who get stuck waiting. So it means that in general, the more your main thread, the main process does, the more your app is going to feel glitchy and junky. You're going to get weird delays and scrolling or the apps are just going to feel glitchy basically.

So Electron actually exposes IPC between process. So we can send messages between the render and the main process. And so it exposes a synchronous way that actually stops and like asks for a response. And so what's tricky is that if you use … One of the things that's really common in Electron is to use the remote module. How many people have ever used the remote module? It's really common enroll in Electron. The remote module is a way to access things on the main process from a render process. So it's really convenient. But the problem is it's also really slow because it has to use this synchronous send. It has to wait for a response. It stops the render process, asks the main process a question, gets back a response. And so if you do that once, it's not a big deal. If you do that by accident all the time, like from an on click callback or an on scroll callback, you're going to really grind.

Even ipc.send is asynchronous. But it's not enough because you can say if I send a message from the render process to the main process, saying calculate digits of Pi, the actual- I didn't lock but the main thread, or main process is still doing a ton of work. So that means that any other messages that Chromium is sending internally or you need to send from the render process don't get processed, right, because it's too busy calculating something.

The main process, really the only thing you want to do in there is tell other processes what to do. So it's kind of like this message bus, right? We're sending information between windows, right? So if we have two windows and they need to share some kind of state, we'll send messages between them. Certain things you have to set up in the main process, which is kind of unfortunate, but it's how it works. And so for example, menu items, because the main process owns the window, it also owns the menu associated with the window. So menus are in the main process, we have to tell the window render process about these events. And so a lot of what you'll do in the main process is just kind of routing messages, like sending them back and forth.

Or like if you want bounce the doc, right. So like on macOS you have these … your programs are in the doc, and you can bounce it to tell them that it's like a message or something. Or if you have a menu bar or a tray item on windows. And there are a bunch of APIs that only work in the main process, like crash reporting. We want to do that in the main process. But in general, we don't want to do anything in the main process.

So you might ask yourself, how can I do anything? I running code in the render process, you know, causes my app to be slow because I can't- the render process is the thing that draws my UI. And the main process causes the app to be slow because it causes all this junk, where can I run anything? I learned about this trick from the VS code people. They have all kinds of stuff that they run in the background, right? Every time you're typing in JavaScript, they're running Linux and they're running, typescript is compiling in the background, they're doing all this stuff. So how did they do it? And so what they do is this super clever trick. What if we create a browser window but don't show it. So it's hidden. So now we've got a process that has a DOM technically, has CSS technically, but more importantly has a JavaScript run loop that we can do stuff in, but nobody could see it. It can grind all at once, it can like spin all day long at a 100% TPU and it won't affect the performance of our application. So that was a super cool trick. And so what they do is they took it even a step further and they said like, “oh, well what if we had a pool of four background browser windows?” And they just sat there and waited for requests, and we could just put something in a queue and treat them like threads. So they essentially turned browser windows into task pull threads. It's pretty clever.

Electron Remote

So I created this library called Electron-Remote which is impossible to Google because as soon as you type it, you'll get the Electron remote module, the synchronous one. I could've come up with a better name. So the cool thing about Electron-Remote is it's just as convenient as a remote. It gives you like this fake object that you can work with and just call methods on. You can just pretend that it's just a regular class, but it's all asynchronous. It means that all the, if you call a method, it's return value will not be, you know, end or bool, it'll promise event or promise a bool. And so that conversion is automatic. And it will read down the prototype of some object and then generate a fake one on the other side. So that's super cool. It uses this feature of yes, 2015, called proxies. Once you figure out how proxies work, you'll try to use them for everything. And the VA team will get really sad because they're slow and they'll be like, “oh no, that breaks every fast path we can find.” But for these, when we're going over remote processes, we kind of don't care that the overhead is not a big deal.

So one of the things that's super useful about Electron-Remote is it lets us call into render processes from the main process. So we can take a window … We're in the main process here. We take a window and we get this fake object called mywindow.jazz. It's confusing because we're talking about browser windows and also the global JavaScript window object. So this is a global window object of some window. And so now we can just call methods on it. We can call getters, we have this kind of magic syntax for getting a property because getting a property is also asynchronous, even though actually getting a property is never asynchronous because it just returns value. We can call methods on it. So we can get navigator user agent even though in the main process that's not available. And we can do this asynchronously.

And I lost a slide. So Electron-Remote also implements that idea. It was supposed to be in the slide, but I think I messed up the slides. Anyway, Electron-Remote also implements that idea of the task pool, right? So you can actually just say like, write a module that does a lot of stuff, right? Like it does some CPU-intensive things. You can hand it to Electron-Remote, here's this module. I want it to be in some other windows, and I'll hand you back this fake object that is the module, right? So all the same functions are on this fake module object that would be if you just required it. And then as you call methods, it'll be sent to, I think the cap is like four windows, and it'll spin them down as they're not used, right? So as you start calling methods, it'll start creating more windows up to a limit. And then any other requests we cued. So you can do these really computationally-intensive things in the background and not worry about making your app slow.

The other thing that it's nice for, that we used at Slack desktop, is that if you have code that's really crashy, maybe calling into a modern Windows 10 operating system function that always crashes for no reason, the thing that gets taken down is a random taskpool thread, and you're just like, “oh no, it's gone”. Sorry. So you can run all your crashy code in some other context and not worry about it breaking it up. So that's super cool. It's really easy to use. That's the thing I like about it. I've described all these complicated things, but the actual user interface to this library, it's just really straightforward and easy. So that's cool. So that's one way to instantly improve performance in your Electron application; just never do things in the main process.

RequestIdleCallback Is Super Cool

Cool. So, I want to talk about another web API. This is actually a browser API that is implemented in Chrome but not other places, called RequestIdleCallback. And so this works in regular Google Chrome, and it's really useful for making responsive web applications. And so one of the cool things about Electron, in general, is because you're guaranteed you're on Chrome and you're guaranteed you're on some version of Chrome. So you can go to- who knows about chromestatus.com? So chromstatus.com is a site that the Chromium team maintains. And it shows all the cool stuff that's coming in Chrome. Basically it just shows this list of features and you're like, “hell yeah, hell yeah, that's cool. I'm using that.” But normally if you were writing a web application, you'd be like, “oh this looks cool, I'll use this sometime next year when there's a polyfill and people get around implementing it on IE and all these other things.” But you're on Electron, so you could use them now, and you don't have to care about anyone else but Chrome, which is awesome. And so RequestIdleCallback is one of these really great APIs.

So it's like setTimeout in that you're running something on a timer, you're running something later, you're deferring an operation. But it's guaranteed to run when the UI isn't busy. Now, trying to do this with any other API is really difficult. So requestAnimationFrame will kind of do it, but not really. setTimeout is always going to run at a certain time and you can never guess if the user is going to move a thing. RequestIdleCallback is guaranteed to run only when the user isn't doing anything, which is great. And so it kind of goes a little further. It gives you a function that you call, that gives you the amount of time you have before the user's going to do something. Now that's kind of reading the future. But the idea is we guarantee you have at least a chunk of 300 milliseconds to do something. And so you can keep asking that. You can do an operation, say you're like running through, you're saving 100 elements to index DB, and that takes a while. And you might save them 10 at a time. So you'll save 10 and then ask this callback, do I have enough time to save 10 more? Save, save, save. Oh, I'm out of time. Then you schedule it again. And so anytime you want to write an update in the background, it’s really cool for RequestIdleCallback. Or in general sending telemetry data, like Google analytics stuff. Anything you want to do in the background and you want to guarantee that it's some time when the user isn’t doing stuff.

Oh, and there's my Electron-Remote slide. I found it. Yes. Cool. Yeah, so this is the API, right? You give it the name of a module and then you run it through RequireTaskPool, which is just required, but it puts it on a task pool. And then you can call your methods, calculate digits of Pi and you will wait. It returns a promise, Yadda, yadda, yadda.

Just Make an HTML Page

Cool. So some more performance advice. How's this going so far? People enjoying this? Good. No tomatoes yet. All right. So more advice. Just make an HTML page. And so what we want to avoid is this. This is the worst loading screen, and it's because they're trying to … a lot of people ,it seems really tempting and there's a lot of like pros and cons to it, but trying to make your app Electron, just like a frame of a website, is really easy to do. But from a user perspective, it's kind of a bummer. Because they see this, and this doesn't feel like a desktop app. This feels like a webpage in a desktop frame. And so there's a lot of, if you're making a remote content app, like a hybrid application, there are a lot of things you can do. You can set up service workers, you can do all this stuff. But if you're starting from scratch, it's really great to just think of writing an Electron application as just like, I'm going to make a static HTML page, right? I'm going to have a folder full of HTML and CSS and JavaScript.

Now, you have to use, you can't use Babel or SaaS or all these things, all this modern web technology. You can totally do that. But the idea is that we want to have all of our content at the end of the day be local, right? So it's downloaded with the application. When the application updates, you get a new version. That's the easiest way to skip a lot of security problems, a lot of performance problems, because anything local is going to load really fast. So we know that it's going to load really fast. So I'm going to set you in the mindset of like, I'm a desktop application, I'm going to save cache locally using local storage or NXDB or like any other storage format. It won't get you in this mindset of like, oh, the minute I load I need to make a JSON request to load the actual data of my app. And so having this desktop mindset when you build the desktop application is really great, and you'll end up with a better application. Offline mode is like way easier because you're already thinking from the beginning like, oh, it needs to be on this computer.

Just an HTML page is kind of easier to manage, right? When you create a hybrid application, you have to create- you're loading remote content into a local context and then running it, right? That's really dangerous from a security perspective and you have to take … it’s a really careful thing to decide what APIs are available to the remote side and build this firewall to only make sure that they can do certain things and not other things, and it's really easy to mess up. When you get rid of this security boundary and say only local code, only code this distributed with the app is going to run, that makes security way easier.

So one of the things that's really important is that code … your policy of only local code that's running, cross-site scripting is so important. So somebody can send you a Messenger message or whatever, that gets parsed at HTML and then executed like you're evaluating it, you're loading remote content and running it, right? You have to be really careful with remote content to parse it and run it instead of just throwing it in HTML page. Great. So if your JSON API consists of an HTML blob that you just cut out and then throw into a DOM element, then you're going to have the same security problems as loading remote content.

Don’t Run Web Servers in Your App

Cool. So another thing that people want to do is they want to run web servers in their desktop applications. So they're coming from a web world and they're coming from a web tooling world and they're like, “Oh, I got to set up express and point Electron to local host in order to make my app work.” You shouldn't do that. You really shouldn't do that in production. Because what happens if more than one person uses your app, right? So, in the enterprise, it's really common to set up a terminal server, right? This big huge box that has, you know, 50 users and people install your app. And then all 50 users can use it. Well, if you all expect that local host: 1234 is where I'm going to put my web server and then connect to it from Electron, now you've got more than one person doing it at the same time. It becomes a huge security problem because now your web service that's running on some person's laptop is a great way to move between users, right? And one of those users could be a website that requests from local host.

If your app runs as admin, which it shouldn't, but it can, it's a great way to local escalation of privilege or have websites be able to run desktop code if they're really bad. So, and the good news … and the people do this, not because they're lazy or whatever, or because they want to run a lot of their web tooling, right? So they want to use React and they want to use React hot reload and stuff like this. So, the good news is that there's a way to do that without having to run any kind of local service at all, a local web serve at all.

So there's this library that myself and some other people, mostly other people, have worked on, called Electron-Forge. So Electron-Forge takes all of the annoying parts of writing an Electron application, all the stuff that isn't writing your app, like setting up builds and setting up Babel, and it's a little bit like parcel for Electron applications, in that it's trying to get rid of all the grunt work that you don't want to do and save you to be able to just write your application. And so it sets up all this stuff like React and View and Typescript and all this stuff out of the box. It's really easy to use. It's a little bit like Create React app in that it does all this stuff for you.

So we don't need Express, we don't need Webpack. It's going to do all this stuff for you during development on the setup like hot module reload. So as you're saving, it'll reload the application, like reload components. We took all the knowledge that desktop developers have around packaging and creating installers and code signing and stuff like that, and did all that for you too. So it's a really great way if you're writing a new Electron application, to just spend time on the fun stuff and not spend time on build stuff, which is like the opposite of fun.

But I like Webpack!

I like Webpack. Who likes Webpack? It's a very powerful tool that sometimes is not a joy to use. I like Webpack too, I think that you can't argue with success, right? It powers some really huge websites and it's, you know, it's a great tool. Unfortunately, using with Electron gets a little weird because Webpack when you create, when you bundle all these things, right? You're writing in your code require statements, or import statements, right? And Webpack is taking all those import statements and kind of mushing them together and creating this one giant file called myapp.js. And so it has this concept of require, and so it implements require because it implements modules.

Electron also has modules, but it uses the regular Node module system. So now when you type require, you're getting Webpack, but sometimes you need Node. So the biggest problem that you hit when trying to do Webpack is that as soon as you go to load a native Node module, like a spell checker or things that do notifications, you blow up. And so packaging becomes this really weird dance of trying to like exclude stuff and ignore Electron, ignore this module, I've got to figure out a way to have a Node modules folder and Webpack's Node modules folder. So now I have to have two Node modules folders, one that's in the app and one that's not in the app that gets Webpacked. Because if you try to Webpack like a native library, then, of course, it's going to fail. And so it gets weird. It gets really frustrating. And so Electron-Forge kind of handles a lot of stuff for you. So sometimes you need Webpack. People will do these various kind of specialized things and they have got to use Webpack and it's a kind of a bummer. But if you can, Electron-Forge is great.

Node-RT Is Cool

Cool. So mostly in the talk, I've talked about performance, memory usage, do your homework kind of topics, blah, performance, security, memory, thanks mom. So now I'll tell you about a thing that has nothing to do with performance or memory, or do your homework kind of topics. I'll talk about something that … a library that's really in my opinion under-marketed, and not as well-known as it should be. So it's called Node-RT. And so Node-RT only works on windows. And what it does, it lets you call basically all of the really cool Windows APIs from JavaScript in Electron. So this is really powerful. So call Windows 10 APIs from Electron is super easily. So I say Windows 10 because it's calling because the name as the name Node-RT suggests, it's calling into the Windows 10, like new style applications. Like the UWP applications.

And so if you're trying to build a desktop application using Electron, you want to be able to do these desktop-y things, and so there's all kinds of really interesting things. So digging through, you can get the geometry of the display and all the features of how many monitors they have so you can save off that information. Save off where the window is and the thing you can call geolocation, which in Electron doesn't work because you need a Google token. I didn't even know this until I was researching for this talk. Windows apparently has an OCR engine built in. You can hand it in image and it'll be like, “here's the text.” I didn't even know it could do that. That's super cool. You can do things like capture the screen, all the things that like desktop apps can do. You can create a power information, say whether you're on battery or on plugged in and you can say like, “oh, I'm plugged in now.” Be a little bit more agro about doing stuff.

What about MacOS? There is no super easy way to call MacOS APIs from Electron. You have to write a Native Node module, which means that you have to, you're dumped into writing C++ code which is a bummer. For Electron there's a lot of really simple Node modules that can kind of cargo cult, copy the whole thing and replace the inside. So it makes it a little bit easier. You can do really simple things also with this module called node-ffi, which is a very old, from the beginning of Node-JS, that lets you call C functions from Node. It's really easy to mess up because you're actually writing a seed, a header definition of a function. And if you make a typo, or say that something is eight bites when it's four, node-ffi will totally trust it and then crop the stack and bring down the render. But it can be useful. And node-ffi is actually cross-platform. You can use on all three operating systems, which is cool. So but Node-RT really easy to use. The API is really straightforward.

How Can I Figure out What I Can Do?

So how can I figure out what I can do? If you install Visual Studio, there's this feature you can get to if you dig. How many people have done native windows development or Windows-y things? If you go through the joy of installing Visual Studio 2017, you can open a bank blank app and then get to this thing that lets you see all the APIs. And so you can kind of browse and get some ideas. Like what can I do? Poke around. And so here's how to do it. You create a new app, a Windows universal app, doesn't matter, blank whatever. You go to the right-hand side and go to add references, go Windows desktop extensions, because VWP runs on all of these different platforms like the phone that doesn't exist or like a HoloLens or an X-box. So you have got to tell it I want to run on a desktop computer because it'll add extra stuff. And so then you go to this menu item called view object browser. And so then you can poke around in all the libraries. It's kind of like this automated documentation-generating thing. It's actually like reading the definition and through the class file and saying like, “oh, this has these namespaces and these classes.” You have to find this extremely obscure name windows.foundation.universal.apicontract. This is new. I didn't know about this until I tried to find this and couldn't find what I was looking for. And then you can scroll through, you can look at the application model. I can poke around, chat, context, like stuff like that. There are a few things that you can't do unless you're a Windows store application. So server push notifications are one thing you can't do. So being a woke up app.

Oh, I'm out of time. So in summary, make an HTML page, load less stuff, and load in the renderer process. Do cool stuff in Windows 10. Thanks. Cool. So questions?

Woman 1: We actually five minutes. So if you wanted to do one more slide we can. Otherwise, we can just start with questions.

Betts: Oh no, it's okay.

Woman 1: Okay, cool. You got a question?

Man 1: Hey, thanks. How you do automation tests for an Electron app?

Betts: Yeah. So for Electron apps you can connect Electron to Selenium, just like you can with a browser. You need to do a few backflips to make that happen. Basically when your app starts up, you'll see in the command line in the parameters that somebody is trying to start Selenium. And so like then you can kind of activate like a special mode of your app would, just load the app and point it at Selenium. But in general, a lot of the web techniques you would use on the browser will do.

Man 2: Hello. Hi. So suppose you want to send push notifications from the server to the Electron desktop app. How is it done?

Betts: Yeah. So in general, on desktop, push notifications are not super valuable. The idea of push notifications or waking up the app when it's not being used. Right? You can be in Windows store app and you can get that, just like on MacOS when you have to be in the app store to get push notifications. Push notifications are separate from showing a notification, right? So if your app is open, you can use web sockets, you can use polling or any other kind of standard thing to know that there is a notification and then just show one yourself. So on Windows 10, even if you're not a Windows store app, you can still show notifications, like native notifications then you know, show all the kinds of buttons and you can do all kinds of really interesting stuff with Windows 10 notifications. You just can't be woken up unless you're a Windows store app.

Man 2: So maybe I'll just use a web socket on pulling. I mean pulling is probably a …

Betts: Yeah, you wouldn't want to pull, but you can use a web socket. Yeah. So unlike any way that you don't on the web, would listen for messages.

Man 2: Cool. Cool.

Man 3: How does the same origin policy work with an Electron application?

Betts: So because your app is running in a … well so you can disable it if, but you shouldn't. In general because your app hopefully is running under a file URL, file URLs don't have same origin policy. So just like if you load, you know, double-click on a webpage or a like an HTML file on your computer on Chrome, so you don't have to worry about being in the same origin policy. So one thing that will trip people up is that because you're a file URL, you don't have cookies. So if you try to use Google Analytics, it's going to try to save a cookie to make it work. You don't have cookies in a file. But there are libraries to fake it out, create a fake document cookie store that like saves local storage instead. So yeah. So, you can actually nerf all web security, which you should not do, but you can do it.

Woman 1: All right. Do we have any more questions? Yeah.

Man 4: So you mentioned some strategies to offload things from the main thread. Are web workers just not a possibility?

Betts: Web Workers are a possibility, but they have a lot of caveats in Electron apps. So Web Workers which you'd probably want to do require a bunch of native code or require a Node module. Originally, Web Workers couldn't do anything related to Node modules, and now they can do certain things but not other things. There's like a guide in the Electron docs that says exactly the kind of caveats and limitations. So those work too. If you can jam what you want to do into a Web Worker, that's great. I mean in general, the reason people don't use Web Workers that much is because all the data you send back and forth has to be over messages and so that data … usually you're offloading it because the thing is huge and you can't send it as a message back and forth because you're sending this giant IPC messaging grinds. So it limits the utility of Web Workers. But yeah, if you can fit into Web Worker, that's great because the debugger experience is a little better. And you can actually see that in Chrome DevTool.

Woman 1: I think we have time for one last question. Does anyone want to ask one? All right.

Woman 2: Say I happened to be front-end engineer writing code that may or may not get used in an Electron application. Are there any things that I should be aware of?

Betts: I think as a front-end engineer, one of the things you can do is just think about, if you pretend you're in a desktop app, what would you want to do? And if you want to do something, the desktop team can probably do it. So if that API doesn't exist, because a lot of like front-end engineers when you work in this hybrid environment, they get used to the restrictions of the web and they're like, “Oh yeah, I can't do that because like I can't do that.” In Electron, you can totally do that. And so if an API isn't there, just come up with a contract on what it should look like, or what you want it to look like, and you could probably do it.

Woman 1: All right. Thank you so much, Paul. Let's give another round of applause.

Betts: Thank you so much for your time.

See more presentations with transcripts

 

Recorded at:

Dec 11, 2018

BT