I didn't expect to be pretty excited again about writing JavaScript, of all things, but here we are… πŸ˜›


It's been about a month and a half now since I started thinking again and doing research about rewriting Skythread from vanilla JS to some kind of framework:

Kuba Suder πŸ‡΅πŸ‡±πŸ‡ΊπŸ‡¦'s avatar
Kuba Suder πŸ‡΅πŸ‡±πŸ‡ΊπŸ‡¦
@mackuba.eu

Doing that periodical every-2-month pondering session where I start thinking if/how I should rewrite Skythread to make it more maintainable, with something less hand-written and more templated/reactive, but while touching as little of modern JavaScript/node ecosystem as possible because eww 🫠

I picked Svelte in the end and decided to rewrite the app in it. I'll write more about the reasons and my impressions later (spoiler: it's cool!), this is just a status update – after a bit over a month of coding and around 140 commits, I'm more or less finished with the rewrite.

You can see the final version of the code here on the svelte branch:

/skythread/src at svelte Β· mackuba.eu/skythread
Thread viewer for Bluesky
https://tangled.org/mackuba.eu/skythread/tree/svelte/src

I got rid of all of the code touching DOM modification APIs like createElement/appendChild/innerHTML etc., everything inside index.html <body>, and most of the style.css – all of the UI-touching code is now in *.svelte component files. I also converted all previous classic scripts to ESM modules, set up bundling (using Bun), and split various files into smaller ones (which was a bit inconvenient before without a build step). Finally, I also converted most of the code from types in JSDoc comments to full TypeScript; I found it a bit too annoying previously with all the DOM access API calls sprinkled everywhere, but with that all gone, it was much simpler now.

I haven't merged it all into master yet, because I want to test everything a bit more in practice; I've deployed this version to a separate address, skythread.mackuba.eu, while the stable version is still deployed at blue.mackuba.eu/skythread. I intend this to be the new official address, and at some point I'll make the old one redirect to the new domain.

I'm pretty happy with how it's all looking now, I think this puts me in a good place to add some new features and improvements to the app easier next year. For now I've already started playing with two things:

  • using Constellation APIs from the fantastic "Microcosm" ecosystem of projects by to look up "hidden replies", instead of my own backend – my "Bluefeeds" service cleans up older posts after about 40 days to save space, so replies older than that couldn't be loaded, but the "backlinks" API in Constellation can get me the same data (at-URIs of all known direct replies) without any time limits:

Kuba Suder πŸ‡΅πŸ‡±πŸ‡ΊπŸ‡¦'s avatar
Kuba Suder πŸ‡΅πŸ‡±πŸ‡ΊπŸ‡¦
@mackuba.eu

Heh, I managed to plug in @microcosm.blue Constellation into the "Hidden replies" loading code in Skythread instead of my (more limited) backend in like ~15 minutes of work 🫠 Really important plus of making XRPC-shaped APIs - very easy to use with existing XRPC client libraries!

Git diff:

try {
removed code:
      expectedReplyURIs = await blueAPI.getReplies(post.uri);
added code:
      let microcosm = new BlueskyAPI('constellation.microcosm.blue');
      let results = await microcosm.fetchAll('blue.microcosm.links.getBacklinks', {
        field: 'records',
        params: {
          subject: post.uri,
          source: 'app.bsky.feed.post:reply.parent.uri',
          limit: 25
        }
      });

      expectedReplyURIs = results.map((x: json) => {
        return `at://${x.did}/${x.collection}/${x.rkey}`;
      });

  • using the new Navigation web API, a modern replacement for the History API (pushState) which is just being added to latest versions of Safari & Firefox now, to add faster in-place switching between pages and (sub)threads without reloading the page:

Kuba Suder πŸ‡΅πŸ‡±πŸ‡ΊπŸ‡¦'s avatar
Kuba Suder πŸ‡΅πŸ‡±πŸ‡ΊπŸ‡¦
@mackuba.eu

Proof of concept :> (before / after)

Kuba Suder πŸ‡΅πŸ‡±πŸ‡ΊπŸ‡¦'s avatar
Kuba Suder πŸ‡΅πŸ‡±πŸ‡ΊπŸ‡¦
@mackuba.eu
Hmm… I was thinking about adding navigating through threads in-place in Skythread using the History API, but I think I will just do it using this and enable it optionally if available (for latest browsers) and just leave it as it is for older ones πŸ€”
Seb βš›οΈ ThisWeekInReact.com's avatar
Seb βš›οΈ ThisWeekInReact.com
@sebastienlorber.com
πŸ‘€ Navigation API will be available in all browsers soon πŸŽ‰

Completely redefines the APIs used to build client-side routers / SPAs

🫀 History API: popState, link click.preventDefault() ...

βœ… Navigation API: clean, centralized way to intercept navigation events πŸ‘Œ

A bunch of statistics from the rewrite:

  • JS bundle size:
    162 KB of unbundled original scripts + 1 dependency (40 KB .gz)
    β†’ 99 KB minified .js bundle pre-Svelte (29 KB .gz)
    β†’ 184 KB final bundle (63 KB .gz)

  • CSS size:
    23 KB original style.css
    β†’ 18 KB minified .css pre-Svelte
    β†’ 19 KB final .css bundle (generated from components)

  • my code size (HTML + JS + CSS):
    6.9k lines β†’ 7.1k lines (surprisingly minimal difference!)
    174 KB β†’ 176 KB
    16 files β†’ 78 files

Note: the 184 KB is from a production bundle made with Bun. This can be brought down to:

  • 179 KB using esbuild

  • or 170 KB with some hacks (it seems that neither Bun nor esbuild are currently able to strip the type of dead code paths for development mode that Svelte code includes)

  • or 156 KB using Vite w/ rollup or rolldown, which seem to be better at "tree shaking" the bundle