In praise of kiln: a simple static site generator

2023-02-07

Static site generators (SSGs) are great: they allow you to render simple text formats like Markdown into fully-featured web templates. But they all do it slightly differently, so the hobby-du-jour of every aspiring tech blogger is to continualy switch whenever they get bored of the old one. And I'm certainly not immune to this! But I am increasingly convinced that we'd all do well to choose boring technology, prioritising simplicity and stability, with well-understood failure modes. This simplicity is exactly kiln, my new favourite SSG, focuses on.

It goes without saying that if you're getting value out of your current SSG, you probably shouldn't switch - your time would be better spent actually writing for your blog. But if you're interested in how different SSGs do things, or like me are frustrated with the limitations and idiosynchracities of your old one, then allow me to explain what I like about kiln.

TL;DR

In summary, kiln prioritises being as simple as possible. As a good free software citizen, it's designed to integrate well with external tools. Data processing tasks are generic, so it handles rendering into different formats (such as HTML, Gemini, and RSS/Atom) with exactly the same mechanism. It's easy to contribute to as well!

I'll go through each of these in turn, giving examples of how it's been useful to me.

kiln integrates well with other software

kiln really aspires towards the UNIX philosophy: it tries to do one thing, and do it well. that thing is tasks, which process files in a directory structure, through some external commands and a templating engine, and into an output directory. It can generate index pages for that content, which can be used as homepages (ever wondered why it's called index.html?), and copy static content into the right place.

This means that the heavy lifting of processing the data is left entirely up to external commands such as mdtohtml, which can focus on doing their one thing well.

No type of input or output data is a first-class citizen over any other (with the exception of feeds), meaning that kiln can be used for all sorts of purposes. This includes dual-publishing your website on different protocols, such as HTML and Gemini, as well as compiling your scss and compressing your images. And then you build it all in a single command: kiln build.

For example, you can set the following in your config.toml to render Markdown files into HTML:

[[tasks]]
input = [".md", ".html"]
output = ".html"
template = ".html"
preprocess.md = "mdtohtml"
static_dir = "static"
output_dir = "public"

Compressing images for the web is easy with ImageMagick:

[[tasks]]
input = [".jpg", ".jpeg", ".png"]
output = ".jpg"
preprocess.jpg = "magick /dev/stdin -strip -interlace Plane -gaussian-blur 0.05 -quality 85% /dev/stdout"
output_dir = "public"
ugly_urls = true

And I render Sass like so:

[[tasks]]
input = [".scss"]
output = ".css"
preprocess.scss = "sassc"
output_dir = "public/assets"
ugly_urls = true

Generating an RSS feed is easy, but is treated as a special case:

[[tasks.feeds]]
input_dir = "portfolio"
template = "atom.xml"
output = "portfolio/atom.xml"

More on that later.

kiln has a good man page

Not sure what some of those arguments were in the previous examples? No worries - man kiln is very easy to read, and contains everything you need to know.

For example:

ugly_urls
    Specifies whether page paths will contain file extensions. By
    default, clean paths without any extension are used.

but the other docs are still quite lacking

That said, all the rest of the documentation is a bit work-in-progress. There's a simple website with some examples, but it's not really enough to get you started. What's really missing is more examples for common tasks, such as website navbars. You'll likely discover that kiln's abstractions suit your needs, but you might have to look at other people's sites in order to see how.

But once you get the idea, the man page has you sorted on how to use each feature.

kiln is simple

The code is written in Go and is easy to understand, as it tries to do as little as possible and then gets out of your way. So, once you understand how kiln works, building a website is easy. Error messages, which can admittedly be a bit obscure, are quite easy to get to the bottom of by digging through the code.

The author adnano is great, and is very happy to accept patches on the devel mailing list. I contributed a number of features, each of which were discussed on the discuss mailing list beforehand.

but its template engine is quite strange

kiln uses the native Go template package, which wins points for simplicity - the only features are basic inheritance, variables, and simple functions. It's designed as a library for Go programs, representing only the bare minimum features, allowing the software author to extend it with additional functions and specify behaviour such as which templates to inherit from what.

kiln therefore implements a number of features, extending the basic templates with page variables such as .Title and .Date, and functions such as math.Add, partial to execute partial templates, and html for escaping HTML. But it feels slightly against the kiln philosophy to put every function that could be needed into the SSG, rather than factoring it out into an external template processor. This also makes it feel at times like kiln has its own custom template language, which you can only decipher by looking at the functions implemented in the man page and the Go template docs. These docs are clearly written with a Go programmer in mind, not someone using the template language, and so can be quite confusing.

There are also some strange Go template idiosynchracies: dot notation references variables and functions, but only from the current context which could be determined by an outer template. These tend to refer to global variables but only sometimes. The loop syntax is also weird, using the range function and binding variables in an inner context:

{{ range $value := site.Params.nav.navbar }}
  <a href='{{ index $value "url" }}' style="font-size: 0.9em"
    class="navbar-item has-text-white
    {{ if (eq $.URL (index $value "url")) }}is-active{{ end }}
    ">
    {{ index $value "name" }}
  </a>
{{ end }}

Perhaps the entire template system were factored out into a separate CLI program, allowing a custom engine to be used. The variables, partial templates, and files in inheritance order could be provided as input, and the rendered template output on stdout.

building large files is quite clunky

kiln's strongest feature is generic tasks, which can be used to transform any type of data into the desired output format. In my setup, this includes compressing images for the web and running openring, which are both quite long running processes. It's great that this can be unified into a single kiln build.

However, without supporting any form of differential builds, build times can be long even when very little was changed. This makes a setup which rebuilds whenever the content of a file changes quite clunky. For context, I run:

while true; do
    find . | entr -s "make site"
done

Differential builds could perhaps be added in a patch. Notably this isn't a problem for other SSGs, which don't even support any kind of generic task system in the first place.

kiln has some special cases

As Tim Peters wrote in The Zen of Python:

Special cases aren't special enough to break the rules. Although practicality beats purity.

Whilst practical, kiln makes a couple of special-case decisions that I think could instead be implemented more consistently.

One such area is feeds: these are implemented as a special case, and are given a set of feed variables: .Title, .Path, .URL, and .Pages. But since these variables are a subset of the page variables, feeds could potentially be implemented in the same way as index pages.

Additionally, HTML content is special-cased, with HTML templates considered separately to all other types of templates. I believe this may be to allow safe escaping of HTML content, but this might not be strictly necessary.

Conclusion

In conclusion, kiln is an SSG that's focused on simplicity and adherence to the UNIX philosophy; working with it is a joy. Many thanks to adnano and the other contributors for their hard work!

The tasks system is its best innovation over other SSGs, and now I wouldn't want to go without it.

Whilst still a somewhat new piece of software, there are a few pain points, and doubtless you'd find a few more. But if you're willing to put in a patch or two, you'd help kiln become even better for the rest of us!

Articles from blogs I read

Hugo: rename a tag

This blog is rendered by the means of a static site generator (SSG) called Hugo. Each blog post has a set of one or more tags associated to it. The more posts I create, the more consolidated the tags become. Sometimes I need to rename tags after-the-fact to …

via Not Just Serendipity January 29, 2024

Why Prusa is floundering, and how you can avoid their fate

Prusa is a 3D printer manufacturer which has a long history of being admired by the 3D printing community for high quality, open source printers. They have been struggling as of late, and came under criticism for making the firmware of their Mk4 printer non-…

via Drew DeVault's blog December 26, 2023

attribution armored code

Attribution of source code has been limited to comments, but a deeper embedding of attribution into code is possible. When an embedded attribution is removed or is incorrect, the code should no longer work. I've developed a way to do this in Haskell that…

via see shy jo November 21, 2023

Generated by openring