Schemescape

Development log of a life-long coder

Test-driving Eleventy for a simple dev blog

I'm testing out the most promising static site generators to see which is closest to my ideal setup. Next up is Eleventy.

Eleventy

Eleventy is a Node-based static site generator that seems to be popular with people who would rather use JavaScript instead of Ruby/Jekyll.

Installation

Installation is done via NPM. Obnoxiously, if you don't install globally, there is a non-Eleventy NPM package named "eleventy" that is picked up by default, so you have to use npx @11ty/eleventy ... for commands. This is the recommended approach and I find it very awkward to type (NPM scripts seem like a good mitigating practice). At least the other package doesn't appear to obviously be name squatting malware!

Setup

Configuration of input/output directories, etc. is done on the command line or in a JavaScript config file (yes, it's real JavaScript code). Their sample feels a bit convoluted for what amounts to JSON:

module.exports = function(eleventyConfig) {
  // Return your Object options:
  return {
    dir: {
      input: "views",
      output: "dist"
    }
  }
};

I was curious what all might go in an Eleventy configuration file, so I took a peek at their official blog sample and was horrified to see 142 lines of JavaScript code in their .eleventy.js config file. I thought this was supposed to be the simplest static site generator! Looking more closely, there appear to be helper functions defined in this configuration file. I'm not going to question their design yet, but it makes me nervous.

Concepts

Eleventy's documentation has lots of details, but there isn't a great overview of all the concepts and the associated directory structure. For example, I still haven't found a clear definition for their use of the word "template".

I'm far from an expert, but here is my understanding of Eleventy's concepts:

I found Eleventy's terminology to be unintuitive (templates vs. layouts vs. includes), but the concepts make sense. The cascading data model is particularly elegant.

The default directory structure for a blog could look like this:

First run

Eleventy's directory structure initially seemed simpler than Hugo's, but as I dove in, I found that it muddled distinct concepts and I ended up having to change the configuration quite a bit to give me what I wanted. Because I wanted to separate content from everything else, I ended up with a .eleventy.js configuration file that looked like this:

module.exports = function(eleventyConfig) {
    // Copy everything under "static" to the root of the built site (note: this is relative to this config file)
    eleventyConfig.addPassthroughCopy({ "static": "./" });

    return {
        dir: {
            input: "content",
            output: "out",
            includes: "../templates" // Note: this is relative to the input directory
        }
    }
};

My corresponding directory structure is visible on the eleventy branch of this repository on GitHub.

Themes?

My experience with Hugo themes wasn't great because they push hard to make you want to share your theme. Eleventy is basically the opposite. Because configuration is JavaScript code run in Node, you probably don't want to download others' themes unless you've gone through the code to make sure it does only what you want.

Not supporting themes in a safe manner could be a downside for people who don't want to create their own templates, but it's fine for me because I want to craft the HTML myself to ensure it has only what I want (simple HTML) and nothing that I don't (e.g. social media icons).

JavaScript templates

Originally, I planned to use a standard template system because that knowledge would be portable across different static site generators. But as I looked at examples of Hugo's templates or Liquid templates, I came to the conclusion that these template languages are surprisingly ugly and unfamiliar. Eleventy supports plain old JavaScript templates which (security concerns aside, since I'm writing my own templates) is convenient because it's just JavaScript. Sure, it's is a quirky language, but with template literals, it's surprisingly readable (although you have to take care to properly escape everything):

module.exports = function(data) {
    return `<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>Blog: ${data.title}</title>
    </head>
    <body>
        <main>
            <header><h1>My Blog</h1></header>
            <article>
                <header>
                    <h1>${data.title}</h1>
                    <p>Date: <time>${data.page.date}</time></p>
                </header>
                ${data.content}
            </article>
        </main>
    </body>
</html>
`
}

Escaping HTML

Note: when using Eleventy's JavaScript template language, I didn't see a built-in, documented way to escape arbitrary text for inserting into HTML. The escape-html NPM Package worked for my purposes, but now I feel like I'm getting close to wanting to build my own template language.

Issues

I'm using relative links to *.md files for linking between posts, but (at least with the default configuration), those links aren't being being updated to point to the resulting HTML files. Note: I'm not implying that Eleventy told me this would work -- it's just part of my desired workflow.

Markdown post-processed by default

In one of my posts, written in Markdown, I show Liquid/Tera/Jinja syntax (e.g. {%) inside a code block. With my mostly-default setup, Eleventy tried to interpret this as template syntax, which is definitely not what I would have expected (it's Markdown, not a template!). Fortunately, you can disable this behavior with the markdownTemplateEngine: false setting.

Page URLs aren't relative

This problem isn't unique to Eleventy (and arguably it's easier to handle in Eleventy's JavaScript template language), but Eleventy doesn't supply relative links to posts -- the page.url property starts with /. This prevents you from just viewing the HTML files directly from the file system (and it also makes it difficult to host a site in a subdirectory).

In my case, since it's just JavaScript code hosted in Node anyway, I'm using Node's path.posix API to compute the relative path to the site root.

Let's stop there

That's probably enough for one post.

Overall, ramping up on Eleventy was more confusing than I anticipated, but all the pain I felt around Hugo's template language was partially avoided in Eleventy by the ability to use good old JavaScript code. I'm not a fan of some of the design decisions (namely, drawing content from the root folder), but I feel like I could actually end up choosing Eleventy just because it prevents me from having to learn (and, later, remember) another programming language.

Update: I fully integrated Eleventy into my dev blog, details are in part 2.