Cache busting in Jekyll revisited

I never quite warmed up to Gulp. It was yet another tool I had to learn in order to get stuff done. Before Gulp there was Grunt, and somewhere along the way I had to cope with Webpack in React projects. The latter was surely fine for those projects, but for my own needs, it was way overkill or not even the right tool for my needs.

I then came upon two blog posts that piqued my interest; Why I Left Gulp and Grunt for npm Scripts and How to Use npm as a Build Tool. I felt inspired and got to work.

npm scripting – the why

Cutting down on stuff is a favorite pasttime of mine. If I can, I minimise and optimise as much as I can, both in code and in real life. So what these blog posts were about resonated quite well with me. For the same reason that I dislike CSS preprocessors like Sass and Less because they add more problems than they claim to solve (which I don’t believe they do anyway), I disliked Gulp because it also was an abstraction layer that I felt added more problems than it solved for me. And the plugins. Oh, all those damn Gulp plugins that I had to use for everything. Ugh.

npm scripting – the what

Getting rid of Gulp means that you try to rely just on npm scripts in package.json instead and that in turn means to mostly rely on CLI versions of different tools. Step one is to identify what I need to happen in my tool stack:

  1. Transpile custom properies in my CSS for better backwards compatibility for legacy browsers
  2. Mash together what little JavaScript I use from several files into one
  3. Generate an SVG sprite
  4. Run Jekyll
  5. Lint my CSS and JavaScript
  6. Deploy code on my live server

It doesn’t take long to find the packages that we need over at npmjs.com. This is what I like about this approach. My package.json only has 12 dependencies since I dropped Gulp. Twelve.

I could almost cry from happiness.

Anyway, here’s what we’ve got:

"devDependencies": {
  "concurrently": "^4.1.0",
  "eslint": "^5.12.1",
  "foreach-cli": "^1.8.1",
  "hashmark": "^5.0.0",
  "onchange": "^5.2.0",
  "postcss-cli": "^6.1.1",
  "postcss-custom-properties": "^8.0.9",
  "stylelint": "^9.10.1",
  "svg-sprite": "^1.3.7",
  "uglify-es": "^3.0.28",
  "uglifycss": "^0.0.29",
  "yarn": "^1.5.1"
}

Let’s quickly go over what each packages does:

  • browserify: mashes all my JavaScript together into one unholy file. You could also use something like requireJS, if you like.
  • eslint: JavaScript linting (optional, of course)
  • foreach-cli: I use this to iterate over files to do things to them. More on this later.
  • hashmark: My cache busting tool. I’ll use this to generate uniqe file names.
  • onchange: Will help me trigger stuff if a change is detected.
  • concurrently: Runs shell commands in parallel and is a bit more versatile than just using & in the shell straight up. Added bonus is the improved compatibility with Windows.
  • postcss-cli: The command line implementation of PostCSS.
  • postcss-custom-properties: Plugin for transpiling a fallback value for CSS custom properties for legacy browsers.
  • stylelint: CSS linting (like eslint, it’s optional)
  • svg-sprite: Builds handy little SVG sprites for me.
  • uglify-es: JavaScript compression.
  • uglifycss: Same as above, but for CSS.
  • yarn: You might recognise this one.

Anyway, that’s what I use. You may use whatever tools you want to get the job done.

npm scripting – the how

To tie everything together, I need to create a couple of tasks in my package.json that will help me get my development environment going again, this time without Gulp. At this point, I assume that you already know how to write stuff in you own package.json. I’m also assuming that you’ve already read the two blog posts that I linked to in the beginning of this post.

