Possum websites, done possumly

Part 8: Includes and macros

10 minute read time / 1211 words

Now that most our site is in place, we can start looking to be a bit more clever about how we do things.

Recap

In our last part, we looked at adding and linking to category pages via tagging, but we discovered a problem: our code was getting less maintainable because we were repeating ourselves often.

Macros and includes are the perfect way to split code up nicely, make things more maintainable and repeatable and generally make working with Nunjucks a much nicer, smoother experience.

Includes

An include can be seen as a 'partial' template, or a 'component' of a page. It's just a Nunjucks file that contains some code that you can 'include' in another Nunjucks file when you need it. It's a simple way to have repeatable code without having to actually maintain several versions of that code across multiple files.

Using includes is very simple. In a content or layout file (or even inside another include!) just call the Nunjucks include method and tell it what partial file to include. That's it. Honestly.

Let's try it out. Cut this code from one of the three templates using it, and paste it into a new file called tagList.njk and save that into our /src/site/includes folder.

<h4>Tagged as:</h4>

<ul class="tags">
    {%- for tag in tagsList -%}
    <li>
        <a href="/posts/tagged/{{ tag | slugify }}/" class="tag tag--{{ tag | lower | replace(' ', '') }}">
            {{ tag }}
        </a>
    </li>
    {%- endfor -%}
</ul>

In our posts, article and categories template, we can now replace that block of code with our new include:

{% include 'tagList.njk' %}

We also need to make sure we set the tagsList variable that the include will expect in each template. It already exists in our posts and categories templates, but not on the individual article. You'll need to add the following before you include the tagList to make it work:

{% set tagsList = categories %}

Not only can we use includes to make repeatable sections in our pages and layouts, we can also use them to make areas of our code more maintainable by being smaller chunks.

Let's say we have a big layout template with quite a lot of lines of code in it. It's just one file, and it's our main layout file so we don't have any repeating code issues here, but how much easier would it be to be able to just take blocks of the code out into their own include files to make it so simple to read that the cognitive load for working with that base template is reduced massively.

Our base template looks like this at the moment:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>My Sexy Eleventy Site</title>
        <meta name="viewport" content="width=device-width">
    </head>

    <body>
        <nav>
            <ul>
                {% set pages = collections.all | eleventyNavigation %}

                {%- for entry in pages %}
                    <li>
                        <a href="{{ entry.url }}">
                            {{ entry.title }}
                        </a>
                    </li>
                {% endfor %}
            </ul>
        </nav>

        <h1>{{ title }}</h1>
        {{ content | safe }}
    </body>
</html>

This is before we've added our social sharing metadata to the <head>, or put a logo, strapline, footer, any images, or even our page CSS styles to the template. Let's face it, before we're done this file is going to be at least double or triple the number of lines of code.

So how about we pre-emptively fix that before it's ever a problem, and move blocks of our base template out into partials? Create some new files in the /src/site/includes folder called head.njk. header.njk, footer.njk, and nav.njk.

In the head include, take everything from our base template that's inbetween the <head> and </head> tags.

<meta charset="utf-8">
<title>My Sexy Eleventy Site</title>
<meta name="viewport" content="width=device-width">

For the header includes, let's create a simple site header to start since we don't already have one:

<header>
    <a href="/">My Sexy Eleventy Site</a>

    <p>
        I made this!
    </p>
</header>

Similarly with the footer, let's go ahead and create something simple to start with as we don't already have one:

<footer>
    &copy; Me! Because I made this.
</footer>

Finally (for now), let's put all our global nav code into the nav include.

<nav>
    <ul>
        {% set pages = collections.all | eleventyNavigation %}

        {%- for entry in pages %}
            <li>
                <a href="{{ entry.url }}">
                    {{ entry.title }}
                </a>
            </li>
        {% endfor %}
    </ul>
</nav>

Visit your local site, and now you should get all these compiled into a template nicely for your consumption.

Macros

Sometimes, we could do with some repeatable code that doesn't have the exact same output everytime we use it. A bit like a JavaScript function, only a dynamic partial template in Nunjucks instead.

This is where macros come in. Let's understand more by just diving in and using them. Let's take our nav include: in here we have a loop that goes through each page in the navigation and outputs a new item. We can take the inner part of that loop and create a re-usable macro from it so we can use it anywhere we need to output a list item with a link to a page.

Open up our nav include, and grab this bit of code from it:

<li>
    <a href="">
        
    </a>
</li>

Create a new file in /src/includes/macros/ called navItem.njk and update our code from above to look like this:

{% macro navItem(entry) %}
<li>
    <a href="{{ entry.url }}">
        {{ entry.title }}
    </a>
</li>
{% endmacro %}

All we've done here is take our known working code and wrapped it in a {% macro %} block.

To make it actually useful, we can now import this macro into our nav include, and then use it for every loop iteration for our nav links, like this:

{% from 'macros/navItem.njk' import navItem %}

<nav>
    <ul>
        {% set pages = collections.all | eleventyNavigation %}

        {%- for entry in pages %}
            {{ navItem(entry) }}
        {% endfor %}
    </ul>
</nav>

Check your running site - everything should look exactly the same as before! Only now, we've got a macro set up we are making our site easier to maintain and to build on in the future. If we decide we want to add some links into a footer menu (the obvious ones like terms, accessibility, privacy and cookie policy, etc.) then we can - easily.

We can also use a macro for our tag output include. Let's do that now! Open up /src/includes/tagList.njk and grab the individual tag output section. Paste that into a new file and save it as /src/includes/macros/tagItem.njk with this content:

{% macro tagItem(item) %}
<a
    href="/posts/tagged/{{ item | slugify }}/"
    class="tag tag--{{ item | lower | replace(' ', '') }}"
>
    {{ item }}
</a>
{% endmacro %}

Now in our tagList include, we can import the tagItem macro, and we can use it just like we did with the navItem macro.

<h4>Tagged as:</h4>

<ul class="tags">
    {% from 'macros/tagItem.njk' import tagItem %}

    {%- for tag in tagsList -%}
    <li>
        {{ tagItem(tag) }}
    </li>
    {%- endfor -%}
</ul>