Using Elix to build a small and fast native-like PWA for mobile devices

I built Memoui, a fast, small-payload scratch pad application with data persistence and offline support using Elix web components and mixins. Thanks to service workers and seamless Android support for adding to the device's home screen, the application has a native feel and user experience, with the benefit of installing from a URL rather than an app store.

While working on a longer term PWA project, I wanted to deliver a more limited sample application so I could more quickly gain insights into the end-to-end patterns and pitfalls. What I ended up with is Memoui (/ˈmemwē/ 😉). This was incidentally a useful app for me, as I like having on my phone a means to write down or dictate something very quickly, without a care for network backup, accounts, etc. I’m also determined not to download and congest my device with native apps from the app stores. Memoui would be, and is, an app I’d actually use, no matter how simple it looks.

Beyond the functionality of the app, I had very specific learning and implementation objectives:

  • I wanted to leverage work I’d already begun on my larger project, making use of my existing build and server side patterns.
  • The front-end must be written almost exclusively with web components, and specifically with Elix elements and mixins.
  • The network experience is exclusively for download/install. No network connection is required thereafter except for updates.
  • Local device persistence of data. No accounts. No network backup. It’s a scratch pad, after all.

And, finally, I wanted to explore the subtleties involved in the full development lifecycle from conception to delivery as a proof of concept project.

Thinking about the project in these terms, it was striking to me how similar this was to the early development of native iOS and Android applications where so many apps were designed to be installed and run independent from network connectivity. We’re not really talking about PWAs in this world, where you care about scalability to desktop browsers, or progressive enhancement. The target is a mobile device, and the subject is an application, pure and simple. It's not necessarily PWA; it’s more WAP — web app, period.

The result was a functional application delivered to a device through a URL, with an initial download size of about 90 KB, and the actual client code contributing about 23 KB, compressed. The application runs fully offline where service workers are supported, and makes use of:

From a functional reactive programming perspective, Memoui follows the React pattern of components for UI interfaces, starting with componentization at the page level. Rather than incurring framework costs, Memoui takes advantage of its target device’s support for native web components. Everything of interest is a web component (modulo work I’d eventually do, server side, to eliminate even my minimal use of Preact for routing and master page framework). And all Memoui’s components are built with Elix.

Elix shines in that, even with its 1.0.0 release, it is a comprehensive library of valuable semantics, organized as mixins that work both synergistically with other mixins, or independently. Elix elements are mostly groupings of mixins. While the Elix code base is growing in size and sophistication, Memoui’s download size remains anchored only to what it needs from Elix. As an interesting comparison with building small Android APKs, read this great article by Charlie Marsh at Khan Academy.

I spent most of my time on the IndexedDB portion of Memoui, learning how best to organize the transaction-based persistence of its two datastores, and the data structure representing the tabbed (fixed) collection of notes and current tab state. If you look at the code, you’ll notice the strange name of the class managing the application state JSON, a result of my modeling Memoui against the similar needs of my more sophisticated app in progress whose UI centerpiece is an Elix SlidingCarousel rather than Tabs.

I should mention this learning point. I grew unhappy with the data structure names, but they were tied into the schema of Memoui’s IndexedDB stores. Since I already had a small set of users, making a naming change would require adding support for the IndexedDB onupgradeneeded event, capturing the current persisted data, creating new versions of the store(s), populating those with the saved data, and blowing away the old stores. While this will be necessary work in future projects, I didn’t care to do so for Memoui. It’s a lesson in careful planning.

No application, even the simplest, is ever done and this exercise opens the door to endless improvements and changes based on learned insights. For now, I’m satisfied both with the learning exercise and with having a minimal app on my phone that, due to its speed and simplicity, I actually find myself using every day.


« Blog home