Using Templates (Nunjucks) to Generate Websites

Dr. Greg Bernstein

Updated September 23rd, 2019

Nunjucks Installation and Setup

Start with package.json

In a directory for your project:

  • Use npm init for a script that will ask you questions
  • Minimal package.json:
{
    "name": "Foiling-Club-Template-Example",
    "version": "0.0.1",
}

Using NPM 1

  • Use npm install nunjucks --save
  • This provides a local to the project installation
  • The --save puts nunjucks as a dependency in your package.json file

Using NPM 2

NPM installs can take a while…

Nunjucks Installation
Nunjucks Installation

Using NPM 3

What did installation do?

  • New directory: After Install
  • Updated package.json and new package-lock.json:
{   "name": "Foiling-Club-Template-Example",
    "version": "0.0.1",
    "dependencies": {"nunjucks": "^3.1.7"}}

Using NPM: node_modules

NPM installs locally into the node_modules directory

  • The node_modules directory can get very large. DO NOT commit this directory into git

  • After installing the Nunjucks template library, over 200 folders, and over 800 files were added to the node_modules directory.

Directory Setup

  • Need a directory to hold template files and related stuff. I’ll call this views.
  • Need a directory to hold website content prior to processing. I’ll call this content.
  • Need a directory to hold processed pages (after its run through the template), I’ll call this output

Directory Setup

Directories on my windows laptop:

Directory layout
Directory layout

First Steps

Creating a Base Template with Navigation

  • Let’s separate out navigation menu. This is the same for all pages.
  • Let’s separate out content from HTML “boiler plate”, Content varies with each page.

Nunjucks Features

  • include tag, pulls in content from another file verbatim.

  • Use {{ variable_name }} variable substitution for content that changes.

Example Zipped Repo

Template Design 1

Branch firstTemplate, file: views/base.njk

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Bay Area Windsurf Foiling Club</title>
        <link href="club.css" rel="stylesheet">
    </head>
    <body>
    <nav>
        {% include "mainMenu.html" %}
    </nav>
    <main>
        {{mainContent}}
    </main>
    </body>
</html>

Content File

branch firstTemplate, file:content/index.html just the HTML content and no boiler plate.

<h1>The Bay Area Windsurf Foiling Club</h1>
    <section>
        <h2>Windsurf Foiling</h2>
        <section>
        <figure><img src="images/MikePercyFoiling.jpg" />
          <figcaption><span>Figure 1.</span> Mike Percy foiling off of Berkeley, California.</figcaption>
        </figure>
        <p>What is <em>foiling</em>? This is the practice and art of adding an
underwater <strong>wing</strong> to a water craft which under appropriate
conditions will raise the bulk of the water craft out of the
water thus reducing drag.</p>
</section>

<figure><img src="images/BoardWithFoil.jpg"/>
  <figcaption><span>Figure 2.</span>  Upsidedown windsurf board with foil attached.</figcaption>
</figure>
<!-- And a bunch more -->

Include File

branch firstTemplate, file: views/mainMenu.html file:

<ul>
    <li><a href="index.html">Home</a></li>
    <li><a href="About.html">About</a></li>
    <li><a href="Tides.html">Tides</a></li>
    <li><a href="Membership.html">Membership</a></li>
</ul>

Program to Run Nunjucks

branch: firstTemplate, file: runIt.js

const nunjucks = require('nunjucks');
const fs = require('fs');  // The file system module

// Tells nunjucks where to look for templates and set any options
nunjucks.configure('views', { autoescape: true });

let contents = fs.readFileSync('./content/index.html');
let outString = nunjucks.render('base.njk', {mainContent: contents});
fs.writeFileSync('./output/index.html', outString);
console.log("Wrote file");

Output

Not quite what I expected

Escape issue
Escape issue

Turn Off HTML Escape

Template update:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Bay Area Windsurf Foiling Club</title>
        <link href="club.css" rel="stylesheet">
    </head>
    <body>
    <nav>
        {% include "mainMenu.html" %}
    </nav>
    <main>
        {{mainContent | safe}}
    </main>
    </body>
</html>

Output 2

Better output
Better output

More Automation

More Files

  • Let’s upgrade the code to cover our other pages.

Updated Code

branch multiFiles, File: RunIt.js

const nunjucks = require('nunjucks');
const fs = require('fs');  // The file system module

let files = ["index.html", "About.html", "Membership.html", "Tides.html"];
let srcDir = "./content/";
let outDir = "./output/";

// Tells nunjucks where to look for templates and set any options
nunjucks.configure('views', { autoescape: true });

for (let fname of files) {
    let contents = fs.readFileSync(srcDir + fname);
    let outString = nunjucks.render('base.njk', {mainContent: contents});
    fs.writeFileSync(outDir + fname, outString);
    console.log(`Wrote file: ${fname}`);
}

Results!

  • Set of consistently structured and styled pages
  • Reuse of menu across all pages reduces chances for broken links.
  • Only essential material in content files!

Issues

  • Have to change the executable runIt.js file every time a page is added or deleted. What if page author isn’t a programmer?

  • All pages have the same <title>.

Solution Approach

  • Move file list out of runIt.js
  • Associate “metadata” with each file to contain information such as title.

File list with Meta Data

branch jsonMeta, file renderList.json:

[
    {"fname": "index.html", "title": "Bay Area Windsurf Foiling Club"},
    {"fname": "About.html", "title": "About the Club"},
    {"fname": "Membership.html", "title": "Membership in the Foiling Club"},
    {"fname": "Tides.html" "title": "Berkeley South Basic Tides"},
]

Updated Template

branch jsonMeta, fileviews/base.njk:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>{{title}}</title><!-- New Stuff -->
        <link href="club.css" rel="stylesheet">
    </head>
    <body>
    <nav>
        {% include "mainMenu.html" %}
    </nav>
    <main>
        {{mainContent | safe}}
    </main>
    </body>
</html>

Update runIt.js

branch jsonMeta, filerunIt.js:

const nunjucks = require('nunjucks');
const fs = require('fs');  // The file system module

let filesInfo = require('./renderList.json'); // Turns into JS array/object
let srcDir = "./content/";
let outDir = "./output/";

// Tells nunjucks where to look for templates and set any options
nunjucks.configure('views', { autoescape: true });

for (let finfo of filesInfo) {
    let contents = fs.readFileSync(srcDir + finfo.fname);
    let tempData = {mainContent: contents, title: finfo.title};
    let outString = nunjucks.render('base.njk', tempData);
    fs.writeFileSync(outDir + finfo.fname, outString);
    console.log(`Wrote file: ${finfo.fname}`);
}

Issues

  • What if we have a lot of pages?
  • Handling of CSS files is a bit weak
  • Maintaining separate JSON file for meta data is error prone
  • What if we want to use markdown for some pages?

Move to a Static Site Generator!