Zach’s ugly mug (his face)

Zach Leatherman

Making a Simple Web Site with the Simplest Static Site Generator, Level 2—Adding Filters

26 Jan 2018 Zach Leatherman 15 min read
Introducing Eleventy, the simplest and most intuitive static site generator. With Eleventy, you can build data-driven sites quickly and easily — focused on long lasting content that’s easy to maintain. Make your website last 10 years, not 10 months.

This is Part 2 in a continuing series describing the basics of the Eleventy static site generator. Read part one of the series: Making a Simple Web Site with the Simplest Static Site Generator, Level 1.

Last time on the show, we made a very simple template that looped over a few image files and displayed links to each. We’re making a web site for our GIF collection and we’re calling it Giffleball.

Full finished source code for Level 2 of this tutorial is available on GitHub.

Let’s add a filter

Let’s do something fancy. Let’s display the file size of each of the GIFs next to their link. To do this, we can make a filter. Filters are added inside of the config file, so let’s make one—an .eleventy.js file. It should look like this:

module.exports = (function(eleventyConfig) {
});

You don’t have to use .eleventy.js but every time you run eleventy you’ll have to pass in the config file name with --config=myConfig.js. So, it’s easier to use the default.

Let’s add our filter with the .addFilter method. We’ll call it filesize and it will just return some dummy text.

module.exports = (function(eleventyConfig) {
eleventyConfig.addFilter("filesize", function(path) {
return "0 KB";
});
});

Obviously this isn’t correct because we’re just returning "0 KB" every time. But let’s get it working first.

Open up our index.html template and look for our list loop. Here’s what it looked like when we finished last time:

<ul>
{% for filename in images %}
<li><a href="img/{{ filename | url_encode }}">{{ filename }}</a></li>
{% endfor %}
</ul>

Notice how we’re using a built-in filter url_encode provided by the Liquid templating engine. Yeah, we made our own though, so let’s add some stuff to call the filter we made, like this:

<ul>
{% for filename in images %}
{% capture path %}img/{{ filename }}{% endcapture %}
<li><a href="img/{{ filename | url_encode }}">{{ filename }}</a> {{ path | filesize }}</li>
{% endfor %}
</ul>

The magic part is {{ path | filesize }}, of course. But also note that we’re using the {% capture %} Liquid tag to create a new path variable in Liquid that gets passed into our filter.

Now, run eleventy to generate the templates.

~/giffleball $ eleventy --formats=html,gif,jpg
Writing _site/index.html from ./index.html.
Wrote 1 file in 0.07 seconds

This outputs the following markup to _site/index.html (we’re only showing the list output here and not the full HTML output for brevity):

<ul>
<li><a href="img/%3F%3F%3F.jpg">???.jpg</a> 0 KB</li>
<li><a href="img/%E2%80%A6.jpg">….jpg</a> 0 KB</li>
<li><a href="img/parrot.gif">parrot.gif</a> 0 KB</li>
</ul>

Okay, that’s close—but why does it have extra whitespace? (Take special care to note that this is a rhetorical question and if you keep reading I will provide you with the answer.) When Liquid processes templates, it doesn’t remove line breaks and spacing around Liquid tags. Luckily, it does provide tools to control whitespace! Instead of{%, use {%- to remove the whitespace before the Liquid tag. And independently, instead of %} you can also use -%} at the end to remove whitespace after the Liquid tag. One or the other. Both. Personally, I think it looks the best with just {%- at the beginning. A clean View Source is important to me, so let’s clean it up:

<ul>
{%- for filename in images %}
{%- capture path %}img/{{ filename }}{% endcapture %}
<li><a href="img/{{ filename | url_encode }}">{{ filename }}</a> {{ path | filesize }}</li>
{%- endfor %}
</ul>

This outputs:

<ul>
<li><a href="img/%3F%3F%3F.jpg">???.jpg</a> 0 KB</li>
<li><a href="img/%E2%80%A6.jpg">….jpg</a> 0 KB</li>
<li><a href="img/parrot.gif">parrot.gif</a> 0 KB</li>
</ul>

Beautiful.

Don’t celebrate yet, our filter doesn’t work

Okay, let’s make our filter actually work instead of just returning "0 KB" all the time. Change our .eleventy.js file to this:

const fs = require("fs");
module.exports = (function(eleventyConfig) {
eleventyConfig.addFilter("filesize", function(path) {
let stat = fs.statSync(path);
if( stat ) {
return (stat.size/1024).toFixed(2) + " KB";
}
return "";
});
});

This is the simplest thing that can work, it does not require any other dependencies with npm install.

Go further with NPM

One of the best benefits of Eleventy over Static Site Generators like Jekyll or Hugo is access to the NPM ecosystem. So many great modules. So if you’re brave enough to do some npm magic, run this to create a package.json for our project:

~/giffleball $ npm init -f

Now we can start installing cool modules to our project, like file-size for better readable file sizes.

~/giffleball $ npm install --save file-size
+ file-size@1.0.0
added 1 package in 1.491s

Let’s use it in our .eleventy.js filter code:

const fs = require("fs");
const filesize = require("file-size");
module.exports = (function(eleventyConfig) {
eleventyConfig.addFilter("filesize", function(path) {
let stat = fs.statSync(path);
if( stat ) {
return filesize(stat.size).human();
}
return "";
});
});

Outputs:

<ul>
<li><a href="img/%3F%3F%3F.jpg">???.jpg</a> 44.52 KiB</li>
<li><a href="img/%E2%80%A6.jpg">….jpg</a> 55.39 KiB</li>
<li><a href="img/parrot.gif">parrot.gif</a> 2.05 KiB</li>
</ul>

Congratulations! You added a filter that tapped into the vast and wide NPM ecosystem.

Wrapping Up

I hope you see the great power in using filters in our templates. They can transform simple content with the great power of the NPM ecosystem. I know I said Level 2 would be Layouts, but we’ll make it to Layouts and multiple templates in Level 3.

Eleventy Tutorials