+++*

Symbolic Forest

A homage to loading screens.

Blog

Forging ahead

Or, how Cait ended up building her own tool to build the site

When I last wrote an article about how this blog is built, how it is turned from Markdown text into an array of thousands of static HTML pages, over eighteen months ago now, I said that “another big change to how the site is published” was coming soon. It did, indeed, come soon; I just didn’t write about it. Since summer 2024, this site has been published by an entirely new static site generator. Moreover, it’s my static site generator.

When, in 2020, I decided to switch the site from hosted Wordpress to being static, there were a few options to choose from. Jekyll is (and was) one of the leaders, but it’s written in Ruby, which would have been a whole new ecosystem for me. Eleventy seemed like a good option, and I can’t really remember why I didn’t choose it at the time. The tool I eventually went with was Wintersmith, because it seemed simple and straightforward, and a quick proof-of-concept showed that it had all the features I needed. The only red flag, though, was that it didn’t seem to have been updated very recently. Nevertheless, I thought, I could always move again later.

I reimplemented the site with Wintersmith, got everything working, and built new Wintersmith plugins to provide all the features that Wordpress had, like tagging and categories. And all was good. Except, Wintersmith still wasn’t getting updates.

Wintersmith was part of the JavaScript ecosystem, distributed via NPM. Every time I ran a build, NPM would warn me that a big stack of Wintersmith’s dependencies were outdated and had security risks. None that genuinely affected me; but still, it was more and more of a concern. I thought: well, I could patch it; but then, I saw, someone else had already submitted a patch to do just that, and the patch was sitting there in the project repository, unmerged. I had to switch.

Rather than learn a new system, though, why not take Wintersmith and fork it? It’s open-source software, after all. A fork is simply when someone takes an existing project which doesn’t quite do what they want, or which is going in a direction they don’t like; and they make it their own. Like two different branches of the evolutionary tree; things split off in different ways, and we end up with mammals and lizards and birds all happily coexisting.

So, I picked a name—Iceforge, to give it a nod to its heritage—and got started. And along the way, I changed a lot. Iceforge is a classic Ship of Theseus. Every single line of code is changed, but it will still build a basic Wintersmith site, and plugins only need slight changes to port them from Wintersmith to Iceforge.

The reason I can guarantee every single line of code in the application has changed—aside from possibly some of the blank ones—is that Wintersmith was written in CoffeeScript. You might not have heard of CoffeeScript; I had used it, in the early 2010s. It is essentially an alternative syntax for JavaScript. It’s a much terser syntax; it’s heavily whitespace-dependent, and at the time it was created it included a few features JavaScript didn’t have, like classes.

I didn’t like using CoffeeScript, although I understood it well enough to write my own Wintersmith plugins; so I immediately knew that Iceforge would use something else. The obvious choice was TypeScript: a more verbose version of JavaScript, more verbose because it includes compile-time type checking.

Naturally, I had to switch over from using CoffeeScript classes to JavaScript classes; that wasn’t an issue at all. The asynchronous coding style also, I thought, could do with an update to it. “Asynchronous coding” means that, when the application goes and asks the computer to do something that might take a while, like reading a file, it will get on with something else whilst it’s waiting until the file is ready. When I’m cooking food I’ll pop it in the oven, and then I’ll go take the rubbish out whilst it’s roasting. Asynchronous coding is just like that, but not as tasty.

Originally, Node.JS handled asynchronous coding through something called continuation-passing. It relies on the fact that functions, individual pieces of code, can be passed to other functions which can then call them. With continuation-passing style, if you want to read a file, you call a system function and give it first the filename, and then the piece of code to call when the file has been read. It worked well given the limits of JavaScript at the time, but it means that code can end up a little bit broken up and fragmentary. The code that gets run after a “slow operation” has to be separate from the code that gets run before a slow operation. Moreover, Wintersmith was largely plugin-based, and continuation-passing clutters up the plugin API. Every Wintersmith plugin has to be written in a continuation-passing style and the plugin authors have to remember to call their continuation functions even if the plugin itself isn’t calling any slow operations.

From 2015, JavaScript gained an awful lot of new features; one of them was “promises”, a new way of calling slow operations without passing continuation functions. A couple of years later, it gained the await keyword to go with them. A slow function, nowadays, can return a Promise object, and the developer can choose whether to treat that in a continuation-passing style, or to use await to pause that particular line of execution until the result from the slow operation is ready. Because, in my opinion, that produces much more readable code, I restructured all of Iceforge to use awaited Promises everywhere that the code had been using continuation-passing. When a developer is writing an Iceforge plugin, their plugin registration function has to return a Promise, but they don’t have to worry about adding a continuation boilerplate call any more.

I made various other useful changes, such as building into Iceforge’s “blog” site template a lot of the features I’d developed for this site. The Markdown parser had to be tweaked a bit, just because the API of the underlying Markdown rendering library has changed a bit. I developed a unit test suite, not something Wintersmith has ever has as far as I’m aware. The one thing I haven’t done yet is documentation. At present, all I’ve done is to add documentation to the code itself; learning how to use Iceforge is left as an exercise for the developer. At some point, when I find the time to write all that, I’ll get a documentation site online. After that there are a few other things I’d like to do, such as port over the rest of the existing Wintersmith plugins, and implement footnote support in the basic Markdown plugin. After that… Iceforge 2.x could do with a much bigger rewrite, to tidy up aspects of the API that I’ve just found a bit weird. There’s no rush, though.

If you want to use Iceforge you can install it from NPM, either as a global tool, or if you’re willing to consider your website source code to be an NPM module, locally within that module. I like the latter, because it works cleanly with CI/CD toolchains, but you could do it the other way too. This blog post isn’t meant to be Iceforge documentation, so I’ll let you play with it yourself, but feel free to try it out and give it a go. I will get the documentation site finished some day, I promise.