Enriching Eleventy data

Almost two years ago I wrote about how I got started with Eleventy. I wrapped up the post by indicating that I’d write a follow-up explaining how I’d combined data with the front matter and content. And… then I forgot about it.

In the time since then, the project I used as an example had partially been moved back to a PHP application since I needed to capture and search data while not behind a computer. How I’d combined the data still turned out to be useful in the other projects I eventually migrated to Eleventy though.

Let’s start with the simplest example: author data. Every post or article on a site usually has an author associated with it, but capturing the author’s details, again and again, isn’t only tedious but will eventually lead to mismatched data somewhere. The solution for this (and eventually quite a few other fields) is to capture a unique ID instead — something like a slug — and enrich it from a central data store.

The front matter:

---
layout: post.njk
title: Brilliant blog post
date: 2022-04-02
author: barry-mieny
---
...

The data file:

[
  {
    "slug": "barry-mieny",
    "name": "Barry Mieny",
    "uri": "https://barry.mieny.com/"
  },
  ...
]

And then gluing everything together in eleventyComputed.js:

module.exports = {
  ...
  author: (data) => {
    if (data.author && typeof(data.author) == "string") {
      return getBySlug(data.authors, data.author) ?? data.author
    }
  },
  ...
}

function getBySlug(collection, slug) {
  return collection.filter(a => a.slug === slug)[0] ?? slug
}

This leaves you with the enriched front matter and if no matching entry was found, you still end up with the slug instead of an empty value:

---
layout: post.njk
title: Brilliant blog post
date: 2022-04-02
author:
  slug: barry-mieny
  name: Barry Mieny
  uri: https://barry.mieny.com/
---
...

This enrichment isn’t confined to front matter and works equally well for data files. A few of the projects make use of data files for information about music releases with common data for things like formats (CD, vinyl, etc) or countries.

{
  "formats": [
    {
      "type": "vinyl-7",
      "manufactured": "SE",
      "released": [
        "SE"
      ],
      ...
    }
  ]
}

The exact same principles apply:

module.exports = {
  ...
  formats: (data) => {
    if (data.formats) {
      const formats = data.formats

      formats.forEach(format => {
        format.type = getBySlug(data.types, format.type)
        if (format.manufactured) {
          format.manufactured = data.countries.filter(c => c.code == format.manufactured)[0]?.name
        }
        if (format.released) {
          format.released = format.released.map(country => data.countries.filter(c => c.code == country)[0]?.name)
        }
        ...
      })

      return formats
    }
  }
  ...
}

The result is the same — enriched data which can be used directly in your templates while keeping maintenance of your data files easy:

{
  "formats": [
    {
      "type": "7″ Vinyl",
      "manufactured": "Sweden",
      "released": [
        "Sweden"
      ],
      ...
    }
  ]
}

There’s another post to be written about how I combined “real” content with data that only exists in collections. Hopefully, that won’t take another 22 months.