In short: I want to build low-road applications for myself. They will have a very narrow function. I may use them for as little as a week or so, finish the task at hand, and move on to the next thing. Think about it like Simon Willison’s work using Claude to generate tools.
This feels like it demands something besides my usual full-stack web app (i.e., Rails) approach1:
- I would like to launch these apps instantly and operate on local data.
- In particular, files and folders; not database rows.
- I would rather not dig up a folder or terminal to run these applications; they should launch like I’d run anything else (i.e., via Raycast).
Tauri seems like it fits the bill here. It wraps a native web view in a small platform-specific host application. The “host” application is a Rust program that wraps a platform-specific web view, e.g., Safari on macOS. You can write native code in Rust, web/front-end code in HTML/CSS/JS, and easily call functions between them. In other words, a better Electron.
Strengths become weaknesses. In theory, “JavaScript everywhere” is a lovely concept. One language across the web, backend services, command-line scripts, embedded systems, even desktop applications. In practice: it’s a thousand cuts, especially if you’re trying to avoid accepting the whole JS build system/framework ecosystem into your life. Which I was, in this case.
It’d be extremely great to have Deno save the day here. Write backend/host-native or front-end code in JS and very little need to drop to Rust. In practice, it doesn’t seem like anyone is doing that. 🤷🏻♂️
What I want to build for myself is a gizmo for re-categorizing my old blog posts. These are all hosted on Micro.blog as of this year. Because of idiosyncrasies in the Micro.blog API, interacting with all posts seems to require using XML-RPC. The REST+JSON APIs don’t provide access to more than one page of posts, so they’re unsuited to working with one’s entire blog archive2.
Doing this from JavaScript, running in the web view, is where I ran into troubles. All the libraries I found assumed the code is hosted in Node, which maybe would have been fine. Node isn’t drastically different from browsers in this regard, if I understand correctly. Except that loading the actual library used the Node require
mechanism instead of the browser-based import
mechanism. And, it’s been a minute since I cared about the details of code loading in JS (did I ever really care?). It wasn’t really the problem I wanted to solve, so I “just tried things”, despite this being one of my pet peeves about how intermediate-level developers work.
Reader: I was not feeling like a badass user at this point.
I wanted to write some tests with Jest. But, Jest deeply assumes you’re using some kind of code transformation (webpack, Babel, etc.) scheme. Which I was trying pretty hard to avoid. I did get a very basic test working, but I didn’t feel good about it.
I might consider using QUnit and live with in-browser tests if I choose “no build step” as a tent pole principle on a real project.
Long story short, I did manage to get an XML-RPC call working from a test in Jest. I never tried integrating it into the web-view code. I fought the module system enough for the week. Maybe if I revisit this approach, I’ll get my story straight here. 🤷🏻♂️
My next foray was attempting to write the networking calls to Micro.blog in Rust. I’ve previously struggled with Rust in the form of extended arguments with the compiler. I like the idea of the language, type system, and lifetime/borrowing scheme. But taken together, I haven’t yet reached a place of confidence working with it. My last serious attempt at using Rust, several years ago, was stymied by a combination of generics, number towers, and getting lifetimes right.
I did not run into those particular challenges this time around. Armed with an LLM, it feels better than several years ago! I didn’t spend the whole time arguing with the compiler. With Claude 3.5 Sonnet (IIRC), I was able to get a rudimentary XML-RPC call working.
I’m in favor of writing the backend/host-executed stuff in Rust. The jury’s still out on whether this is a plausible approach. I’ve never argued with the Haskell compiler about, e.g., JSON and walked away with a successful compilation.
pico.css
is perfect for my needs. I included it with a link tag and mostly used classless HTML tags. That’s about it. No scheme of semantic, functional, or utility class names to learn. No build step. It’s good enough that looks aren’t the weakest link or distracting. That’s a good tool.
Similarly, I tried using Alpine to layer some interaction logic into the UI. I got hung up on declaring a data structure and then using it from any old element. I think this was a load order issue or misunderstanding on my part. Instead, I should have tried starting with basic interactions and then gone for the fancy stuff.
The verdict, after a few hours of hacking:
- Tauri has a lot of promise for problems shaped like “I want to build a quasi-native desktop app, but I would rather not get caught up in native ecosystems”.
- Rust, in combination with LLM copilots, is easier to navigate than it was without them and I spent less time arguing with the compiler than I’d feared I would.
- Small, low-customization CSS libraries: they’re good for me!
-
Previously: I learn new tricks. ↩︎
-
I’ve explored this a few times and come up with the same answer. I’d love to be wrong about this! ↩︎