Yesterday a follower on Twitter encountered an interesting issue with Eleventy that turned into a bit of a bigger issue. Let's start with his question.

The where filter in Liquid provides a simple way to select values in an array by simple property matching. So consider this array:

[
	{"name":"Fred","gender":"male"},
	{"name":"Ginger","gender":"female"},
	{"name":"Bob","gender":"male"},
	{"name":"Lindy","gender":"female"}
]

I've got four cats with names and genders. By using the where filter on gender, I could select different cats like so:

{% assign male_cats = cats | where: "gender", "male" %}
{% assign female_cats = cats | where: "gender", "female" %}

<h3>Male Cats</h3>
{% for cat in male_cats %}
	{{ cat.name }}, {{ cat.gender }}<br/>
{% endfor %}

<p/>

<h3>Female Cats</h3>
{% for cat in female_cats %}
	{{ cat.name }}, {{ cat.gender }}<br/>
{% endfor %}

If you run this in Eleventy though, you get this:

All cats, not filtered

The assign works fine, but there's no filtering.

Why?

Turns out Eleventy ships with an older version of the Liquid template engine. This then leads to the question, how do you know what version Eleventy ships with? If you go to the docs for Liquid in Eleventy, you'll see it isn't mentioned. I raised an issue on this saying the docs should make it more clear (for each engine obviously). It could actually be in the docs and I don't see it of course.

Luckily though you can provide your own version of Liquid (or Nunjucks, or Handlebars, etc) by using eleventyConfig.setLibrary in your .eleventy.js file. The docs show this example:

module.exports = function(eleventyConfig) {
  let liquidJs = require("liquidjs");
  let options = {
    extname: ".liquid",
    dynamicPartials: true,
    strict_filters: true,
    root: ["_includes"]
  };

  eleventyConfig.setLibrary("liquid", liquidJs(options));
};

I gave this a shot. I made a new directory, did npm i liquidjs, and tried this code, but it threw an error. I checked the docs for liquidjs and saw that their initialization code was a bit different. I copied their code and ended up with this:

module.exports = eleventyConfig => {
	let { Liquid } = require('liquidjs');
	let engine = new Liquid();

	eleventyConfig.setLibrary("liquid", engine);

}
All cats, filtered

Woot! But huge caveat here. Eleventy passes in it's own default options for Liquid. In my sample above I passed none so I'm using the liquidjs defaults instead. This could lead to backwards compatibility issues. This is discussed in another issue.

So what version of Liquid does Eleventy ship? The user @DirtyF commented that by using npm outdated in a repo with Eleventy you can see the following:

Package Current Wanted Latest Location ejs 2.7.4 2.7.4 3.0.1 @11ty/eleventy handlebars 4.7.1 4.7.3 4.7.3 @11ty/eleventy liquidjs 6.4.3 6.4.3 9.6.2 @11ty/eleventy mustache 2.3.2 2.3.2 4.0.0 @11ty/eleventy

You could use this as a way to figure out exactly what features you have available when using your desired template language.

As I raised in my issue, I think Eleventy needs some kind of "statement" or plan about how it does upgrades, when/how it handles backwards compatibility, etc. I don't think there is an easy solution for this but I'm hoping to be able to help the project with this effort. (If you can't tell, I'm rather enamored with it. ;)

An Alternative #

So what if you don't want to muck with how Liquid works in Eleventy? Well you've got options, lots of em!

One way is to just use a conditional:

{% for cat in cats %}
	{% if cat.gender == "female" %}
	{{ cat.name }}, {{ cat.gender }}<br/>
	{% endif %}
{% endfor %}

While this implies looping over every record, keep in mind this is only done in development. In production it's just a plain static HTML file.

Another option is to use filters. Liquid filters support arguments, so you could build this generic utility:

eleventyConfig.addFilter("where2", function(value, prop, val) {
	// assumes value is an array
	return value.filter(p => p[prop] == val);
});

I named it where2 just for testing but you would probably want something else. This lets you use the same format that the newer Liquid uses:

{% assign test_cats = cats | where2: "gender", "female" %}

Finally, as yet another option, consider switching engines. What do I mean by that? While Liquid is definitely my preferred engine, EJS is incredibly flexible when it comes to code in your template. To be honest, it's too flexibly imo and encourages you to do stuff in your templates I think you should do elsewhere. But that flexibility could be a lifesaver, and one of the most awesome features of Eleventy is that you can easily switch one document to another engine by just changing the extension.

Header photo by Daniel Levis Pelusi on Unsplash