Four Killer Features of Nunjucks

Avatar of Chris Coyier
Chris Coyier on

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

Nunjucks calls itself “A rich and powerful templating language for JavaScript”, which sounds about right. It’s not intentionally super lightweight like Mustache or the slightly more robust (but still pretty light) Handlebars. It’s a full-on language, packed with all kinds of stuff you might want when writing templates.

You can run it in the browser, but you probably shouldn’t. This is meant to be run in Node.js and used to compile templates server side.

In other words: it’s a real fancy HTML preprocessor. Let’s look at some features that I think are particularly cool about Nunjucks.

Fair warning: this is highly subjective and based on only light experience! I’m only using like 10% of what Nunjucks is capable of here.

Nunjucks is a Node thing, so you install it with npm and work with it through the command line, build tools, and that whole world.

Here’s a single screenshot showing me run a Node script that renders a Nunjucks template:

I’ve run my Node script, which logs to the connsole the results of nunjucks.render()

1. It’s just HTML

Notice that the file we passed to nunjucks.render() was really just HTML with {{ handlebars }}-like templating syntax inside. I named it `index.njk` but that isn’t really necessary, I just like being explicit about intention.

I’d bet there is a good contingent of front-end devs that prefer working in HTML even when the HTML is ultimately processed. I like Pug sometimes, but it’s a whitespace dependent language all to it’s own. Preferring Nunjucks is kinda like preferring ERB to HAML.

Here’s a perfectly legit template:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>{{ page_title }}</title>
</head>

<body>

  {% for feature in features %}
    <div class="module">
      <h3>{{ feature.name }}</h3>
      <p>{{ feature.description }}</p>
    </div>
  {% endfor %}

</body>

</html>

What is features? Data! You pass it to the render function.

nunjucks.render(
  'index.njk', {
    page_title: "Cool Product",
    features: [
      {
        name: "Speed",
        description: "It's fast."
      },
      {
        name: "Reliability",
        description: "You can count on it."
      },
      {
        name: "Security",
        description: "You don't have to worry about it."
      }
    ]
  }
);

You can imagine that data coming from a database or an API, I’m sure. You can also define data right in the view, as well, if needed.

<div>
  {% set foo = "bar" %}
  {{ foo }}
</div>

2. Includes

Sometimes I use a language just for the includes. For example, CodeKit has a language that is pretty much just for includes, because they know how useful they are.

Here’s how simple includes are in Nunjucks:

<body>

  {% include "_header.njk" %}

  <main>
    ...
  </main>

  {% include "_footer.njk" %}

</body>

3. Extends / Blocks

Extends take includes to the next level. Extends allow you to define a template document with “blocks” inside of it that are meant to take chunks of content.

Here’s a template with some includes, but also a block right in the middle:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>{{ page_title }}</title>
</head>

<body>

  {% include "parts/_header.njk" %}

  {% block main %}
    This is the default content
  {% endblock %}

  {% include "parts/_footer.njk" %}

</body>

</html>

Now any other file can extend that template, and not need to worry about all that boilerplate HTML that comes along with likely all pages of the site. Our `index.njk`, for instance, becomes:

{% extends "parts/_template.njk" %}

{% block main %}
  {% for feature in features %}
    <div class="module">
      <h3>{{ feature.name }}</h3>
      <p>{{ feature.description }}</p>
    </div>
  {% endfor %}
{% endblock %}

I bet you can imagine what fleshing that out looks like. Perhaps multiple templates for different types of pages. More blocks, gives yourself an opportunity to pass in additional stylesheet or scripts. More blocks for areas of the site, like what goes in the sidebar and footer as well.

4. Macros

Speaking of taking templating to the next level, macros do that yet again.

Macros are like imports with parameters. Like functions! You give it some values and it returns some stuff for you.

Imagine a module that takes three values. For example sake, say a “color swatch” (as if we’re building a pattern library), that takes the color value, name, and notes.

We could build that like this:

{% macro cardSwatch(colorName, colorValue, colorNotes) %}
  <div class="color-swatch-tile">
    
    <div class="color-swatch"
         style="background-color: {{ colorValue }};">
    </div>
    
    <div class="color-name">
      {{ colorName }}
    </div>
    
    <div class="color-notes">
      {{ colorNotes }}
    </div>
    
  </div>
{% endmacro %}

Now I can use that over and over:

{{ cardSwatch("brandColor", "#f06d06", "Our main color.") }}
{{ cardSwatch("brandHighlight", "#d0b000", "For callouts and highlights.") }}
{{ cardSwatch("grayDark", "#333333", "For things like code block backgrounds.") }}

Better still, I can move the macros into their own file/files and import them as needed. Here, I’ll import a macro, extend a template, and loop through colors data calling a macro:

{% from "macros/swatch.njk" import swatch %}

{% extends "parts/_template.njk" %}

{% block main %}

  {% for color in colors %}
    {{ swatch(color.color_name, color.color_value, color.color_notes) }}
  {% endfor %}

{% endblock %}

CodePen Projects Supports Nunjucks

Yes indeed! Which is nice, because it requires zero setup. (You know about CodePen Projects, right?) Just name a file ending in `.njk` and CodePen will know to process it as Nunjucks.

Here’s one to check out.

Nunjucks with Gulp

Working locally with Nunjucks, you’ll almost certainly want a build tool like Gulp to help process the files. Fortunately there is gulp-nunjucks to make it even easier.

gulp.task('default', () =>
  gulp.src('index.html')
    .pipe(nunjucks.compile({
      my_data: "is here"
    }))
    .pipe(gulp.dest('dist'))
);

Repo

I created one as I played with Nunjucks in the writing of this article.

Other Information