Rendering Pug Templates with Multiple Data Files

By

If you haven’t already heard of Pug (formerly known as Jade), you’re definitely missing out. Pug is a super easy-to-use, high performance template engine built in JavaScript used for building static websites.

I’ll be using Gulp to automate my workflow and build tasks. For the purpose of speeding things along, I’ll assume you already know what Gulp is and how it works, but if not, be sure to check it. gulp-pug is a convenient package that allows you to compile your Pug templates with support for template data.

Rendering Templates without Data

Let’s start with a simple Pug template that consists of no data/locals:

/**
 * ./templates/helloWorld.pug
 */

doctype html
html(lang="en")
    head
        title My Awesome Website
    body
        ul.nav
            li Link 1
            li Link 2
            li Link 3
        h1 Hello World    

To compile the above template with gulp-pug, all we have to do is write a simple gulp task:

var gulp = require('gulp'),
    pug = require('gulp-pug');

gulp.task('build', function() {
    return gulp.src('/templates/**/*.pug')
        .pipe(pug())
        .pipe(gulp.dest('/dist'));
});

Running the build task above in our console will compile all of the templates in the /templates directory and output the rendered HTML files to the /dist directory:

/**
 * ./dist/helloWorld.html
 */

<!DOCTYPE html>
    <head>
        <title>My Awesome Website</title>
    </head>
    <body>
        <ul class="nav">
            <li>Link 1</li>
            <li>Link 2</li>
            <li>Link 3</li>
        </ul>
        <h1>Hello World!</h1>
    </body>
</html>

Pretty simple, right? Okay, let’s move on to something a bit more exciting now.

Rendering Templates with Data

Let’s re-use our helloWorld.pug template above, but lets get rid of all the hard-coded data.

/**
 * ./templates/helloWorld.pug
 */

doctype html
html(lang="en")
    head
        title= title
    body
        ul.nav
            each link in links
                li= link
        h1= message    

We can render our helloWorld template using our build task above, but with one modification: we need to pass our data into the gulp-pug plugin as JSON. Our template will still render without it, but it will be missing all of our data. Lets take a look at our revised task:

var gulp = require('gulp'),
    pug = require('gulp-pug');

gulp.task('build', function() {
    return gulp.src('/templates/**/*.pug')
        .pipe(pug({
            data: {
                title: 'Our Awesome Website',
                links: [
                    'Link 1',
                    'Link 2',
                    'Link 3'
                ],
                message: 'Hello World!'
            }
        }))
        .pipe(gulp.dest('/dist'));
});

Running the above task by executing gulp build in your console will generate the exact same helloWorld.html file as above. Now this technique may work for smaller websites with minimal amounts of data, but it isn’t the best solution for larger and more complex websites (with much more data). It’s unorganized, will clutter your Gulp file, and uses poor name-spacing.

A better way to approach this would be to keep all of your data in an external JSON file, and then use the gulp-data package to extract it’s contents and pass it to gulp-pug.

Rendering Templates with External Data File

Instead of passing our JSON data directly to our gulp-pug plugin, let’s extract our data into a separate JSON file and use gulp-data to pipe it into gulp-pug.

/**
 * ./data/data.json
 */

{
    "title": "Our Awesome Website",
    "links": [
        "Link 1",
        "Link 2",
        "Link 3"
    ],
    "message": "Hello World!"
}

Now we can use gulp-data and to extract our JSON and pass it into our gulp-pug plugin:

var gulp = require('gulp'),
    pug = require('gulp-pug'),
    data = require('gulp-data'),
    fs = require('fs');

gulp.task('build', function() {
    return gulp.src('/templates/**/*.pug')
        .pipe(data(function(file) {
            return JSON.parse(fs.readFileSync('/data/data.json'))
        }))
        .pipe(pug())
        .pipe(gulp.dest('/dist'));
});

Nice! Extracting our JSON data in a separate file is a great way to clean up our code. I recently worked on a project in Pug that consisted of large amounts of data in a single file. I wanted to come up with a way to easily break this single file into multiple files, while maintaining strict name-spacing for my templates. Here’s how I did it:

Rendering Templates with Multiple External Data Files

Let’s assume we are working on a much bigger project with a lot more data. We keep all of this data in a single JSON file.