"scripts": {
  "start": "concurrently 'yarn run build:watch' 'yarn run jekyll:serve'",
  "prebuild": "touch _includes/sprite.svg & mkdir -p dist",
  "build": "yarn run build:css && yarn run build:js && yarn run build:svg",
  "build:watch": "onchange ./src/** -i -- yarn run build",
  "prebuild:css": "rm -rf ./dist/css/*",
  "build:css": "postcss -c postcss.config.js ./src/css/*.css -d dist/css",
  "build:js": "rsync --checksum --recursive --delete src/js/ ./dist/js",
  "postbuild:js": "hashmark -r -l 8 dist/js/vendor/require.min.js 'dist/js/vendor/{name}-{hash}.js'",
  "build:svg": "svg-sprite -C svg-sprite.config.json --dest ./_includes src/svg/*.svg",
  "deploy:css": "postcss -c postcss.config.live.js ./src/css/*.css -d dist/css",
  "css:uglify": "foreach -g 'dist/css/*.css' -x 'uglifycss #{path} --output #{path}'",
  "css:hash": "hashmark -r -l 8 dist/css/*.css 'dist/css/{name}-{hash}.css'",
  "postdeploy:css": "yarn run css:uglify && yarn run css:hash",
  "deploy:js": "yarn run build:js",
  "lint": "yarn run lint:css && yarn run lint:js",
  "lint:css": "stylelint --color -f verbose src/css/**/*.css",
  "lint:js": "eslint src/js/**/*.js",
  "jekyll:serve": "sleep 1; jekyll serve --incremental --drafts",
  "test": "yarn run lint",
  "dist:clean": "mkdir -p ./dist && rm -rf ./dist/*",
  "predeploy": "yarn run dist:clean",
  "deploy": "yarn run deploy:css && yarn run deploy:js && yarn run build:svg"
}

The scripts section does grow somewhat when you’re not using Gulp anymore. Even if JSON has its shortcomings, like the lack of commenting — if you keep the naming as descriptive as possible, you should be fine.

A quick word on the pre and post hooks. As you can see in the above code snippet, there’s a few entries with the prefixes pre and post, like prebuild and postdeploy:css. This is a neat feature of npm wherein any script that has either a post or a pre hook, will automatically run either before or after that script. The above linked post by Keith Cirkel does a much better job than me in explaining the intricacies of these hooks.

CSS in particular

For the sake of brevity, let’s focus on the CSS part.

"build:css": "postcss -c postcss.config.js ./src/css/*.css -d dist/css",

First, I’m using PostCSS to transpile any custom properties to provide fallback properties for legacy browsers. In effect, this means that the following:

:root {
  --text-color: #333;
}

body {
  color: var(--text-color);
}

Will be transpiled into:

:root {
  --text-color: #333;
}

body {
  color: #333;
  color: var(--text-color);
}

