I Moved This Blog To Eleventy

Astute observers of this blog may have noticed that the blog looks suspiciously different lately. That’s because I changed the static site generator I was using.

Previously the static site generator I used was Hugo. This arrangement worked for a while but it had some shortcomings.

Growing Pains

I was using Pandoc to convert Markdown to HTML. Hugo’s support for Pandoc had some limitations. For example, you can’t specify options for Pandoc or use a bibliography. When I moved the blog to Hugo, I lost some of these features. Attempting to close the gap I authored a patch that added support for configuring Pandoc.

Pandoc is extensible via filtering the AST of a document through a series of programs which can modify it. There is no restrction on the kinds of programs that you can use.

If Hugo allowed configuring all this via frontmatter, that may introduce some tricky security issues.

Say you download a Hugo theme from the web and try to build your site. A theme could include frontmatter that causes Pandoc to run an unsafe binary. That’s not great. So it is understandable that this kind of feature isn’t a part of Hugo proper1. While my patch worked, it was safe only because I controlled every aspect of the blog.

While tinkering on this was fun, it created an additional headache. Namely, whenever and whereever I wanted to build my blog, I had to maintain a custom build of Hugo. After moving my build a couple of times maintaining a custom build became a painful chore.

The Exodus

So started the process of looking for an easy-to-use static site generator (hereafter SSG). My requirements were:

Eleventy was almost a no-brainer. People had high praise for it, including developer relations folks at Google. So I felt like that would be an easy move.

Eleventy’s Mardown support is based on Mardown-It. One thing that I liked about Markdown-It from the start is that there appeared to be people who are extending it to support various features of Pandoc. In fact I found plugins for basically all Pandoc features that I used in this blog. Everything was looking quite rosy.

Templating

WebC + Eleventy Is Broken

Eleventy supports a templating language called WebC. It follows the web-component way of defining layouts, but can avoid much of the custom element overhead by opportunistically generating plain HTML. I had already accepted that switching the SSG meant rewriting layout templates, and using web components to do that seemed like a good idea2.

A couple of standalone WebC based templates worked fine. But the moment I had nested templates, Eleventy hung and eventually crashed. I didn’t want to go on a side quest to debug it, and ultimately that would’ve likely led to be violating the “no local patches” rule. So that was that for WebC. If WebC was more robust or just worked, it would have been my choice of template engine.

One cool feature of using WebC with Eleventy is that bundling works trivially. For example, if you have a <script> tag or a <style> tag in a component, WebC automatically pulls the content into the relevant bundles. All other templating engines supported in Eleventy (save for third-party engines that I didn’t try) require manual labor to get stuff into the bundles.

Nunjucks + Eleventy Works

The next templating language I looked at was Nunjucks. I had briefly looked at Liquid as well, but for my purposes Nunjucks and Liquid seemed to be quite similar. Nunjucks won out because it had safer defaults3.

Handrolling

Pandoc Again

I tried to rely only on Markdown-It for this blog. But very soon I had a big stack of extensions to support the various features I needed, like supporting <div>s, bibliography, and attributes. While the Markdown-It ecosystem is extensive, it also introduces a lot of confusion. For example, there are multiple packages that all provide the same functionality and also based on the same base package. Keeping track of all this was starting to be a pain. Also, I had trouble getting block level containers working.

After a while I decided not to waste time trying to replicate features that already existed in Pandoc. So I wrote a little library that invoked Pandoc within Eleventy to convert Markdown to HTML. It was a little slower, but worked out great.

Bundling Files

While it is possible to add content to bundles (like CSS and JS), curiously it was not possible to stuff files into said bundles. The suggested method was to do something like this:

  {% css %}
    {% include "some-file.css" %}
    body {
        // other CSS.
    }
  {% endcss %}

This seems like a good idea, but there is one crucial issue: The contents of the included file (some-file.css in the example above) is also processed by the same templating engine. If the file doesn’t contain any sequences that look like template tokens, then we are good. But this is asking for trouble.

So I wrote a custom tag called bundleFile that can take the contents of a file and stuff it directly into a bundle. The earlier example now looks like this:

  {% bundleFile "some-file.css" %}
  {% css %}
    body {
        // other CSS
    }
  {% endcss %}

SASSy

Eleventy doesn’t support SASS out of the box, but there are examples in the documentation for how you can set it up. But all of the examples assume that you have SASS or SCSS files that you are going to conver to CSS, and then serve without bundling. I wanted to be able to compile SASS and stuff the resulting CSS into a bundle.

So alongside the bundleFile shortcode, I added a bundleScss shortcode so I can do stuff like this:

   {% bundleScss "my-styles.scss" %}

Katex

I like Katex for my math typesetting. Pandoc supports it out of the box. But the rendered HTML needs to invoke Katex in the client to render the math. I wanted to avoid this step entirely and try to render Katex on the server.

This was a pain to do with Hugo, but since Eleventy was alredy runing in Node, server-side rendering is a breeze.

Conclusion

Overall I found Eleventy fun to work with. My gripes are mostly with the documentation which lacks a reference. It’s a pain to go through tutorial-like documents to find out what exactly gets passed to a shortcode or what to do when a shortcode adds a dependency. I made way too many trips to the 11ty GitHub repo. The right number of required visits should be zero.

In the coming weeks I’ll package Eleventy Pandoc support and some of the shortcodes so others can use them.


  1. There are ways to mitigate this problem. For example, we could restrict which flags can be inherited from themes, or require that certain options can only be set from the top level hugo.yaml file that the user controls.↩︎

  2. Not quite all the way true because use of shadow DOM and custom elements was not a requirement.↩︎

  3. The defaults I’m referring to is what happens with a tag like {{ foo }} where the contents of the variable foo would be inserted into the document. Nunjucks will treat the contents as unsafe and requires manually opting into unsafe injection via {{ foo | safe }}. Liquid will insert the contents with no escaping.↩︎

Last modified: December 13, 2024