I figured it might be nice to take a break from the doom and gloom to talk about something positive for a change, such as how I’m singlehandedly reinventing the Internet!
Ha. If only. But I have been refining my coding process and I’m very happy with how far I’ve gotten with it. If you’re here for the doom and gloom, don’t worry. It will be back shortly.
No dependencies
Perhaps the number one goal of my “craft code” efforts is to reduce or eliminate the use of dependencies, at least in the shipped code.
I’ve been advocating for reducing dependencies in web applications going way back more than a decade to when I was running 12-week web development “boot camps” for General Assembly in London, L.A., and Hong Kong.
Back in those days it was Ruby on Rails, although by Hong Kong I’d largely abandoned it for JS frameworks (Ember, Angular, Meteor). But it was during my first class in London that I discovered a troubling tendency among my students.
In short: they never met a “gem” they didn’t like. They would add gems (small dependencies) for everything and anything. I remember one gem in particular that did one thing: it added an attribute to links. And I remember saying to the student, “Seriously? You couldn’t just add that attribute yourself when you add the link?”
(Answer: But why should I if the gem will do it for me? This student went far in programming, I’m guessing.)
In short, they were undeterred. Why do anything if you can get a dependency to do it for you? Those folks, if they’re still coding, must just love the new AI/LLM tools.
Me? I like to code.
Pure vanilla
For some time now I’ve been experimenting with a “back to vanilla” approach. I like having static types (and inferencing), so until the TC-39 committee finally get off their asses and add a decent (optional) static type system to JavaScript, I’ll probably stick with TypeScript. But it compiles to vanilla JS, so no problem there.
As long as I write the TypeScript, it’s effectively my JavaScript. A direct mapping. I heard today about a way to use JS and still get type checking using a bit of TS and JS-Doc. Hmm. Might try that, but can’t we just add a similar (but better) system to JS itself?
I have never liked any of the UI component libraries, especially MUI. While I used Sass or LESS or Stylus (and then PostCSS) back in the day, as CSS improved I found that I needed them less and less (pun intended). CSS properties made that final. And now with @supports
, @layer
, and more, there is no going back.
I’ve been a proponent of semantic HTML since forever. The late Nineties to be exact. I was riding the XHTML train until WHAT-WG derailed it with HTML5, the bastards. But I have always written the most semantically-correct, accessible, and usable code I could manage. AAA, baby!
So those—HTML, CSS, and JS—were no problem. But one thing that working in React, Svelte, SolidJS, etc. has done is to sell me on the component style of developing.
I did look into using the TypeScript compiler and JSX to get the benefits of components without having to use a front-end library, but in the end I realized that I was just rewriting Solid, so why bother?
Enter the Titan
A year or so ago, I started testing out some of the newer frameworks to see if there was anything I liked: Qwik, Marko, Stencil, Lit, and others. I’d already built sites in Svelte and SolidJS and yes, I’ve tried 11ty and didn’t really care for it. I don’t even like Markdown that much, although I’ll use it where it makes sense.
But one framework caught my eye. Unlike Qwik, it didn’t pollute my code with tons of gibberish. And unlike many others, it didn’t force me to choose the one true way. That framework was Astro with its “islands” architecture.
At this point I had largely given up on pure vanilla JS. Too painful, I thought.
So I built a site for my partner in Astro and I used islands of SolidJS for the forms. It was a very simple site: JAMStack served from S3, a couple of Lambdas to handle the forms, tied to SES for the contact form and MailerLite for the newsletter sign up. A bit of API Gateway and Route 53 and we were good to go.
But that Solid kept bugging me. Shouldn’t we be able to make a form using only Web APIs at this point? So I did a bit of experimenting and discovered that I could get the DOM to do the form validation for me, and I could easily intercept it to provide my own validation messages if I wanted to.
Wow. Eight years of mostly React had really dulled my understanding of the fundamentals of the user agent. How embarrassing.
And what did I need separate state for? The form told me the initial value, if any, and the current value, so I could easily determine which fields were dirty and which were not. CSS alone hides and shows error messages.
I wrote a simple JS script (loaded via a script tag using Astro) to handle validation and submission, and dropped the Solid completely. Presto! Zero front-end dependencies. In short, all the code that ships is my code.
Eliminating dependencies one by one
Back in the day, I was a big fan of Ramda. It was the first dependency I added to my React sites after React. But then JS kept improving, adding more and more methods. Unless I needed lenses or transducers, I could probably get by with plain JS.
And I never used lenses or, heave forbid, transducers because hello! Other devs? My coworkers would freak the fuck out. One reason I prefer solo projects: the only limitation is me.
I do like composition, but it was no problem to write simple wrappers, curried, for the built-in methods such as map
, reduce
, filter
, find
, and sort
. And I could easily add a pipe
or compose
function.
So I wrote my own tiny utilities library leveraging the JS methods where possible, but in a fully composable, pure FP way. Now I just copy in utility functions as I need them. It’s surprising how rarely I do.
Here’s a simple example. I’m sure it can be improved, but that’s the fun part. Over time, these will just get better and better.
export type CurriedPick<T> = (o: Partial<T>) => Partial<T>
export default function pick<K extends keyof T, T>(
keys: Array<K>,
obj?: Partial<T>,
): Partial<T> | CurriedPick<T> {
const curried = function(o: Partial<T>) {
return keys.reduce((out, key) => ({
...out,
...(Object.hasOwn(o, key) ? { [key]: o[key] } : {}),
}), {})
}
return obj ? curried(obj) : curried
}
[I plan to abandon Substack soon and one of the reasons is no syntax highlighting. WTF, Substack?]
So no more Ramda, although they may be dead anyway. They still haven’t released version 1.0, and it has been many years now.
But what about React (or Solid) and re-rendering?
I find that most of the interactivity (read: behavior) on my sites is either form validation/submission (already solved), or UI interaction such as accordions, drawers, etc. Unless you’re building online games or something like spreadsheets, what else is there?
And I’ve discovered that with the CSS pseudo-classes such as :target and :checked, I can do almost anything with just CSS. I do add a bit of JS occasionally to smooth things out (or to remember settings, for which session storage is great), but everything works without JS. Even the forms.
I just wrote a dark mode switch using a checkbox and some CSS. No JavaScript at all (although if JS is enabled, it uses sessions storage to remember your setting between pages). Of course, it is already responding to your system settings. This is only for use if you want to override them on just my site.
Each form uses two Lambda functions: one for regular (HTTP) form submission, the other for submission via AJAX. The former redirects to separate HTML pages for errors, etc. (not perfect, but the validation works on the browser without JS). The latter re-renders using… wait for it… document.createElement
.
Hey, it works just fine!
We don’t need no stinkin’ optimization
Oh, but isn’t that SLOW?
Um, no. It’s rather fast. In fact all of my current websites (most still in dev, but near completion) are built in this “vanilla HTML/CSS/JS” way using Astro and TypeScript on the back end, but shipping only vanilla code that I wrote to the front end.
And not only do they rate AAA on the WCAG 2.2 guidelines. (Yes, TRIPLE-A, not double.) They also score 100% across the board consistently on Lighthouse: for performance, accessibility, best practices, and SEO, even on Mobile.
I keep trying to improve that, but 100% seems to be the limit.
When people visit those pages they usually remark immediately, “Oh, that’s fast.” I’ve had no complaints about the interface or the forms (but some snarky ones about my choices of typefaces and colors—hey, I’ve never claimed to be a graphic designer).
Is it vanilla on the back end, too? I don’t even know what that would mean, but I’m not stupid. I’m perfectly happy to use cloud services for hosting, serverless functions, image manipulation, etc. Though I’m trying to get away from AWS, at least directly, and use Netlify or Vercel or equivalent.
It’s JAMStack, for now, but I’m investigating the use of tiny servers at the edge to work around a few of the pain points of making the site work without JS.
I just don’t see any point in loading gobs of extra code written by people who I don’t know and who don’t know me to do things that the browser is already capable of doing just fine. Probably faster and better. It’s native code, after all.