Two major improvements last night. Switching to versioned URIs was trivial thanks to the magical gulp-rev-all. I decided I wanted to apply it to images too, so I assumed I would have to jump through hoops. Turns out, this is some of the only software I’ve ever encountered that Just Works™ right out of the box. All hail the rev-all.

The other improvement was setting up responsive images. This was… harder. Every time I start a new project, it’s with a new permutation of tools and versions, and it eventually requires inventing a new way to deal with responsive images, but I couldn’t very well leave 6 MB of unoptimized full-size images in my entries.

Squish, squish

I started by adding gulp-imagemin to my images task so that every image was being properly optimized. This actually caused a problem which I didn’t notice until later: a few PNG images were turning transparent in the whites. It was pretty odd to see a screenshot of my DotA profile where my name turned yellow when you hovered over it (because it was inside a link and the background showed through the transparent sections). Re-saving the original images with transparency explicitly turned off seemed to fix it. The only remaining issue is that some ‘optimized’ images are actually larger than the originals!

I also tried using gulp-image, which provides useful status information, but I wasn’t able to get it to work correctly. There was some sort of error relating to svgo.

Generation

Next, I looked up gulp-responsive-images, which is my usual choice for generating the images themselves. I noticed a new package, @tigersway/gulp-responsive, which was a Complete rewrite of gulp-responsive & gulp-responsive-config, with dependencies updated and in only one package. I pulled the shiny new thing into my configuration.

I always use img srcset to mark up my images. I created a JSON file with a list of widths I wanted to generate images at: 400, 800, 1200, and 1600 pixels.[1] Then I adjusted my Gulpfile to read the widths and started writing the configuration for the responsive images plugin. That was when I realized my plan to create four widths, a full-size image, and a thumbnail made no sense. I used this formula in the past because I needed to create galleries, but in this case, all I wanted were small thumbnails that would link to the full-size images. I pared down the configuration: two widths, with Sharp settings to scale them down and crop them to squares. It took some experimenting to figure out the correct syntax. This is what I arrived at:

JavaScriptfunction generateResponsiveImageConfig(imageConfig) {
  const sizeConfigurations = [];

  for (const size of imageConfig.widths) {
    sizeConfigurations.push({
      resize: {
        width: size,
        height: size,
        fit: "cover",
        position: "left top",
        withoutEnlargement: true,
      },
      rename: { suffix: `.${size}w` },
    });
  }

  return Object.freeze({
    "**/*.png": sizeConfigurations,
    "**/*.jpg": sizeConfigurations,
  });
}

Markup

Now I needed to point to the right images in my HTML. These entries are written in Markdown, where the syntax for images is ![image-url](Some alt text). I could have used a Markdown plugin to modify the output,[2] but I’m more comfortable with processing HTML, so I first looked up 11ty’s Transforms feature, then searched for a way to transform the HTML. I wanted something which would give me both an easy method to search for what I wanted to change and an easy method to replace it.

After some consideration, I chose PostHTML. Its model looked simple and usable. I did find that you couldn’t match using a callback, making certain tasks difficult. Fortunately, searching for <p><image-link … /></p> was easy once I understood. Building up the actual nodes was trivial too. I wired it up and it worked fine.

There was one more detail I wanted to address. Given an img element with an srcset, the browser will decide which file to use based on the width of the window, but the width of the actual image will likely be much smaller. HTML 5 provides the sizes attributes to control this. It has a specific syntax with restrictions on which units can be used inside it. I wanted to devise a common system for both my Sass and my build to use so that the HTML sizes attribute would always be in sync with my CSS.

I added a sizes setting to my JSON file from earlier and looked around for a way to import it in Sass. All I found were packages doing the opposite (converting Sass to JSON), so I gave in and reversed it, adding sass-parser to my dependencies. Sadly, this simply ignored complicated declarations like $larger-sizes: ("(min-width: 32em)": 75vw, "(min-width: 64em)": 28em). Unable to find a functional alternative, I started perusing the documentation for Dart Sass itself and even considered parsing my Sass variables file into an AST with sast, but I regained my perspective in time and punted on the problem, just hard-coding the sizes for now. (Naturally, while I was looking for Sass-to-JSON converters, I found a bunch of JSON-to-Sass converters, so I’ll probably reverse it again and try one of those.) Problems aside, I’m happy to report I can now write this in my entries:

HTML<image-link key="emacs-rust-syntax-highlighting.png" alt="An Emacs buffer showing a properly highlighted Rust file." />

And have it automatically turned into this:

HTML<a class="image-link" href="/assets/images/emacs-rust-syntax-highlighting.png"><img class="image-image" alt="An Emacs buffer showing a properly highlighted Rust file." src="/assets/images/emacs-rust-syntax-highlighting.400w.png" srcset="/assets/images/emacs-rust-syntax-highlighting.400w.png 400w, /assets/images/emacs-rust-syntax-highlighting.800w.png 800w" loading="lazy" sizes="(min-width: 32em) 75vw, (min-width: 64em) 28em, 50vw"></a>

Which is really all anyone can ask for in life.

Sharp on Netlify

I ran into a common issue with Sharp when I tried to build the site on Netlify. The situation got pretty complicated between pnpm, Netlify, Sharp, and npm. I couldn’t get it to build with a clean cache. In desperation, I switched entirely from pnpm to npm for this project, and figured out the right incantations for it all to work. I’m unhappy about switching away, but at least this isn’t a multi-package workspace.

Miscellany

The title is a reference to a 12-year-old meme video.


  1. I don’t have any specific reason for using those. They just seemed sensible a long time ago.
  2. That said, I’m not sure Markdown’s paragraph handling would have worked for me.

Next in series: (#5 in Colophon: Finding A Place For My Head)