I spent the last weeks getting started on a minor side project, which I eventually want to use as a simple macOS application. I didn't require any fancy distribution paths through the Mac App Store, but sharing with friends and perhaps making the application available for being downloaded, would be great to have.
As it ideally shouldn't have a big footprint on the system, I planned this undertaking to be done using Swift & Swift UI, the latter of which is relatively new.
With these choices made, I started what would become a long journey for what was anticipated to take a weekend, or two.
🙋🏼♂️ Getting into native application development
Never having built an Application in Apple's ecosystem before, I entered the first phase of the project full of ideas about what I wanted to build. I had set up my Apple Developer account, prepared an Xcode project, and was ready to get started. When you create your Xcode project, you'll get some pre-generated code for displaying a simple window filled by a SwiftUI content view. So far so good.
Since I played with SwiftUI for a bit some days before, I already knew that I needed to make heavy use of the system's menu bar, as I wouldn't be able to create fancy user interfaces by the time I desired to ship the project.
Through lengthy sessions, I learned the Swift basics I needed, as well as gaining some basic understanding of the way native macOS apps are built, grasping relationships between NSWindows, view controllers, NSMenus, and more.
Being able to move quickly, empowered by Xcode and AppCode (which I preferred due to it naturally being similar to GoLand, the IDE I primarily use), I drafted and discarded ways to make my menu bar application work. Early on, I saw, that, as with other huge ecosystems, there were seemingly infinite ways to accomplish what I wanted to do.
But there was a certain beauty in being able to simply build and run my application, as weird as it sounds. By archiving and using the Organizer built into Xcode, apps could be exported and eventually shared in the blink of an eye. The possibilities of everything except the actual code were endless.
👍 Advantages of following recommendations
Throughout the first sessions, I was ecstatic about the workflows offered by Xcode and the other developer tools. Everything from developing and previewing to building and shipping seemed already figured out. I wouldn't have to spend days on configuring build systems and convoluted toolchains, compared to some of the frontend projects I've experienced. This was a huge advantage I easily got, just by following the native path Apple provided.
🧐 Stepping back and reviewing progress
After the initial honeymoon phase of the project, I started to stumble over increasingly-exhausting barriers in the development process. Spending hours on the seemingly simple task of preventing the application from terminating after the last window was closed, for example, was fairly annoying.
It also dawned on me, that I wouldn't get past adding a few interfaces, mostly for the most simple tasks of displaying lists and input forms. And I also hadn't invested time in state management either, so, after multiple evenings of working on the project, I stepped back and thought about the fundamental pain points I was facing.
This is the part where I fully acknowledge, that most of the issues I faced, were due to me just getting started on a completely different field, compared to what I usually do (if you didn't know, I'm primarily a full-stack web engineer, enjoying the harmony of developing backend services in Go).
Nonetheless, with me being the sole developer working on this project for my personal amusement, I stopped regularly working on it based on the lack of such.
🤩 Switching to what I know
Doing a full turn, I checked out the Electron ecosystem a few days ago. Throughout the years, Electron has gained impressive adoption by lots of projects, most notably Slack, Figma, and VS Code. Having an average of ten applications I use daily built on Electron, I also had premonitions that Electron was the exact opposite of the lightweight foundation I wanted to build on.
Enormous application bundles and the memory consumption to expect from a fully-fledged instance of Chrome running to power the application, Electron has repeatedly come under fire by developers and angry users deprived of their system's RAM over the years. That said, I fully agree that we should rather strive for smaller binaries, less memory and compute consumption, and whatever else makes us happy with applications.
Note that I didn't say all applications should be native by design: If all performance characteristics are met for the app to feel snappy, why do we have hard feelings against the ability to build for multiple targets (web and desktop)?
All things aside, in this instance, I wanted to check how far I could get with Electron, especially whether I could solve the pain points I encountered when trying the native approach.
So I set up the most straightforward setup I could find: A frontend powered by Create React App, and an Electron backend using TypeScript. Out of the box, this gave me lots of breathing room: With great documentation available, Electron immediately felt approachable. A couple of years ago, I had last built something small with it, so I roughly knew the architecture with main and renderer processes, as well as the popular workflow of running the hot-reloadable React app in development versus loading the built bundle files in production.
No longer using Xcode, I lost quite a bit of developer tooling like icon set management (where you can manage icons in multiple sizes, targets, etc.), but more importantly, the ease of building and sharing my application with others.
When building a macOS app, you'll have to sign your code and notarize your packaged application, so other users can launch your application without getting warnings of your system not trusting the file you're executing.
Quickly I set up Electron Forge, which tries to abstract the cumbersome tasks away, leaving you with a minimal (but powerful) set of configuration options for packaging, signing, and releasing your application.
🤬 Obstacles of Electron app development
Learning while building once again led to me facing issues that fortunately proved solvable. From not being able to package the app, through packaging every. single. file. in my repository, over building successfully to then crash when opening because the code signing step had failed somehow, I got exposed to all layers Forge tried to hide. Notarization required me to create an application-specific password and some specific entitlements (the notorious app sandbox) led to the aforementioned behavior.
As I said earlier, this is the cost that you'll pay when leaving the carefully-crafted developer path Apple envisioned for native application development, but with a huge and active community behind the Electron ecosystem, even those problems can be resolved. It's still worth mentioning that I didn't go the hardest path, which would have been to distribute via the Mac App Store, which requires you to manage even more complexity.
🙂 Comparing both approaches
As with all subjective decisions, arguing for native apps versus the Electron approach boils down to personal preference. I really enjoyed diving into the Swift & SwiftUI world, but in the end, the cost of learning and figuring out the right way to build applications was too high, compared to building on top of Electron, using technologies I've in part been using daily for the last couple of years. What's more, the value of effective tooling also surfaces repeatedly throughout the product lifecycle, which makes some of the trouble setting everything up negligible.
It also seems like the discussion around Electron is hugely centered around the significant performance aspects I mentioned above, while downplaying the incredible velocity a fully-working setup enables. While native applications should result in superior performance or resource efficiency, for some projects it's just not worth investing the developer resources, especially if you're building personal projects or tools that will be used internally, for example. But if you've got a developer to build the greatest native app for your product, sure, go ahead.
Thank you for reading this post! Next up, I'll probably work on some resources for building Electron applications. If you've got any questions, suggestions, or feedback in general, don't hesitate to reach out on Twitter or by mail.