Powered by

Forge

A Node.js static content generator

by in Dev

After trying out static website generators, like Jekyll and Octopress, I have started to appreciate the simplicity of deploying a bunch of files and be done with it. With the GitHub Pages, the whole process becomes even more attractive, as even the hosting is free, and very simple, just one push away.

While any existing solution works well, I'm a JavaScript guy, and I want to be able to customise everything to my liking. Hexo was a good start, but we can do something better.

In the following post I will present Forge, my static generator that powers this website.

Tree structure

In Forge all data is organised in a tree structure, with each node having one of the basic types:

  • text: such as a static page, or a blog post.
  • data: stores arbitrary data (we'll come to this later)
  • namespace: groups related nodes together. Namespaces inherit from their parents.

The default file processors are:

  • folders create namespaces
  • markdown files create a data (the frontmatter) and text nodes
  • YAML files create data nodes
  • there are special layout nodes for templates
  • .js files are executed to process the tree structure on the spot.



Forge doesn't have any implicit behaviour on how to generate anything, really. You have to write your own tree parsers that generates whatever you want.

Here is an example of how blog posts may be processed:

blog.post(function(blog) {
    var regexPost = /^(\d{4})-(\d{2})-(\d{2})-(.*)$/;
    blog.each({ type: Node.NS, deep: true, name: regexPost }, function(post, y, m, d, title) {
        post.addData({ // add extra data to posts
            blog: blog,
            title: title,
            date: new Date(y, m-1, d)
        }); 

        posts.push(post);
    });
});

Pages

Pages that are supposed to be generated are explicitly specified while parsing the tree. This allows greater flexibility in what you produce, what data is available and what template to use. In our example blog, pages are generated for valid posts, tags, categories, archive, etc.

blogNS.render('index.html', { layout: 'indexPosts' }, {
    posts: posts // custom data
    // rest of data is provided from the namespace
});

Templates

For templates, I use Nunjucks, an amazing template engine from Mozilla, which besides partials and includes, also provides a nice inheritance feature via blocks.

Conclusion

I really like this approach in generating the content, and I don't regret spending the time. It reminds me if my graduation project, a Content Management System based on a similar tree structure, so I had fun with it.

If I will have the time to iterate on the protect, I hope I can reduce the complexity of node processing, currently new data gets appended in new nodes, as I hoped I would be able to regenerate only affected nodes when filesystem changes, but that seemed to be more complex than I expected, and irrelevant at the moment as rebuilding everything is fast enough.

Another point I would like to work is the way tree scanning work. Currently the namespace iteration is not contrained, you have to implement the iterators to deep search for the nodes of specific types and break when you found what you need.

I don't expect I will release the project any time soon, as it's more of a personal toy, complex and messy in places.

Have you found this interesting? Let me know, and I might post some more details about it.