Projects
-
TIL: Cleaner Log Output When Using 'Concurrently'

If you’ve ever used the package
concurrentlyto run more than one thing at the same time while developing, you probably have seen how the logs are a little jumbled together. All output is logged to the console, by default, with a number representing the process that created the output. As an example, here’s a Vite frontend and a toy websocket server in the backend that logs every message received from the FE:[0] VITE v7.3.0 ready in 220 ms [0] [0] ➜ Local: http://localhost:5174/ [0] ➜ Network: use --host to expose [1] received: { 'message': 'hi' }This is…fine? But it’s easy to miss the [0] or [1], it’s just one number and honestly if you’re not looking for it it just sort of fades into the background.
But by passing the
--namesflag in you can call your processes anything you want.concurrently --names frontend,backend 'vite' 'node src/server.js'turns the above output into:[frontend] VITE v7.3.0 ready in 220 ms [frontend] [frontend] ➜ Local: http://localhost:5174/ [frontend] ➜ Network: use --host to expose [backend] received: { 'message': 'hi' }And of course you can use more descriptive names, although these are plenty for me.
I wasn’t able to find the official documentation on this, although there is documentation on how to interact programmatically with concurrently and pass in the names options. I’m not sure how to incorporate that into a development pipeline but that’s a me problem. For now, the command-line flag is all I need.
-
Making This Blog Even Faster With Speculation Rules

Browsing the HTMHell Advent Calendar I learned about a completely new-to-me browser API called “speculation rules.” This poorly-named (according to me) feature allows browsers to prefetch or even pre-render content speculatively, basically predicting what a user is going to click on. Currently, this feature is available in Chromium-based browsers, but Safari and Firefox are working on it, and in the meantime, including it doesn’t harm the experience for Firefox and Safari users.
In its most basic form, adding the following to a page:
<script type="speculationrules">{ "prerender": [{ "where": { "href_matches": "/*" }, "eagerness": "moderate" }]}</script>is all it takes to pre-render the destination link when a user hovers their mouse over it, making the load almost instantaneous from the user’s perspective.
The author of the blog post, Barry Pollard, who works on Chrome at Google, goes on to explain some of the quirks of
speculationrules. For example, how do you handle mobile users, where “hover” isn’t a thing? What about Javascript that you don’t want to execute before the page is actually viewed? (What about analytics where you don’t want to count page views before the user actually looks at the page?) These are problems that are “actively being worked on,” which means not solved just yet. I would not use this feature on a production site where I cared about any of those things.This is a progressive enhancement to
<link rel="prefetch">which is more widely supported. But the old way seems harder to implement as, at least by my read of MDN, I would have to consider exactly what links are most likely to be loaded next by the user on what pages, whereas the speculation rules API decides for me based on the user’s actions.To sum up, this is a pretty neat option that is definitely not yet ready for use on all sites. There are a lot of questions that need to be answered. But in the meantime, I think speculation rules are a perfect fit for a static, no-JS site like this blog. Which is already pretty fast, by dint of it being a static, no-JS site. 🤷
If you’re using Chrome and want to check out the new behavior for yourself, simply open Devtools, navigate to the Network tab, then hover over any link on this site. You’ll see a little blip of activity before you click. Neat!
-
Webpack 201
This cat is now bundled for production.In 2024, I wrote about learning the basics of Webpack when I realized it was a tool that I used almost daily without thinking about it.
Now, I’m working on a Firefox extension that is going to depend on a third-party library. How on earth do you get third-party packages into a Firefox extension?
The answer is, again, Webpack. But unlike with React-based projects, where Webpack is often invisibly part of the build and deploy process, we are going to have to manually configure Webpack ourselves for this extension.
The problem
I want to include third-party Javascript in my extension. As we learned from my previous post, it makes developers’ lives easier to be able to
requireorimportexternal scripts, but the browser doesn’t know how to find those scripts on a filesystem. Or even how to access a filesystem. So we need to use Webpack to turn those external dependencies into internal code that the browser knows how to use.Yes, this is overly simplified.
As a concrete example, my extension’s structure looks like this:
. ├── db.js ├── node_modules ├── package.json ├── package-lock.json ├── readme.md ├── index.js └── webpack.config.jsNote that there are multiple
.jsfiles in the root of the extension, plus a node_modules folder. Any time I writeimport {thing} from 'thing'in my code, whether I’m talking about code I created or a module I installed, my local dev environment knows how to resolve those imports, but a browser environment wouldn’t – hence the need for a build tool like Webpack. (Note: this is overly simplified and I read Lea Verou’s writing on this topic and, much like the post below, it broke my brain.)The footnote
There are a zillion other ways to get Javascript libraries working without a build system. I read about a number of them on Julia Evans’s blog, but the outcome of reading that blog post is I realized I just don’t know enough about Javascript to understand all these options. I’ve set up time with a senior engineer at work to learn more, which is exciting in a very nerdy way.
I can say for sure that one of the alternatives is, depending on how the module is written, “just stick it in a
<script src>tag, which would be very simple except that Firefox extensions don’t use HTML.1There are other options (including importing modules directly from CDNs??) but let’s assume for this project we just want to use webpack.
The solution: roll your own (simple) webpack config
First, we need a package.json file to define our dependencies and build tooling. Actually, as I write this, I don’t know for sure if this step is 100% necessary, but it makes things easier. I ran
npm initfrom my extension’s base folder and a wizard walked me through creating a package.json. Super easy!I then modified my package.json to look like this:
{ "name": "linkedin-extension", "version": "1.0.0", ... "scripts": { "build": "webpack" }, "author": "rachel kaufman", "devDependencies": { "webpack": "^5.74.0", "webpack-cli": "^4.10.0" } }Now when we run
npm iwe should install two Webpack tools as dev dependencies, and when we runnpm run buildwebpack will run.We then define a webpack.config.js file:
const path = require("path"); module.exports = { entry: { whatever: "./whatever.js" }, output: { path: path.resolve(__dirname, "addon"), filename: "[name]/index.js" }, mode: 'none', };This defines an entrypoint of
whatever.js, meaning when we runnpm run build, Webpack will look at that code, resolve any imports in it recursively, and output new, bundled files to theaddondirectory. Specifically, toaddon/whatever/index.js. We then refer to those built files instead of the source files when running or testing our extension.This was surprisingly easy, thanks to MDN’s great docs.
What does the extension do? Can’t wait to tell you about it next time.
Further reading
- MDN example using Webpack to include left-pad.js in a Firefox extension
- Some advanced tricks with Webpack and browser extensions, by Eric Masseran
Footnotes
-
Okay, they do, but I’m talking about content scripts which allow us to load
.jsor.css. ↩
-
I Created Custom Procedurally Generated Truchet-Tiled Open Graph Images for This Blog

