Get cozy at work
Building an opinionated browser is hard. More than three years ago, when the team started the development, we chose Electron as a foundation for our application. Out of the box, the framework embeds Chromium and NodeJS runtimes to write cross-platform desktop applications. The best known Electron-based applications include Visual Studio Code, Slack, Facebook Messenger Desktop, Abstract, and many more.
In 2017, after a proof of concept written in one week, Station was born to build the workstation of the future. Electron was our engine to create a new user experience for the SaaS centric productivity era.
We tackled and nailed unpredictable challenges all these years: performance, user interface development, and minimal browser prerequisites for appointing them. Let's dig in!
Station 1.0 Global Architecture
💡 Before starting, a quick hint concerning the Electron architecture: all application interactions run in a process called "main" or "browser process" while the rendered user interface runs in another process, the "renderer process". An architecture based on the Chromium model process. These processes can communicate through IPC. In fact, in the case of Station, we have the main process, the user interface process, and many more processes that render hosted web applications.
After the proof of concept, we started to turn our vision into product innovations. Like all iterative software products with many technical unknowns, we knew we were going to have unforeseen difficulties.
Our technical architecture relies on redux and redux-saga (and even redux-thunk at the beginning) acting like an application state and business logic handler in the browser process. In the renderer process, React was our user interface drawer with a copy of the Redux state in memory, forwarded via IPC, and used by the react-redux connector. It was a relatively simple and straightforward technical stack and code.
During the following months, Station started to show new ergonomics, interface, and experience, differentiating itself from traditional browsers, with their URL bar and endless stacked tabs. We were on the right track.
Adding features has a cost. Known in advance or discovered in the product lifetime, controlled or not. Effects can be technical debt, product debt, or decreased performance, among other things.
The cost can be controlled with a budget, like having four staff-days to deliver a feature. It's part of product management. Software performance also needs a budget: application bundle size, interaction time, memory, and CPU usage, to name a few. For an early-stage software company looking for product-market fit, these budgets are not always defined from day one since the product and his inherent code moves fast without a focus on the subject.
In the case of Station, developers' and users' feedback regarding the feeling is our first quality metric for performance. One of the primary reasons for churn was about performance even though other users were happy with the performance...
We dedicated a lot of effort to find why and how to get an application running at 60 frames per second without compromising our existing and future features capabilities.
At this time, we started to use Webpack as a module bundler. With a large codebase like our own and many libraries, from SQLite to electron-spellchecker, tree shaking, and code splitting have significantly reduced the loading time and bundle size. Our first win.
At the same time, in our foundation, we discovered that our main process was under pressure all the time with CPU spikes, and sometimes an unresponsive application state. This bottleneck will kill the product and the company if we ignore it.
Most of our business logic has been implemented with redux-saga in the main process, sometimes executing heavy computation. Under the hood, we bring a lot of homemade differential technology. Some of them include a tab discarding algorithm to save battery life, an URL router with tab deduplication, and a proxy system to persist our Immutable.js application state into an SQLite database.
Meanwhile, our principal renderer process and all web views processes (hosted web applications) used IPC to talk with the business logic about their states and interactions.
Application IPC Graph
Moreover, the main process, in the architecture of Electron, houses the UI thread and is the receiver of all interactions between our application and the operating system. We bloated it, and we needed an alternative.
We introduced a new heartbeat: the Station Worker.
Responsibility partition for the processes architecture
With the worker process-based architecture, we moved almost all code from the main process to the worker.
The worker, a simple renderer process, is now in charge of handling all the computation while the main process is a control tower that orchestrates the children's processes.
The problem raised with this implementation was the communication between processes without having to go through the main process.
We wrote our own solution, which is partially open-source. We decided to wire all processes with a bi-directional RPC based on Node streams carried over the Electron's asynchronous IPC transport layer. With this backbone, all processes act as nodes and don't need to have an operational computation in the main browser.
With the main process unloaded, the overall user experience has become smooth with a real contrast in users' feedback.
In our methodology to identify and use the right solution, we wrote a Proof Of Concept of the application. The validators of the right path were numbers from the telemetry. After we finished the implementation, we added, behind a flag, visual indicators in the application's user interface acting like traffic lights. We set up two indicators, the first for CPU spikes (CPU >85% for 10 seconds) and second for CPU latent consumption (CPU >20% for 1 minute). The data is retrieved with an open-sourced library we wrote: electron-process-reporter. From our prerelease distribution usage, we could quickly know which prerelease would broke our performance budget.
Moving off the main process allowed us to achieve our objective and scale our product without compromising speed.
The prerequisite is now handled and transformed into a value proposition.
Our high coupling between interfaces and business logic was a critical pinpoint identified earlier. It slowed us down in our ability to iterate with user-facing features quickly. Our in house product designer and front-end web engineer couldn't move forward without the support of back-end software engineering.
The front-end React-based web application was connected to the business logic with the react-redux library reading a Redux state copy from the main process. From a developer experience point of view, writing interface components was a pain since restarting the whole stack was necessary to visualize changes, among other things.
With the adoption of an in-progress design system and the new worker-based architecture starting to look like a standard Web application, a new option has emerged: develop the user interface based on React with a reactive GraphQL data layer.
In one go, we have been able to remove the coupling between the interface and business logic, remove the necessity to copy the state in the rendering process, and UI engineers can now use their standalone tools without friction to live edit components in Storybook.
The funniest part of this improvement is the capacity to build the Station user interface in Station since Storybook is available as a hosted web application in our platform.
Electron is born to make cross-platform desktop applications with web technologies backed by the Chromium rendering engine and Node.js runtime instead of using lower-level technologies for each operating system.
We will see how a Web platform, its historical browsers, including usage adoption and user expectations, are hard to align with this in mind.
First, the Web platform is a set of standards developed and improved for more than 20 years. Each browser vendor has to implement these standards to be compliant. This will ensure that everyone can access your favorite web applications and leverage the browser's capabilities. Over the last few years, Google led efforts to make the Web platform more powerful, adding many new APIs from the adoption in standards to implementation in its browser.
Electron is packaged with a fork of Chromium, and until last year, there was a gap between the available Chrome version and the one in Electron. Under the hood, there was a lot of instability between one version and another, with many changes from Google not tested in Electron's integration. This seriously affected our users who found themselves with banners to update their browser but also with connection modal windows that did not want to automatically close. These are just a few examples from a long list. It took us a long time to fix the glue with crappy hacks to make it work.
Web platforms evolve fast, and it's hard without a lot of resources dedicated to keeping your browser up to date.
Another story is why there are no more browsers on the market?
When Station launched publicly, the product was well-received, but we faced major setbacks later. Our approach was new and bold, all existing browsers and their common form factor (about UI and features) were far from us. When we launched, we didn't have a print option, no more Google Cast tool. The URL bar, back and forth buttons, history & downloads management pages, and extensions support were not considered.
The most requested features were about implementing features inherited from the traditional browser, from corresponding keyboard shortcuts to Chrome extensions which were at the top of the list. The Chrome Extension platform is not standard, but millions of users use them daily from password managers to writing assistants.
After getting this feedback, we prioritized it as a prerequisite and started to implement the necessary APIs to support Chrome Extensions. It was a long and not always successful path to give Chrome extensions all the required capacity with our hosted web applications since we have designed our browser in a different form factor.
While the acquired technical expertise about making a browser on Electron made us proud, our velocity for shipping user-facing features slowed down significantly. Engaging significant investments for plumbing the gap between our use of Electron and established browsers without the insurance of return on investment was too high to stay in this position.
Aside from our story, new market entrants like Brave and Wavebox started on Electron and moved on Chromium, learning from the many setbacks of the framework.
Since the beginning of the year, we have been working hard on a new approach from a product perspective to answer our mission: ease your work life. The technical cost wasn't worth the trouble in order to achieve our goal. Some technical challenges can yield great value for users, but sometimes there are just that... technical challenges.
In the next coming post, we will look at our newly created core product infrastructure that will change your working environment's entire backbone.
Station in and out.