Tech Stack Overview: Homechart's TypeScript/Mithril Frontend
In part one of Homechart’s tech stack overview, we explored Homechart’s HTTP API server component. In part two, we’ll explore Homechart’s frontend architecture.
This component provides an interactive, easy to use user interface for performing actions against the HTTP API server component. By leveraging web technologies and packaging tools, we can deliver the same user interface on the web, desktop, Android, and iOS with ease as a Progressive Web Application (PWA).
- HTML describes the layout/content of the frontend
- CSS describes how the HTML should look
Yes, compared to the backend, the Homechart frontend has quite a bit of needless complexity related to the underlying technologies. And unlike Go, there aren’t a lot of guidelines for frontend codebases, especially with a more “freeform” framework like Mithril.
Below, we’ll review the following sections of the codebase:
All of the
- Stylelint - Reviews our CSS code for correctness.
- Vite - Takes our code and outputs the UI assets for the Homechart API server and app stores.
This folder contains code for the Homechart android app. The code is a wrapper around the Homechart frontend created by bubblewrap, which creates a Trusted Web Activity (TWA) from our PWA. Basically it’s a complicated process to get the Homechart app on the Google Play Store.
Unlike Google, Apple has stricter guidelines around apps and hasn’t fully embraced all of the web technologies for PWAs/TWAs. Instead of rewriting Homechart for iOS, we use another wrapper around the frontend code called Capacitor. The end result is quite similar to the TWA setup–another complicated process to get Homechart on the Apple App Store.
This folder contains the actual frontend codebase:
Here are the parts we’ll review:
Our build tool needs an “entrypoint” into the codebase:
index.ts combines the Mithril routes (
/calendar, etc) defined in
routes.ts and initializes the
Homechart leverages a Service Worker to handle background web tasks like Push Notifications and caching assets so Homechart can work offline.
Wherever possible, we try to reuse our code via Mithril components. Combined with TypeScript, they let us effortlessly reuse parts of our UI like a Button or a Table by exposing properties other components can set to change their behavior.
We embed most of our CSS definitions within each component (or rarely, a view). This lets us keep our CSS definitions tightly scoped and easily changeable.
To keep the styles consistent across components, we leverage CSS variables. These and some other “global” CSS styles live in the css folder.
CSS variables are extremely handy. Instead of having to declare all of the various media queries in each component, we declare them in the main variables.css file and use variables to change things:
This folder contains the main layout definitions for Homechart–
AppFooter, etc. All pages are “wrapped” with this layout, keeping the framing of the app consistent between views.
This folder centralizes various services Homechart uses:
One benefit of using Mithril is we aren’t bound by any specific state paradigm, so we can implement one that is tailor made for our application. Our current state setup involves having a class for each object type in Homechart (BudgetAccount, PlanTask, etc) that inherits from a base class (avoid duplication) and leverages Mithril Streams to make the data reactive.
The end result is a set of independent state files that can be updated by the user directly or in the background via SSE.
With all the data changes happening, we need a way to offload the operations so we don’t hang the UI. We leverage Web Workers for this.
Routes map URLs (
/settings/account) to views which combine components and state into screens for the user to interact with. With the help of component reusability, the views end up being fairly small and easy to read.
Web Workers are a way to perform things on a separate thread/in the background without impacting the UI. Due to the way data is passed between the main thread and a web worker, a lot of actions become difficult or costly to implement in a web worker paradigm, make sure you research/validate before you go down this path!
Homechart leverages Web Workers to perform specific actions around data operations and fetching data.
Instead of spinning up web workers on demand, Homechart leverages a set of long running Web Workers in a pool and submits tasks to them.
The workers inspect the task to determine what type of task it is, perform the task, and then send the result back to another handler in Homechart which figures out what to do with the result. This has been a good tradeoff for us in terms of complexity and performance, you may end up with a different solution.
We chose to use Mithril at the creation of Homechart due to the flexibility it provides, and have stuck with it even in the face of newer, more exciting frameworks like React, Vue, and Svelte for the same reason. For us, Mithril gives us just enough functionality without adding mental load to understand what it’s doing. We also strive to limit our dependencies and keep our code as simple as possible.
We hope you enjoyed this overview of Homechart’s frontend. In part three, we’ll checkout our tooling and CI/CD processes.