Matt Sweetman

Latest articles

New website built with Haggerston

The latest version of this site was built using Haggerston, a static site generator developed in Node.js by myself and Kelvin Luck. It was named after the place it was built in.

For the past few years this site has been powered by Wordpress, but after several hacking attempts and an endless battle with spam I decided it was time to change. The new site is served as flat HTML - no PHP or databases in sight. I write the articles locally in markdown, run Haggerston to generate HTML, then sync the files to the web server. It's fast, easy to update, and security issues are kept to a minimum.

Leveraging the power of Grunt

Haggerston is built as a Grunt task. The reason we chose Grunt is because it can already do a lot of what is needed to generate a static site. It can copy files and folders, compile CSS, rsync to remote hosts, start local servers, and watch for file changes. The only task that's missing is one to generate HTML pages. This is where Haggerston comes in.

Haggerston is a relatively simple task, it's job is to turn a folder of .json files into .html files. Each json file specifies a template to use when rendering the html. If you've used javascript templating libraries before then the process should be familiar to you. All Haggerston is doing is turning templates into html files, and using the json files as data for the templates. Haggerston uses Swig as its templating language.

A basic example

Using Haggerston is as simple as dropping in an empty task into your Grunfile.js:

module.exports = function(grunt) {
  grunt.initConfig({
    haggerston: {}
  });

  grunt.loadNpmTasks('grunt-haggerston');
};

This makes the following assumptions: source files exist in a subfolder called src/content, templates exist in src/templates, and the generated site will be created in a subfolder called out. Each of these properties can be overridden in the config if necessary.

Let's take the url for this article as an example. The page exists at /articles/new-website-built-with-haggerston/, which started life in a source folder on my local machine like so:

src
 |-- content/
 |-- articles/
 |  |-- new-website-built-with-haggerston/
 |     |-- index.json
 |-- templates/
    |-- article.html

Running grunt haggerston from the command line will take each .json file from the content folder, render it against a chosen Swig template from the templates folder and export the .html into a corresponding structure in the output folder:

out
 |-- articles/
    |-- new-website-built-with-haggerston/
       |-- index.html

You choose which template to render the data against by specifying it in each json file. Let's take a look at the index.json file of this page as an example:

{
  "template": "article.swig",
  "templateData": {
    "title": "New website built with Haggerston",
    "date": "2013-04-27",
    "blurb": "How I built my new website with Haggerston and Grunt",
    "content": "index.md"
  }
}

This process is extremely simple, but the key to making a good static site is knowing how to use templates. Swig supports something called template inheritance which allows templates to inherit and override blocks of markup from their parent. I won't go into detail here, but check out the Swig documentation for an example of how to use it.

Using the concept of middleware

Each step in the Haggerston process is designed as middleware, an idea borrowed from connect that allows developers to intercept the flow of a process and manipulate the data being worked upon.

You may have noticed a property in the index.json example above:

"content": "index.md"

Haggerston is designed to find properties with values that end in '.md'. Once it finds them it tries to locate the referenced markdown file, parse it into html, and replace the original property with the result. This means that when you use {{content}} in your template you're actually getting the html generated from the index.md file, not the string 'index.md'.

The part of Haggerston that does this is called the Markdown middleware. It's simply a function that gets called during the overall Haggerston Grunt task. Each middleware acts the same, and the function is given access to all the json data and rendered templates, meaning it can manipulate the pages in whatever way it likes before they're written to disk.

The basis for a piece of middleware looks like this:

module.exports = function() {
  return function (pages, next, options) {
    // Here you can manipulate the pages array in whatever way you want.
    // Afterwards just call next(pages) to continue with the Haggerston process.
    next(pages);
  };
};

Haggerston comes with a default set of middleware modules that performs common tasks. Each middleware is executed sequentially, and is top-and-tailed with core tasks that cannot be moved. Here's an example of how the overall process works:

  1. Core process: Read .json files into javascript objects
  2. Middleware: Read markdown files and parse them into html markup
  3. Middleware: Render the template into html markup
  4. Middleware: Highlight any code blocks found in the rendered markup
  5. Core process: Write the rendered markup to .html files.

You can write your own middleware and insert it at any point between the opening and closing core processes.

If you want to know more check out the github repo, or take a look at the source code of this website.