NantesJS #7016 Feb 2023
Yesterday I was at the NantesJS #70 meetup. I was one of the two speakers (sharing tips on how to write better tech blog posts), but also had the chance to see the first talk of the night.
The meetup took place at SII, and we were about 20 in the room (including organizers and speakers). Considering that it's post-COVID, a rainy day, vacation and strike day all at once, it's a pretty reasonable number.
The rest of this blog post will be a rewriting of my notes when listening to the first talk, for people that didn't get the chance to attend. She also wrote a detailed article on the topic and has a starter kit GitHub repository.
She develops apps for professional business, used by people on the field, to register information. Think about people working on fixing large machines, they need to take pictures of what is broken, which engine piece to buy, and move to the actual fixing.
Their job is not to fill information into an app; their job involves more manual labor, but they need to use their phone 10-20 times per day, to fill in some information.
More importantly, they often work in environment where Internet access is either not possible (underground for example) or shaky (on a moving train). In that context, it's important that the app reliably works offline.
Thankfully for her, she has the perfect lab to simulate those conditions. She has a boat, and can sail at sea where she has limited access to Internet. She actually also has low access to electricity, which also forces her to be careful about the battery drain of the apps she creates.
Her motto is clear: Keep the complexity low
She aimed at producing frugal software:
- She's using standard APIs, and no framework
- She doesn't want to have to build/compile anything
- She doesn't want more than two dependencies
In the interest of keeping the complexity low, she went the PWA route, so she doesn't have to deal with the slow AppStore and GooglePlay review processes and can update apps by pushing new content.
Considering that her users will use the app a dozen times a day, and never for more than a five minutes at a time, the UI must be clean. No need for fancy gradients or animations. Her users care about getting the job done, not how beautiful the app looks.
Another specific aspect of her apps is that they work as a "backend for one person", as she puts it. Every user is able to add/edit/delete items in a list, which will be synchronized online with the server once a connection is available. But no user has access to the list of items added by another user. The app works as a single-person point-of-view.
indexedDB as the database to store the items locally, and Service Workers to act as a fake proxy. Whenever the app is offline (the default state), data is stored in
indexedDB. Interaction with
indexedDB is still done through a fake-CRUD interface, with requests intercepted by the Service Worker.
When the app is online, the Service Worker stops intercepting the requests, and can send them directly to the backend server. It can also synchronize its local state data with the server. The server acts as the source of truth.
IndexedDB is an old API, so she uses idb as a wrapper API Client that allows her to interact with it using promises. Still, it's not SQL, so joining or sorting results is hard. This prompted her to think about the schema.
She creates one table per value she needs to store. So instead of having an
items table with and
image field, she would have
images tables that each store one kind of data (string, dates, blobs) sharing the same
She also has the Service Workers build the pages asynchronously in the background, before they are actually requested. When the user navigates to such a page, the Service Worker intercepts the request and serves the page from its cache.
To keep the cache fresh, Service Workers constantly refresh pages in the background when one of its constituent changes. For example whenever an item is being edited, the page listing all the items is being updated in the cache.
I find that this is an effective way to handle fresh cache; you regenerate the content on the "server" side whevener it needs to be updated without waiting for the client to make a request.
She also explained some of the hacks she had to put in place to circumvent quirks related to cross-browser compatibility. One was a specific issue with Safari on iOS not able to handle blob data coming from a FormData inside a Service Worker. Highly specific indeed.
Instead, she makes use of a
PUT request to her server, passing the binary blob data as the content, and all the other fields as
She also mentioned that Service Workers can be killed randomly whenever the browser thinks they are doing nothing. Which meant that sometimes when the Service Worker is waiting for a response from a server and that response takes a long time to arrive, it can get killed before it has had time to register that the data has been updated.
To avoid duplicating content on the backend by pushing again the same content, she first checks (using a
HEAD request) if the item she's about to synchronize has already been saved. This adds one more request, but increases stability (nobody wants to have to cleanup duplicated records).
Her whole philosophy of "Less code = More perf" works well, and I enjoyed the talk and the idea of building frugal software.
Not using a framework has clear advantages, has it allows you to directly interact with the core APIs, and not having to wait for their support to be embedded into the core of the framework.
Once again, always bet on standards.
Tags : #nantesjs
Want to add something ? Feel free to get in touch on Twitter : @pixelastic