Useful patterns for building HTML tools
31 points by simonw
31 points by simonw
A lot of neat stuff on this list! One small addition is that if you're producing a bunch of files, the tar format is very simple to work with and you can easily create a tarball on the client. You can use a library like JSZip too, but tar is doable without a library.
Sidebar:
ffmpeg-crop lets you open and preview a video file in your browser, drag a crop box within it and then copy out the ffmpeg command needed to produce a cropped copy on your own machine.
I was expecting it to use ffmpeg.wasm to do the cropping for you!
Great article.
Personally, I usually build TUI apps for things like these and distribute them from personal homebrew cask.
I packaged all dev utilities into a single tui app https://github.com/skatkov/devtui.
CDNs like these have been around for long enough that I’ve grown to trust them, especially for URLs that include the package version.
The alternative to CDNs is to use npm and have a build step for your projects. I find this reduces my productivity at hacking on individual tools and makes it harder to self-host them.
I still don't trust other people's CDNs, for all the well trod reasons, though I don't mind using them for early exploratory programming. Once I've decided to build out a tool, I find that using vite to handle the build step removes very nearly all of the friction I associate with NPM during development.
And having gitlab or sourcehut CI run the bundler when I push to main, then publish to their "pages" host once it's done, reduces my involvement in publishing an update to git push. I'm sure github ci can do the same thing, but I've never personally set that up.
Thanks for publishing this. I learned a lot reading it.
I do have a strong preference for self-hosting assets in my "professional" projects, but the convenience has won me over for prototypes and smaller tools.
Maybe I could figure out a build process that rewrites CDN links to local copies but otherwise leaves the single page HTML files intact.
I am very confident that Claude could one-shot "write me a Python script which rewrites an HTML page to have all external non-relative scripts inlined". Opus 4.5 knows about inline script metadata so you can make the script itself standalone for anyone with uvx.
Obviously it is marginally more efficient to deduplicate the scripts between multiple tools instead of inlining a copy into each tool, but in practice it isn't really going to matter much, and there's a lot to be said for truly self-contained tools.
I ran a similar experiment with that here, haven't tried it for anything real yet though: https://github.com/simonw/research/tree/main/ast-grep-import-rewriter
i have a few of these out there too, i like the pattern...
https://aae42.github.io/markdown-table-to-json/ i have for turning markdown tables into gitlab json ones, i go back and forth as to whether the tables are actually better, but the tool is useful
This is great stuff. I'd suggest considering localStorage legacy tech though. Indexeddb for basic key/value stuff is only a few more lines of code, can store more complex objects than strings with less code, and can be accessed from every browser context safely.
You know what? I've never actually built anything with IndexedDB before. I got stuck on localStorage and didn't clock that IndexedDB has been fully supported across every browser that matters for many years at this point.
Time to learn it properly!
Why bother learning it when you're vibe coding your projects? (I know---harsh and quite cynical take here, but I find it hard to fathom you are actually learning the technology when LLMs are doing the heavy lifting, and I'm surprised that the LLMs you're using haven't already used it somewhere. Aren't LLMs there to remember this stuff so we don't have to?)
There's an important difference between learning concepts and memorizing syntax trivia.
Take this project for example: https://tools.simonwillison.net/octave-explainer
Things I learned:
I did not memorize the syntax for (new window.AudioContext()).createAnalyser() - though I'm not sure I would have memorized that if I had typed it by hand either.
Crucially, I learned all of this in a couple of minutes from a project I created on my phone to satisfy a moment of idle curiosity ("what happens if I tell Opus to build an interactive explainer for a musical concept") - and now I get to tuck all of that away in my forever-notes, and share it with anyone else who's interested too.
Times that by 150 more projects. I'm learning a huge amount from all of this.
If you want more examples of things I'm learning the linked article has dozens.
I'm not a big fan of LLM-assisted coding in itself, but I think it's uncharitable to OP to suggest he's not learning anything while vibe-coding. If anything, nicely summarizing two years of work and pointing out interesting tricks-of-the-trade is a type of learning. Like writing up code that you pair-programmed with another programmer in order to solidify your own understanding. Kind of like the Benjamin Franklin method maybe?
I do something similar, though in a much less organized fashion, shoving everything into a difficult to search lab folder (my fault, I give awful names to the subfolders).
I've been meaning to give a try to https://github.com/quests-org/quests which–as I understand–might give one an already-set-up playground for these types of small apps.
I'm planning a follow-up post where I describe the infrastructure I have setup around my simonw/tools repo to help with all of this - including the mechanism that powers the search box on tools.simonwillison.net and generates the descriptions for tools.simonwillison.net/colophon.