Includes and Macros

When you’re working with Nunjucks (or Jinja2), you have two[1] options for creating reusable bits of template: includes (I often refer to these as “partials”) and macros. It’s not always obvious why you should choose one over another. Just about anything you can do with a macro can also be done with a template include[2]. The heuristic I follow when I’m trying to decide how to organize my templates is:

Put another way: if I find myself having to use {% set %} to configure the context for a partial before I {% include %} that partial, it should probably be a macro.

I’m going to walk through an example of when I would use each to illustrate how I use them in my templates.

Includes

Includes are the simplest of the two encapsulation mechanisms. All you have to do is write a fragment of a template in a file, and then you can use the {% include %} tag in another template and Nunjucks will replace that tag with the contents of the fragment. An example might help to make this more clear.

Suppose I want to encapsulate the markup for my site header so that I can use it in multiple templates. I might create a template fragment that looks like this:

<header>
<a href="/">{{ site.title }}</a>
<nav aria-label="main">
<ul>
{% for link in navigation %}
<li><a href="{{ link.href }}">{{ link.text }}</a></li>
{% endfor %}
</ul>
</nav>
</header>
templates/includes/site-header.njk

Now, I can include that fragment in any page template. So, for example, if I have a template that I use to render each of my blog posts, it might look something like this:

<!DOCTYPE html>
<html>
<body>
{% include "./includes/site-header.njk" %}
<h1>{{ title | safe }}</h1>
{{ content | safe }}
</body>
</html>
templates/post.njk

Et voilà, all of the markup for my site header gets put in every blog post. If I need the site header in another page, I just have to {% include "./includes/site-header.njk" %} in that template wherever I want the site header to appear in the markup.

When you include a template fragment in Nunjucks, it is interpreted using the same context that is present in the calling template at the time the fragment was included. So in my site-header.njk partial, I’m making the following assumptions about the context:

Judging by my use of the fragment from post.njk, one might conclude that site and navigation are both global variables provided by whatever application is rendering the template, since you don’t see those variables being set in the template itself. If you’re using a static site generator (like Eleventy), there is often a mechanism for you to define your own global variables that will be added to the context of every template that is rendered, and there is also usually a set of common variables that the static site generator adds to every context.

Now what if I have a design for a card component that I want to use to list all of my blog posts. I could use a template fragment that gets included in a loop that iterates over all of my blog posts. That partial might look like this:

<article class="card">
<h2 class="card__title">
<a href="{{ cardUrl }}">{{ cardTitle | safe }}</a>
</h2>
<div class="card__media">
<img src="{{ cardMedia }}" alt="{{ cardMediaAlt }}" />
</div>
<div class="card__body">
{{ cardBody | safe }}
</div>
</article>
templates/includes/card.njk

This include assumes the presence of cardUrl, cardTitle, cardMedia, cardMediaAlt, and cardBody in the context when it is included. Since I am going to be changing each of these variables for each post in a list of posts, I will need to do something like this:

<!DOCTYPE html>
<html>
<body>
{% include "./includes/site-header.njk" %}

<section aria-label="posts">
{% for post in posts %}
{% set cardTitle = post.title %}
{% set cardUrl = post.url %}
{% set cardMedia = post.data.media.src %}
{% set cardMediaAlt = post.data.media.alt %}
{% set cardBody = post.data.description %}

{% include "./includes/card.njk" %}
{% endfor %}
</section>
</body>
</html>
templates/home.njk

On each iteration of the loop I have to set five variables in the current context before I include the card fragment. This is how I “pass” parameters to the template fragment when I include it.

I’ve seen a lot of people do this with Nunjucks. And, really, there’s nothing wrong with it. The only thing that might cause some odd behavior is that at the end of this loop, you have five variables in your context that are still set to the values from the last post that was evaluated. If you happened to reuse those same variables for a different purpose and forgot to change or unset them, you could get some unexpected content in your page, but that’s about it.

That said, I think there’s a better way to handle situations like this: macros.

Macros

Macros are a bit like functions for Nunjucks. They don’t receive the context of the calling template, so they have a local scope; they take parameters; you import them. If you don’t know much about using macros in Nunjucks, I recommend having a look at Thomas Semmler’s writeup on using macros in Eleventy.

Now, let’s have a look at how we can convert our card template into a macro. We can start by just by wrapping the HTML in a {% macro %} tag.

{% macro card(title, url, media, mediaAlt, body) %}
<article class="card">
<h2 class="card__title">
<a href="{{ url }}">{{ title | safe }}</a>
</h2>
<div class="card__media">
<img src="{{ media }}" alt="{{ mediaAlt }}" />
</div>
<div class="card__body">
{{ body | safe }}
</div>
</article>
{% endmacro %}
templates/includes/card.njk