I love Truchet tiles, which are square tiles that form interesting patterns when you tile them on the plane. The idea that some basic shapes, like the triangles above, can form elaborate emergent patterns when tiled in interesting combinations, fits in nicely with my interests of quilting and drawing geometric abstract shapes, which are both things I do in my spare time.
I recently rediscovered the Truchet tiles by Christopher Carlson; he is also a mathy quilter to some extent, but I rediscovered his work thanks to Alex Chan, who blogged about recreating the Carlson tiles in SVG in order to use them as blog headers. That tripped something in my brain, and I remembered reading Cassidy Williams’s post about generating custom open graph images last year, and obviously I needed to smash these things together.
The result is a custom image for every blog post that is used in the
og:imagetag in its header, which is what controls how posts are previewed when shared on social media, within Slack, etc. Each image has a unique, procedurally generated tiled background unique to only it1, plus the title of the post and my name. Here’s what this post’s image looks like:
So now I’ve covered why I built this (I got nerdsniped over winter break), but how did I do it? Read on to hear about that.
I will add the disclaimer that there are probably much easier ways to achieve the same end result. Somehow I just got hung up on “do the thing Alex Chan did and then combine it with the thing Cassidy Williams did” and that was the architecture I ended up following. I’m curious how others would implement this while starting from scratch – please reach out!
Creating the template
Here I basically followed Alex Chan’s pseudocode, but as I don’t think they were writing with the intention of someone coming along and
wholesale liftingborrowing it into their own project (fair), I did have to do a lot of tweaking. Essentialy, you define a set of base tiles as SVG that can be used to create all the shapes in the set, then you define all the shapes in the set, and then you use Javascript to randomly pick a tile from the bag and place it in your image:tilePositions.forEach(c => { let tileName = tileTypes[Math.floor(Math.random() * tileTypes.length)]; svg.innerHTML += ` <use href="#${tileName}" x="${c.x }" y="${c.y }" transform="translate(${padding} ${padding}) scale(5)"/>`; });In Chan’s initial implementation (as well as Carlson’s) there’s also the complexity of the Truchet tiles working at multiple scales. While this is the coolest part of the original project, mathematically speaking, I a) didn’t love the look of the smaller tiles and b) couldn’t figure out the fiddly padding, even with Chan’s pseudocode, so mine is just a single layer of tiles of a single size. I dumped all this into a single HTML file that lives on my computer.
Once I had the background, I needed to add some text. There are many approaches to this, but I decided to add a query param to the local HTML file which would take in text and render it to the SVG using the
<text>element.That looked like this:
const text = new URLSearchParams(window.location.search); svg.innerHTML+=` <text class="regular" style="font: bold 30px sans-serif; text-anchor:end" x=1140 y=250 >${text.get("foo")}</text> `;This worked fine…until it didn’t. SVG text positioning is a little janky and you don’t have as many levers to pull as with regular HTML text positioning. And we have to handle our own linebreaks.
What I ended up doing is pretty hacky, but it works. I decided that no line should be longer than 8 words. If the input text is more than 8 words long, we divide it in roughly equal halves. (If the input text is more than 16 words long, we divide it in thirds.) Then each line of text is output into the SVG with a vertical offset.
That looks roughly like this:
function splitTextToLines(text){ const result = text.split(" ") if (result.length<8){ return [text]} else if (16>result.length){ const first = result.slice(0,Math.ceil(result.length/2)); const second = result.slice(Math.ceil(result.length/2)); return [first.join(" "),second.join(" ")] } else { const first = result.slice(0,Math.ceil(result.length/3)); const second = result.slice(Math.ceil(result.length/3),Math.ceil(result.length/3)*2); const third = result.slice(Math.ceil(result.length/3)*2); return [first.join(" "),second.join(" "),third.join(" ")] } }And the loop that actually renders the text to the image:
const lines = splitTextToLines(text); y-=lines.length*45; for (let i=0;i<lines.length;i++){ svg.innerHTML+=` <text class="regular" style="font: bold ${fontSize}px sans-serif; text-anchor:end" x=1140 y=${y+i*65} >${lines[i]}</text> `; }I got the numbers right by just generating a lot of text and manually tweaking it. I like the end result, although it’s definitely not perfect and might still fall down with edge cases of really long or really short words.
Finally, I defined a handful of palettes that I like looking at and that vaguely go with the color scheme (such as it is) of this blog. The script selects one at random and injects a stylesheet to color the foreground and background of the tiles, as well as the text.
Creating the images
To actually create the images from the template, I wrote my first Jekyll plugin! Here, I am quite grateful to this 8-year-old gist that did close to what I wanted to do. Instead of generating the image with ImageMagick (which is also extremely cool!!!), I added code that uses Ruby Puppetteer to load the file and save a screenshot to the
/assets/opengraphfolder, returning the path to the file. The code is then registered as a Jekyll tag calledog_image. This means…Putting it all together
All I need to do to generate and use these images is edit my
head.htmllayout like so:{% if page.image %} <meta property="og:image" content="{{ page.image }}"> {%else %} <meta property="og:image" content="{{site.url}}{% og_image %}"> {% endif %}Now, if the page has an image defined in its front matter, Jekyll will use that. Otherwise, Jekyll will generate an image using the plugin and refer to that.
I’ve wanted to do something like this for some time. I’m not sure how many people will ever see these, which makes me feel a little insane, but, well:

Resources
- Drawing Truchet tiles in SVG
- Multiscale Truchet Tiles
- Generating open graph images in Astro
- Dynamically generated open graph images in Jekyll
Footnotes
-
With 14 tiles to choose from, 540 tile positions, and 3 color palettes, the odds of a repeat are… low. #math ↩

