Share options

Links to related pages

Essays about code. Essays about connection. Essays about context. Critiques of coding practices.

The Craft Code approach

A craft coding primer.

Time to read

, 2135 words, 3rd grade

Iʼve been refining my coding practices, and Iʼm very happy with how far Iʼve gotten with them. Farther than even I had hoped.

Zero client-side dependencies

A key goal of my Craft Code efforts is to reduce or eliminate the use of external dependencies in shipped code. The Craft Code site is an example of this. Other than a polyfill or two, it has no client-side dependencies not written by me.

Even I am surprised at how easy this turned out to be.

More than a decade now Iʼve advocated for reducing dependencies in web applications. From 2013 to 2014, I ran 12-week web development boot camps for General Assembly in London, Santa Monica, and Hong Kong. I pushed the learners hard to avoid dependencies whenever possible.

In those days, I taught Ruby on Rails. By Hong Kong, Iʼd abandoned RoR for JavaScript frameworks (Ember, Angular, Meteor). It was in London that I discovered a troubling tendency among the learners.

In short: they never met a RubyGem (dependency) that they didnʼt like. They would add gems for anything and everything.

I remember one gem in particular. It did one thing only: it added a single attribute to links. And I remember saying to the learner, “Come on! You couldnʼt add that attribute yourself when you code the link?”

The learnerʼs answer: “But why should I if the gem will do it for me?” That learner went far in web development, Iʼm guessing.

In short, I could not deter them. Why do anything if you can get a dependency to do it for you, am I right? Those folks, if any are still coding, must love the new  AI / LLM  tools.

Me? I like to code. And I want to know what Iʼm coding.

Vanilla code is beautiful code
Vanilla is a beautiful flower.

Pure vanilla

Recently, Iʼve been experimenting with a “back to vanilla” approach. Because I like having static types (and inferencing), I use TypeScript. It would sure be nice if the TC-39 committee got around to adding a decent, optional, static type system to JavaScript. Donʼt hold your breath. But TypeScript compiles to vanilla JS, so Iʼm OK with it for now.

As long as I write the TypeScript myself, it is the same as writing JavaScript. A direct mapping, right? But why not add a similar (but better) static type system to JavaScript itself? Just sayinʼ.

I have never liked any of the UI component libraries, especially MUI. Itʼs an atrocity. Sorry, but it is. I have another essay in the works to prove it.

True, I used Sass then LESS then Stylus and then finally PostCSS. But as CSS improved I found that I needed preprocessors less and less (pun intended). CSS properties made that final. And now with @supports and more, there is no going back.

Iʼve been a proponent of semantic HTML since, hmm, forever. The late Nineties to be exact. I was riding the XHTML train until WHATWG derailed it with HTML5 … sigh. I still have a bit of PTSD from those battles …

I have always written the most semantically-correct, accessible, and usable code that I could manage. WCAG AAA, baby. Or at least AA. Since 1997!

So HTML, CSS, and JS are no problem. But one thing that working in React, Svelte, SolidJS, etc. has done is to sell me on the component style of developing.

To get the benefits of a component architecture, I did investigate using the TypeScript compiler and JSX alone. No library or framework. But in the end I realized that I was rewriting SolidJS, so why bother?

The stars, my destination

A year or so ago, I started testing some of the newer frameworks to see if there was anything that I liked. I tried qwik, Marko, Stencil, Lit, and others.

Iʼd already built sites in Svelte and SolidJS and yes, I even tried 11ty but didnʼt care for it. I donʼt even like Markdown that much, although Iʼll use it where it makes sense, generally as MDX.

One framework did catch my eye. Unlike qwik, it didnʼt pollute my code with tons of cruft. 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 pretty much 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 is a very simple site.

I started with JAMStack served from S3. Then added a couple of Lambda serverless functions to handle the forms. I used SES for the contact form and MailerLite for the newsletter sign up. A bit of API Gateway and Route 53 for routing and we were good to go.

A side note: Wow! AWS is an enormous PITA. So overly-complex! And such terrible documentation! We have since switched to Vercel and I am much, much happier. Bye, Jeff! Hola, Guille!

The new site was very nice. But the inclusion of Solid kept bugging me. Shouldnʼt we be able to make a form using only Web APIs at this point? So I experimented a little.

I discovered that I could get the DOM to do the form validation for me. Better, I could easily intercept the DOM validation. Then I could provide my own using the DOMʼs FormValidity object.

Oh, my! Eight years of React had devastated my once-proficient grasp of user agent fundamentals. How embarrassing! Iʼve been playing catch up for a year or so now.

And why did I need to keep state in memory? The form tells me both the initial value and the current value. I can calculate which fields are dirty and which are not. CSS alone permits me to hide and show error messages. Do I need any JavaScript at all?

I wrote my own simple JS script to handle form validation and submission.

Goodbye, Solid.

And presto! Zero front-end dependencies! In short, all the code that ships is my code. The vanilla JavaScript is for progressive enhancement, no more. The forms work fine even if the user disables JavaScript.

Eliminating dependencies one by one

Years ago, I was a big fan of Ramda. I added Ramda to my sites first thing, even though it was a large dependency. And this was before tree shaking.

But then JavaScript kept improving, adding more and more methods. Unless I need lenses or transducers, I can get by with plain vanilla JS.

