~eddsalkield/edd.salkield.uk

ce2ecfde2f497cbdd6b89dd5a338882e7401f44f — Edd Salkield 1 year, 3 months ago c414539
blog/in_praise_of_kiln
1 files changed, 182 insertions(+), 0 deletions(-)

A content/blog/2023-02-07-in_praise_of_kiln.md
A content/blog/2023-02-07-in_praise_of_kiln.md => content/blog/2023-02-07-in_praise_of_kiln.md +182 -0
@@ 0,0 1,182 @@
---
title: "In praise of kiln: a simple static site generator"
date: 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](https://mcfunley.com/choose-boring-technology), prioritising simplicity and stability, with well-understood failure modes.
This simplicity is exactly [kiln](https://sr.ht/~adnano/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](https://gemini.circumlunar.space/), 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`](https://git.sr.ht/~adnano/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](https://imagemagick.org/):

```
[[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](https://sass-lang.com) 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](https://kiln.adnano.co/) 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](https://adnano.co) is great, and is very happy to accept patches on the [devel mailing list](https://lists.sr.ht/~adnano/kiln-devel).
I contributed a number of features, each of which were discussed on the [discuss mailing list](https://lists.sr.ht/~adnano/kiln-discuss) beforehand.

# but its template engine is quite strange

kiln uses the native [Go template package](https://pkg.go.dev/text/template), 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`](https://git.sr.ht/~sircmpwn/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](https://mail.python.org/pipermail/python-list/1999-June/001951.html):

> 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!