There are only two differences between this new macro and our old template partial:

  1. The HTML is wrapped in a {% macro %}{% endmacro %} Nunjucks tag
  2. I’ve removed the card* prefix from all the variable names, since the variables are scoped to the macro

The {% macro %} tag acts a lot like the function keyword in JavaScript. You could almost think of the above as:

function card(title, url, media, mediaAlt, body) {
return `<article class="card">
<h2 class="card__title">
...
</article>
`
;
}
The card macro written as a JavaScript function

Now that we have defined our macro, we have to import it before we can call it.

{% import "./includes/card.njk" as card %}
Importing the card macro

And then we invoke the macro like it’s a function inside of a Nunjucks variable reference.

{{ card.card(post.title, post.url, post.media.src, post.media.alt, post.description) }}
Calling the card macro in a template

Now, I want to do one last thing before showing how to change the original template that relied on {% include %}, because I dislike the card.card() call—it seems silly and redundant to me. You can collect multiple macros into a single file (unlike template partials, which are one partial per-file) that are all accessible from a single import, so I’m going to rename the includes/card.njk file to macros/components.njk. This keeps my macros separate from my template partials, and gives me a single place to collect my component macros.

Now, with our macro in place, our template for the home page becomes this:

{% import "./macros/components.njk" as components %}
<!DOCTYPE html>
<html>
<body>
{% include "./includes/site-header.njk" %}

<section aria-label="posts">
{% for post in posts %}
{{ components.card(post.title, post.url, post.media.src, post.media.alt, post.description) }}
{% endfor %}
</section>
</body>
</html>
templates/home.njk

The first change is that we import the file containing our macros. The next change is that we simply call that macro and pass whatever information we want for each parameter.

Personally, I like this much better. It’s more succinct, and doesn’t add a bunch of additional variables to my template context.

Call Blocks

I have one last tweak I’d like to make to our macro to make it a little more flexible. Suppose we want to use this component in our portfolio, where the description is a simple paragraph, but we also use it for our recent blog posts on the home page, where we want to include the description and the publication date in separate paragraphs. Our portfolio template might involve something like:

{% for p in projects %}
{{ components.card(p.title, p.url, p.screenshot.src, p.screenshot.alt, p.summary) }}
{% endfor %}
A simple example of invoking the card macro where the card body is contained in a single property of our object

But our recent posts needs to construct a custom card body, so we might do something like this:

{% for post in recent_posts %}
{% set postBody %}
<p>{{ post.description | safe }}</p>
<p>Published: {{ post.pub_date }}</p>
{% endset %}

{{ components.card(post.title, post.url, post.media.src, post.media.alt, postBody) }}
{% endfor %}
Creating a body for the card comprised of multiple properties from our object

And now we’re back to using {% set %} again…

But Nunjucks offers an alternate syntax for invoking a macro called a call block. Call blocks have start and end tags, and anything you put between the tags gets passed to the macro. So the first thing we have to do is update our card macro to use the contents of the call block. We do this by putting {{ caller() }} in our macro where we want that content to appear.

{% macro card(title, url, media, mediaAlt) %}
<article class="card">
<h2 class="card__title">
<a href="{{ url }}">{{ title | safe }}</a>
</h2>
<div class="card__media">
<img src="{{ media }}" alt="{{ mediaAlt }}" />
</div>
<div class="card__body">
{{ caller() }}
</div>
</article>
{% endmacro %}
templates/macros/components.njk

There are only two changes to the card macro:

  1. I removed the body parameter from the macro
  2. I replaced {{ body | safe }} with {{ caller() }}

Now, when we want to create a card, we use a call block:

{% for post in recent_posts %}
{% call components.card(post.title, post.url, post.media.src, post.media.alt) %}
<p>{{ post.description | safe }}</p>
<p>Published: {{ post.pub_date }}</p>
{% endcall %}
{% endfor %}
Invoking the macro using a {% call %} block
{% for p in projects %}
{% call components.card(p.title, p.url, p.screenshot.src, p.screenshot.alt) %}
<p>p.summary</p>
{% endcall %}
{% endfor %}
For the portfolio, we just put the summary between the {% call %}{% endcall %} tags

Now we’re done with our card macro, I think.

Choosing the Right Tool

At the end of the day, you should use whatever mechanism for encapsulating components that works for you. I’m not trying to convince you that it’s wrong to {% set %} a bunch of variables before you {% include %} a template fragment. You should organize your code in a way that makes sense to you and works for you. But if you’ve ever been a bit perplexed about the differences between a macro and an include, or unsure of which one you should use in any given situation, hopefully this helps you decide one way or the other.

Further Reading


  1. Well, I suppose you could say three, if you include custom filters as an option, but let’s just ignore that for now. ↩︎

  2. The one exception that comes to mind is recursion. A macro can call itself recursively, but a template cannot recursively include itself. ↩︎