And I never used lenses or, heaven forbid, transducers because hello! Other devs? My coworkers would freak out. And yes, I speak from painful experience. Yet another reason I prefer solo projects: the only limitation is me.

I do favor composition. But it is easy to write simple, curried wrappers for the built-in methods such as map, reduce, filter, find, and sort. And I could also add a pipe or compose function.

Where workable, I write my own utilities leveraging JS methods. But in a composable, pure FP way, naturally. These days I have my own library. I import utility functions as I need them. Itʼs surprising how rarely I do.

You may ask, isnʼt your utility library a dependency?

Yes, it is. But it is my code, same as if it were in the app itself. I am not averse to code re-use. I am averse to depending on other peopleʼs code.

Hereʼs a simple example. Iʼm sure that I can improve it, but thatʼs the fun part. Over time, these utilities will get better and better. And I will control them all and know exactly how they work.

First pass at a curried pick function.
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
}

Here is a code example showing the TypeScript code for a curried pick function. The function has two parameters. The first is an array of type K where K extends keyof T. The second parameter is an object of type Partial T.

The function then creates an internal function and assigns it to the variable curried. This inner function takes a single parameter, namely, the object from which to pick. It then uses the reduce method to create a new object with only the keys passed in the call to pick and returns that.

Finally, the pick function returns conditionally. If an object was passed, then it calls the curried function, passes the object, and returns the response. If an object was not passed then it returns the curried closure function with the keys already set.

So, no more Ramda for me. But what about React (or Solid) and re-rendering?

The interactivity on most sites comes from one of two activities. The first is form validation/submission. I have already solved this. The second is UI interaction, such as accordions, drawers, etc. I can handle most of this with simple CSS. Maybe a bit of progressive enhancement.

Unless youʼre building complex online apps (e.g., a spreadsheet), what else is there?

The simple truth is that most of the state that we manage on the front end using complex state management libraries is actually server state. We push it all up to the browser because we are running a client-side, typically single-page app. The state we need to maintain on the client is usually minimal. And we can keep it in the DOM itself. No, really.

With CSS pseudo-classes such as :target and :checked, I can do almost anything I need to do. I add a bit of JS to smooth things out. Or to remember settings, for which session storage is wonderful. But everything works without JS. Even the forms.

Recently, I wrote a dark mode switch using a checkbox and some CSS. No JavaScript at all. I use session storage to remember the setting between pages, but it is not essential — it is a progressive enhancement. Of course, the site is already responding to your systemʼs dark mode settings. This is for when you want to override them for this site.

Ironically, Iʼve now decided that this switch is overkill. It complicates the code unnecessarily. Your system already allows you to switch the site between light and dark mode. Do we really need a switch for this site alone?

But the site owner loves it, so Iʼm having a hard time convincing her to abandon it. Murder your darlings, I guess.

The forms submit to serverless functions. I use one per form. The functions look to see if the submission was via HTTP or AJAX. If the former, it redirects. Yes, this means I need extra pages for each error and for success, but thatʼs no biggie.

With JavaScript enabled, the submission is via AJAX and the server responds with JSON. The page then updates using DOM methods such as document.createElement. Easy peasy.

And it all works perfectly! I built Craft Code using this approach.

We donʼt need no stinkinʼ optimization

Oh, but wonʼt that be SLOW?

Um, no. Itʼs rather fast. Click on a few links here if you donʼt believe me. I am building all my websites using this vanilla HTML/CSS/JS approach. I use Astro and TypeScript on the back end, but ship only vanilla code that I wrote by hand.

And not only do these vanilla sites achieve AAA on the WCAG 2.2 guidelines. Note: thatʼs TRIPLE-A, not double. They also score 100% across the board consistently on Lighthouse. Thatʼs 100% on Performance, Accessibility, Best Practices, and SEO. Even on mobile.

I keep trying to improve that, but 100% seems to be some sort of limit.

When people visit these pages they usually remark immediately, Oh, wow. Thatʼs fast! Iʼve had no complaints about the interface or the forms. OK, the occasional snarky one about my choices of typefaces and colors. But hey! Iʼve never pretended 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 use cloud services for hosting, serverless functions, image manipulation, and more. I like to sit out on the edge, although I do worry about sustainability.

Currently, Iʼm using Vercel for hosting and I am pretty happy with it. Seems they care about sustainability, as do I. Thanks, Guille. Keep up the good work.

Right now, itʼs all JAMStack. But Iʼm investigating the use of tiny servers at the “edge”. My goal is to work around a few of the pain points of making the site work without JavaScript. Iʼm looking at you, content security policies.

I donʼt see any point in loading gobs of extra code written by strangers. People I donʼt know and who donʼt know me. People who have zero stake in my projects. People who may or may not write sustainable, secure, robust code. People who may or may not maintain it.

And why borrow code to do what the browser is already capable of doing? The browser is faster and better. Itʼs native code, after all.

Ergo, I resist adding dependencies unless it is something the browser doesnʼt provide and I cannot do myself.

But donʼt take my word for it. See what the Web Sustainability Guidelines have to say about dependency management.

Links to related pages

Essays about code. Essays about connection. Essays about context. Critiques of coding practices.

Get notified form

Get notified of site updates
Button bar

Carbon emissions for this page

Cleaner than 98% of pages tested
0.020g on first visit; then on return visits 0.010g
QR Code

Scan this code to open this page on another device.