Due to the wonderful way that CSS is progressively enhanced, any browser that does not understand a property will simply ignore it and move on. This mean that the occurrence of var(--text-color) will for example be ignored by Internet Explorer 11 and the previous value (#333) will still apply since it was declared before.

The following two lines are only run when I want to deploy to production, and this is also where the fun happens in terms of cache busting.

"css:uglify": "foreach -g 'dist/css/*.css' -x 'uglifycss #{path} --output #{path}'",
"css:hash": "hashmark -r -l 8 dist/css/*.css 'dist/css/{name}-{hash}.css'",

The tool first used; uglifycss takes care of compressing the CSS by remove line breaks and whitespace. Since we’re not concatenating all our stylesheets into one big file, as has been traditional — but rather leverage the power of HTTP/2 multiplexing, we just run it on each file in place.

The second line is all about cache busting. hashmark enables us to do this by providing us with a unique file name base in the hash of the file and, more importantly, only change this hash if the file actually has changed. The flags used are firstly -r which means replace whatever file you’re working one with the hash renamed one, and -l 8 tells hashmark to limit the length of the hash in the filename to eight characters, which will be more than enough for our needs. The pattern {name}-{hash}.css should be pretty self explanatory; file.css would become file-d121a5d4.css.

How Jekyll finds the files

We have a few problems with this approach to solve. Since we’re keeping all of our stylesheets as separate files, we could of course manually link them all in our Jekyll templates, but that’s not really maintainable and as soon as we start hashing our files, that strategy goes straight out the window. So how to make Jekyll aware of these dynamically changing files, without us having to poke around manually each time something changes?

Luckily, Jekyll keeps track of static files.

A static file is a file that does not contain any front matter. These include images, PDFs, and other un-rendered content.

They’re accessible in Liquid via site.static_files

Using this info, we can use the metadata to filter out the files in /dist/css (i.e. where npm is putting our source files once they’ve been transpiled) and then iterate over each file and output the path. It’ll look something like this:

{% for css in site.static_files %}
  {% if css.path contains "dist/css" %}
    <link rel="stylesheet" href="{{ site.baseurl }}{{ css.path }}">
  {% endif %}
{% endfor %}

There’s a caveat with this method, though. This implementation would list any kind of file present in that folder, even those that aren’t legitimately CSS files. In this example, that would never happen, but if you want your solution to be more robust (like if someone haphazardly starts putting PNG files or JavaScript files in your folder), you can also filter using css.extname. Now, if everything is working correctly, assuming that we have three files in /dist/css, that we say are named file1.css, file2.css and file3.css, Jekyll would render markup accordingly:

<link rel="stylesheet" href="/dist/css/file1.css">
<link rel="stylesheet" href="/dist/css/file2.css">
<link rel="stylesheet" href="/dist/css/file3.css">

Awesome! No matter how many files we add to our project, or whatever names they will dynamically get from hashmark, Jekyll will take care of linking them properly for us.

Wrapping up

Simplifying things and throwing out superflous tools felt really great! The less of them I have, the quicker my development environment got and I felt that the overall robustness went up a few ticks. There is still some things that I likely will never be rid of, like the cache busting feature, since the benefit is too big (and I just can’t wrap my head around getting caching headers right).

I hope this might be of some use to someone else than me. The basic principle isn’t really tied to Jekyll (apart from the static files functionality), so you should be able to implement this with whatever tools you choose. If nothing else, it might’ve served as an inspiration to cut down a little in your own tool stack.

Inverted colors on focused links

Some time ago, I worked on a project where we made digital teaching aids. It was essentially web based e-books. Most of these materials would contain links leading to other parts, and in some instances the text could be inside colored boxes, for example an info box or something along those lines. Did I mention that links could also be inside those colored boxes?

Since this was teaching materials for public schools, we had accessibility requirements to fulfill. This included clear and distinct focus states for users navigating the UI with a keyboard. We chose to invert the colors of links — that is, if the text color was black and the background was white, upon focusing a link the background would turn black and the text white.

It’s a fairly simple setup. The required styling would be accordingly:

body {
  background-color: #fff;
  color: #000;
}

a {
  color: currentColor;
}

a:focus {
  background-color: #000;
  color: #fff;
}

Thus far, things aren’t too complex. We also chose to let links have the same color as the text and to rely on their underline to indicate that it was a link.

Now, about those colored boxes. One scenario could be the following:

.theme-1 {
  background-color: antiquewhite;
  color: darkred;
}

If we wanted the same effect as above, we would need additional styling for the links:

.theme-1 a:focus {
  background-color: darkred;
  color: antiquewhite;
}

This would need to be repeated for each theme, which in itself could be a bit tedious.

CSS custom properties to the rescue

What if you could have just one set of rules for this focus styling, no matter how many themes you create? Well, thanks to the power of variables in CSS — or as they are actually called: CSS custom properties — we now can.

Consider the following:

:root {
  --background: white;
  --text: black;
}

body {
  background-color: var(--background);
  color: var(--text);
}

This is our starting point. The neat thing about custom properties is that, like other properties in CSS, they are a part of the cascade. This means that they can either be global, as in the above example where we’ve declared two properties in the :root selector, or they can be scoped to a selector. Now, let’s style our links.

a {
  color: currentColor;
}

a:focus {
  background-color: var(--text);
  color: var(--background);
}

As stated before, we first make sure that links use the same color as the text, then we start leveraging the power of custom properties. This is the inverting of the colors in action. We’re actually done with the styling of the links. Don’t believe me? Check this out. We’re going to make ourselves a nice colored box. First the markup.

<div class="themed-box box-theme-1">
  Content here
</div>

Then, we do the styling.

/* Set up our themed boxes */
.themed-box {
  background-color: var(--background);
  color: var(--text);
}

/* A pretty, colored box */
.box-theme-1 {
  --text: white;
  --background: green;
}

That’s it! See what’s going on? All boxes will get the class name themed-box, which declares that the background color and the color of the text should be set by the custom properties --background and --text respectively. Then, all we need to do is assign the values of those custom properties for each theme we create. Now, without any additional styling for focused links, we get the following:

There’s a CodePen up if you want to play around with this yourself. Happy coding!

Just a text editor and a few hours

Rachel Andrew, once again, seriously hits the nail right on the head:

There is something remarkable about the fact that, with everything we have created in the past 20 years or so, I can still take a complete beginner and teach them to build a simple webpage with HTML and CSS, in a day. We don’t need to talk about tools or frameworks, learn how to make a pull request or drag vast amounts of code onto our computer via npm to make that start. We just need a text editor and a few hours. This is how we make things show up on a webpage.

I’ve been building interfaces for the web since 1996. I first picked this up when I found out that Netscape came with something called Netscape Composer. It didn’t take me long to realise that there was something beneath that WYSIWYG-like editor that interested me even more; HTML. Fast forwards a few years and I had now found out about the wonders of CSS (and the horrors of the browser wars of the early 2000s). One thing have remained constant right up until today; the web still consists of HTML and CSS. No matter the tooling, no matter the frameworks, you still end up with these things (and JavaScript, of course).

Learning the basics is not only a great entry point, as Rachel points out, it’s also a vital skill if you want to become a truly great web developer.

Mocking actions with buttons

A common scenario in my line of work is when I build UI components into complete pages, and somewhere on this pages there might be a form. This form might contain a button, and when you click this button you want something to happen. Now, since pattern libraries and HTML prototypes very often are just static HTML, you are limited in what can happen. Naturally, when it comes to regular links, everything works out of the box since all you have to do is link pages together. Buttons, on the other hand, are a different story.

Let me first state, very clearly, that this should not be used in production. Ever. If you want to link to something – well, then we already have the excellent <a> element that does that job amazingly well.

With the disclaimers out of the way, let’s get down to business! We want our little prototype button to feature two things;

  • A <button> element
  • A URL that you will be taken to upon clicking the button

That’s really all we need. No need to complicate things, right?

The markup

<button type="button" data-prototype-url="link.html">Click me</button>

The type="button" attribute is optional, since the default type for a button is submit and that might be just what you want. In order to easily enter the URL in question we’ve got the data-prototype-url attribute. You can name it however you want. but it might be prudent to use a name that communicates its prototype use case as clearly as possible. Again, we do not want this type of code running on a live web site.

The JavaScript

You guessed it. We can’t really do very much with this button unless we enlist the help of good ’ol JavaScript.

document.addEventListener('click', function (event) {
  if (event.target.matches('[data-prototype-url]')) {
    var button = event.target;
    var buttonURL = button.getAttribute('data-prototype-url');
    window.location.href = buttonURL;
    console.log(button.textContent + ' clicked');
  }
}, false);

Since we might have any number of buttons in a page, all that might potentially mock a scenario that takes us away from the current page, I found that the most preferrable method is to attach an event listener to the entire document and use event delegation to trigger the actions. This was something that I’ve picked up from Chris Ferdinandi. The short-short version of the script:

  1. Listen for clicks across the document
  2. Check if the click matches an element with the data-prototype-url attribute
  3. Grab the value of said attribute and send the user to that value (i.e. a URL)

That’s it! You now have a nifty solution for sending a user to another page by clicking a button, which will enable you to create mock scenarios for user testing or other purposes. If you easily want to test it out, I’ve got a version on Codepen going that you can play around with.

Just please don’t use it in production, ok?

Developing websites for Apple Watch

As of WatchOS 5, you are able to render web content on the Apple Watch. Marcus Herrmann did a write-up on the subject this summer and after stumbling over it, I couldn’t resist doing some testing on my own site. Actually, all I did was to add the following to my <head>:

<meta name="disabled-adaptations" content="watch">

Also, I did some quick and dirty experimenting with a (very ugly) media query that should target only Apple Watches. More testing is warranted.

/* Apple Watch only? How can this possibly come back to bite me in the ass? */
@media only screen and (max-width: 22em) and (max-height: 357px) {

  html {
    font-size: 6.5vmax;
  }
}

It works… ok, I guess? It might need some more work.

Update: It appears that my code blocks looks like crap at the moment. I will have to take a look at that at some point…