/**
 * ./data/data.json
 */

{
    "site": {
        "title": "Our Awesome Website",
        "author": "Tushar Ghate",
        "meta": {
            "charset": "utf8",
            "description": "Lorem ipsum dolor sit amit.",
            "keywords": "lorem, ipsum, dolor, sit, amit",
            "favicon": "favicon.ico"
        }
    },

    "nav": {
        "links": [
            {
                "href": "/link1",
                "displayName": "Link 1"
            },

            {
                "href": "/link2",
                "displayName": "Link 2"
            },

            {
                "href": "/link3",
                "displayName": "Link 3"
            }
        ]
    },

    "translations": {
        "HELLO_WORLD": {
            "en-CA": "Hello World",
            "fr-CA": "Bonjour le monde"
        }
    }
}

Our data file may not be large now, but it does have the potential to grow much larger. For instance, most sites that support localization consist of hundreds to thousands of strings. A great way to keep our data better organized is to split this file into multiple files, where our filename acts as the scope or namespace for all of data contained within it.

The first step is to split our JSON file into three separate files:

/**
 * ./data/site.json
 */

{
    "title": "Our Awesome Website",
    "author": "Tushar Ghate",
    "meta": {
        "charset": "utf8",
        "description": "Lorem ipsum dolor sit amit.",
        "keywords": "lorem, ipsum, dolor, sit, amit",
        "favicon": "favicon.ico"
    }
}
/**
 * ./data/nav.json
 */

{
    "links": [
        {
            "href": "/link1",
            "displayName": "Link 1"
        },

        {
            "href": "/link2",
            "displayName": "Link 2"
        },

        {
            "href": "/link3",
            "displayName": "Link 3"
        }
    ]
}
/**
 * ./data/translations.json
 */

{
    "HELLO_WORLD": {
        "en-CA": "Hello World",
        "fr-CA": "Bonjour le monde"
    }
}

Okay! Looks like our data files are all set up. Next lets update our Pug template to reference the new variables.

/**
 * ./templates/helloWorld.pug
 */

doctype html
html(lang="en")
    head
        title= site.title
    body
        ul.nav
            each link in nav.links
                li= link
        h1= translations.HELLO_WORLD['en-CA']    

As you can see, our data is now properly namespaced based on our JSON files. Now comes the fun part. We need to write a new Gulp task to do the following:

  1. Iterate through each JSON data file.
  2. For each file, set the name of the file as the primary key (namespace).
  3. Merge all of the compiled JSON into a single file.
  4. Pass the merged JSON into gulp-pug to render our Pug templates.

Whew! Okay, let’s get started. The first thing we are going to do is set up a separate task to iterate through our data files, set our namespaces, and generate our merged JSON. To do this we are going to use the package gulp-merge-json.

var gulp = require('gulp'),
    pug = require('gulp-pug'),
    data = require('gulp-data'),
    fs = require('fs'),
    path = require('path'),
    merge = require('gulp-merge-json');

gulp.task('pug:data', function() {
    return gulp.src('/data/**/*.json')
        .pipe(merge({
            fileName: 'data.json',
            edit: (json, file) => {
                // Extract the filename and strip the extension
                var filename = path.basename(file.path),
                    primaryKey = filename.replace(path.extname(filename), '');

                // Set the filename as the primary key for our JSON data
                var data = {};
                data[primaryKey.toUpperCase()] = json;

                return data;
            }
        }))
        .pipe(gulp.dest('/temp'));
});

Our pug:data task iterates through our JSON files in the ./data directory, sets the filename as the namespace for the inner JSON, and the merges it into a single JSON object in /temp/data.json. We can now pass this file into gulp-pug like we did before, and use it to render our Pug templates.

gulp.task('build', ['pug:data'], function() {
    return gulp.src('/templates/**/*.pug')
        .pipe(data(function() {
            return JSON.parse(fs.readFileSync('/temp/data.json'))
        }))
        .pipe(pug({
            pretty: true,
            basedir: './'
        }))
        .pipe(gulp.dest('/dist'));
});

As you can see above, we always run pug:data before running build, so we can always ensure that our data file is merged and ready to go before rendering our templates.

And there you have it! A clean, organized and efficient way for handling large amounts of data in your Pug websites.