Anyway, I've probably thought way too hard about this kind of stuff, and I may have micro-optimized things that only marginally impact the user experience. But Pinafore is kind of an opportunity for me to geek out and try to squeeze out every millisecond, so I definitely use that opportunity to its fullest.
Hopefully this thread is interesting, and it maybe gives you something to poke around with in your own web projects. π
In terms of resource usage, I'm also pretty careful in Pinafore to correctly handle browser events like passive/active, frozen/unfrozen, etc. PageLifecycle.js is a lifesaver for this: https://github.com/GoogleChromeLabs/page-lifecycle
These events basically correspond to things like Pinafore going into a background tab, or if the user has a lot of tabs open, then the browser may freeze/suspend it until a later time. In those cases I free up background timers, IndexedDB, WebSockets, etc.
Another really minor optimization: Sapper has a concept of "prefetch" links. Basically it means that when you hover over a link with your mouse, it can start fetching the next page the moment the "mouseover" event is received instead of waiting for the "click" event. I use this for same-origin links. https://sapper.svelte.dev/docs#prefetch_href
Pinafore uses Service Worker, so most everything is cached locally anyway, but this can still save some time on fetching the JS from the disk, parsing it, and compiling it.
Speaking of memory usage, I really use LRU caches all over the place in Pinafore. I find them to be a handy data structure. This small library by Sindre Sorhus is my go-to: https://github.com/sindresorhus/quick-lru
I use it to cache various things, like:
- most recent statuses, notifications, and accounts - blurhash blob URLs - the 10 most recent timelines the user has visited
My standard rule of thumb is: use IndexedDB as the default, keep recent stuff in-memory in an LRU.
The Tesseract.js library is also a large dependency (including a 3.2MB WASM file and an 11MB trained English language model), so I was careful to only load it when someone actually clicks the "extract text from image" button. (It's not cached by default in the Service Worker.)
Also, Tesseract uses a web worker, which avoids blocking the main thread but adds memory overhead. I set it to automatically terminate the worker after a few minutes of inactivity to save memory. https://github.com/nolanlawson/pinafore/pull/1449
With the new OCR feature, I had to be really careful with how the Tesseract.js library is used. After the user uploads an image, I might have passed the server-generated URL into Tesseract. But that has two problems:
1. Not all Mastodon admins have enabled CORS (cross-origin resource sharing) on images. 2. It requires you to download the image you just uploaded!
So I keep a cache of the 4 most recent image uploads, and pass it to Tesseract as a Blob URL.
When you first load Pinafore, there's a small inline script that does some quick setup work. For instance, if you've set a theme, then it starts downloading the CSS for that theme (which minimizes the flash of the default blue theme).
It also adds a "preconnect" tag to whatever instance you're logged in to. This way, it can start connecting to that instance faster, even before the page has fully loaded.
The JavaScript bundle is also heavily code-split. The total size of all JS is just over 1MB (minified but not gzipped), but the vast majority of that is lazy-loaded. When you land on the home page for the first time, you only download 110KB (35KB gzipped). On a typical logged-in view, you load 474KB (served from the Service Worker at that point).
First off, if you load Pinafore with JavaScript disabled, it actually shows the landing page. Most of the pages have their content statically available, although you get a "please enable JS" notice if you actually try to log in to an instance.
The point of this is not to work with JS disabled, but to show a quick HTML-only page while the JS is loading.
How do we build social spaces that leave more room for get-to-know-you? How can we reduce the prejudgement that comes from presenting a globally consistent face to the world, like individualistic social media does? How can we let people interactively vet the groups they're joining before they commit? What affordances do we need to understand community from the point of view of a new member?
Loved this interview with Feross Aboukhadijeh about open-source sustainability. Lots of things that I empathize and identify with here. https://changelog.com/podcast/359
@kepstin Ahhhh I see, that wasn't really clear to me. Thanks for the explanation! I wondered if they were testing on a really slow system or something. π
I'm also excited for a better performance profiler in Firefox Dev Tools. The lack of good performance tooling is one of the main reasons I still tend to use the Chrome Dev Tools for development (despite using Firefox for everything else). https://profiler.firefox.com/