Static site generators (SSG) have proven to be very useful tools for easily generating static websites from neatly organised content files. Most of them support using markup languages like markdown for writing content, and offer incremental compilation so that updating a website stays fast, regardless of its size. However, most SSGs are very opinionated about how you should manage your content. As soon as your specific needs deviate slightly from what your SSG supports, it becomes a lot more tedious.

This leads to many people writing their own personal static site generators from scratch. This results in a completely personalised workflow, but without good libraries it is a time-consuming endeavor, and incremental compilation is often out of the equation as it is hard to get right.

This is where achille and Hakyll come in: they provide a domain specific language embedded in Haskell to easily yet very precisely describe how to build your site. Compile this description and you get a full-fledged static site generator with incremental compilation, tailored specifically to your needs.

Why Hakyll is not enough

To provide incremental compilation, Hakyll relies on a global store, in which all your intermediate values are stored. It is your responsibility to populate it with snapshots. There are some severe limitations to this approach:

There are other somewhat debatable design decisions:

Other tools

As always when thinking I am onto something, I jumped straight into code and forgot to check whether there were alternatives. By fixating on Hakyll, I did not realize many people have had the same comments about the shortcomings of Hakyll and improved upon it. Therefore, it’s only after building most of achille in a week that I realized there were many other similar tools available, namely: rib, slick, Pencil & Lykah.

Fortunately, I still believe achille is a significant improvement over these libraries.

How achille works

In achille there is a single abstraction for reasoning about build rules: Recipe m a b. A recipe of type Recipe m a b will produce a value of type m b given some input of type a. Conveniently, if m is a monad then Recipe m a is a monad too, so you can retrieve the output of a recipe to reuse it in another recipe.

(Because of caching, a recipe is almost but not quite a Kleisli arrow)

-- the (>>=) operator, restricted to recipes
(>>=) :: Monad m => Recipe m a b -> (b -> Recipe m a c) -> Recipe m a c

With only this, achille tackles every single one of the limitations highlighted above.

Once you have defined the recipe for building your site, you forward this description to achille in order to get a command-line interface for your generator, just as you would using Hakyll:

buildSite :: Task IO ()

main :: IO ()
main = achille buildSite

Assuming we compiled the file above into an executable called site, running it gives the following output:

$ site
A static site generator for fun and profit

Usage: site COMMAND

Available options:
  -h,--help                Show this help text

Available commands:
  build                    Build the site once
  deploy                   Server go brrr
  clean                    Delete all artefacts

That’s it, you now have your very own static site generator!