Electron in Practice: Building a Desktop Application with Web Standards
I just released a large Electron app which I’ve spent the past two months coding. It’s been an exciting but often times frustrating experience.
I definitely don’t have any regrets. Electron is great and honestly I wouldn’t have built it on any other platform.
Polar started out as a way to to solve a difficult problem I was having — my reading collection had gotten way out of hand.
I had tons of PDFs and HTML pages in my own personal document repository and of course no decent way to manage them.
I wanted to be able to annotate them and of course I wanted to sync them to Anki. This way any key fact would be remembered and maintained for as long as they’re important to my career.
To complicate this I use Ubuntu and PDF support on Linux is, to say it kindly, rather pathetic.
I stumbled upon the pdf.js, a web framework from Mozilla and was rather impressed.
What makes pdf.js so impressive is that it’s a full PDF rendering solution based on web standards that renders inside the browser using canvas. The text is layered above the canvas so you can also search the document while it’s rendered in the browser.
At that point a light went off — I could just combine pdf.js with Electron and create a full PDF viewer and document manager which was cross platform!
My background is web infrastructure. My company Datastreamer is distributed blog and news content syndication platform. This seemed like it would be pretty straight forward since I could basically duplicate a web architecture in a local desktop environment.
Now Electron is great. However, like every existing framework ever created there are some problems which make it a bit difficult to work with.
IPC and Communication Between Processes
Electron is based on a multi-process model. The main process is basically NodeJS and it controls a set of renderer processes backed by libchromiumcontent.
The process isolation is both a blessing and a curse. It allows you to separate concerns but the second you have to do anything complicated between processes you’re in a world of pain.
The Electron IPC framework leaves a lot to be desired. It’s just far too difficult to work with.
Additionally, there are major known performance issues with IPC. Good luck if you perform anything latency sensitive.
I think the main solution here could be to use something like GRPC. With GRPC you could actually generate clients bindings for making calls within Electron instead of having to craft and send custom JSON message.
It could also allow you to build out frameworks for serializing message requests to prevent mutation of datastructures across multiple threads.
You could embed the GRPC daemon in a single thread handling all requests for an embedded database (for example) without having to worry about the processes stepping on each other.
For now the main problems seems to that within Node, GRPC just uses the main event thread. This would block Electron and the GPU (which is part of what we’re trying to solve).
Additionally, the GRPC bindings for node are in C and need to be re-compiled for every platform. It’s already too difficult to build Electron packages so adding another native dependency is just going to make your life harder.
Chrome Extensions
Electron isn’t Chromium so it doesn’t support Chrome extensions.
I initially didn’t think it would hold me back but now this is one of the main things I wished Electron supported.
For Polar, we would like to include ad blocking when rendering content. If chrome supported extension it would be a 5 minute task to embed uBlock to our distribution.
The current proposal now is to implement brave ad-block but this isn’t going to be as easy as I initially thought. Both Ad Block Pro and uBlock implement more complex solutions other than URL blocking. They support CSS classname blocking and iframe collapsing.
I need to do a bit more research here to make sure I implement ad blocking just like the mainstream Chrome extensions.
Serving Your App
Serving your app is a bit difficult. You could serve from the filesystem but them some libraries will refuses to run when served from a file URL.
You could implement your own ‘fake’ protocol like myapp:// but there are plenty of other apps which will refuse to run over anything other than http and https. Firebase is a great example. They just won’t run in Electron unless you serve the content via http or https.
Now you could embed express. But now you have another somewhat complex library to think about. And then there’s the security issue of keeping a port open and also finding out which ports are generally available.
What if you’re distributing this via the Mac App store. You have to request additional permission to bind that port due to their sandboxing. Yet more work.
I think the solution here is to implement your own protocol interceptor and create your own fake URL.
This way you can then have a fake URL like http://mylocalapp.local which isn’t actually behind an HTTP server but Electron can serve up content from a map folder due to your protocol interceptor.
The only way I really came upon this as a practical solution is that Polar has some cool functionality around caching and saving web content as documents that uses protocol interceptors.
If it works I might end up bundling it up into its own library.
Additional Issues
Here are some other somewhat critical issues I had trying to put everything together.
Tabbed Browsing
I really wish there was a standard tabbed browsing library that worked and perfectly emulated the chrome.
I’m going to play with chrome-tabs to see if I can make it work with React.
Code Signing Certs
This was a major pain and I blew about a week of time on this. I had to call Dunn and Bradstreet and yell at a half dozen companies for the privilege of releasing an Open Source app to the community.
In the end I spent about $1000 of my own money to release an Open Source app which I spent two months developing for free.
Logging
There’s really no easy way to setup logging in Electron. There are a few projects that I looked at but nothing really worked. In the end I implemented something from hand which burned up probably 3–5 days of engineering time.
Conclusion
In the end I would definitely still use Electron. I might consider using NW.js though as it actually supports Chrome extensions.
Hopefully Electron ends up supporting extensions at some point which would make this whole process a lot easier.