A Cinema Blog, à la Jekyll

Jekyll is the static blog generation staple. Let's mimic its behaviour in Waffel!
Goal
We are going to generate a blog that thoroughly reviews the best 1000 movies ever, according to NY Times.
First, you would like to have a look at it yourself, so generate it via:
$ npm install
$ node index.js
Et voilà, point your browser to the URL written in your console (with all chance, http://localhost:1337) and browse around.
Cool! Fast! Tell me more about how you made it!
Walktrough
The structureFile.
Sure, here we go. As you may have already read, Waffel generates output from website content (by default located in /data), following the instructions contained in the structureFile(namely site.yml). No configuration in any file / YAML frontmatter, because the old concern separation adagio still holds.
Let's check the structure part of the structureFile:
structure:
blog:
collection: posts
pages:
index:
template: blog/index
url: /
priority: 0.5
sort:
field: date
order: desc
categories:
template: blog/category
url: /category/:category
groupBy: category
priority: 0.5
sort:
field: date
order: desc
tags:
template: blog/tag
url: /tag/:tag
groupBy: tags
changefreq: weekly
priority: 0.7
sort:
field: date
order: desc
single:
template: blog/post
url: /posts/:slug
priority: 0.8
about:
template: about
url: /about
404:
template: 404
url: /404
sitemap: false
We have a blog section, holding four pages:
indexis the list of all posts, that will serve as homepage (simply because its URL schema is/);categoriesis a set of pages, one per category (among all categories used over all posts, in this case classic, independent, avantgarde);tags: as above, but referred to tags;single: all single post pages, that will result in separate HTML documents.
Notice the collection property (set to posts): Waffel will tie the blog section to the list of posts, and generate pages accordingly.
And what does groupBy do? It tells Waffel to generate a different page for every single instance of the given property all over the collection - in this case, for the categories page we get:
/category/avantgarde/index.html
/category/classic/index.html
/category/independent/index.html
For tags:
/tag/247/index.html
/tag/b2b/index.html
/tag/back-end/index.html
/tag/best-of-breed/index.html
/tag/bricks-and-clicks/index.html
/tag/clicks-and-mortar/index.html
/tag/collaborative/index.html
/tag/cross-media/index.html
/tag/customized/index.html
/tag/cutting-edge/index.html
...
/tag/web-enabled/index.html
/tag/wireless/index.html
world-class/index.html
(silly random buzzwords, may vary across different generations)
On top of that, every page is sorted according to the sort field, and is paginated in chunks whose default size is 10.
A different one can be provided via paginate parameter, e.g.:
tags:
template: blog/tag
url: /tag/:tag
groupBy: tags
paginate: 20
There is so just one property not covered yet, the template!
Templates
Waffel uses Nunjucks, due to its expressiveness. Let's check in detail what happens in the single post template:
{% extends "layout.html" %}
Extending another template (in this case layout.html) means to reuse its structure by adding further content in any of its blocks:
{% block meta %}
<meta name="description" content="{{ item.__content | excerpt(150) }}">
<meta property="og:type" content="article">
<meta property="og:description" content="{{ item.__content | excerpt(350) }}">
<meta property="article:published_time" content="{{ item.date | format('YYYY-MM-DDTHH:mm:ss.SSSZZ') }}">
{% for tag in item.tags %}
<meta property="article:tag" content="{{ tag }}">
{% endfor %}
<meta name="twitter:description" content="{{ item.__content | excerpt(350) }}">
{% endblock %}
This snippet for instance, adds meta tags information in the meta block, that is located in the base.html template.
Nunjucks docs cover the topic pretty well here! Concerning specific Waffel topics, you can see many references to the item variable. It holds data for the current page, in this case for the single blog post.
For instance, in order to fill the description metatag, we apply the excerpt() filter to the __content property of the post, that holds its Markdown formatted content. Unsurprisingly, it will fill it with an excerpt of the blogpost!
Note: the excerpt() filter is a bit expensive in terms of computation, that's why it has been cleverly memoized. Feel free to use it in different parts of the website, e.g. for metatag information and post previews or related posts cards!
Going further down to the body block, we meet the url() helper for the first time:
<header>
<h1>{{ item.title }}</h1>
<p><img src="{{ item.cover }}" class="img-responsive" alt="{{ item.title }}"></p>
<p class="info">Written on <strong>{{ item.date | format('MMM DD YYYY') }}</strong> in <a href="{{ url('blog.categories', item) }}">{{ item.category | capitalize }}</a></p>
</header>
It helps generating URLs for internal document references, keeping thus the hypertext graph consistent - read: if you change your URL names in the future, they will be generated accordingly.
In this case, we generate a category page URL (e.g. /category/classic/index.html) by calling url('blog.categories', { category: item.category }).
Notice how we passed item as the second parameter: this is an useful shorthand that works because item has a category field itself! Waffel will just access the property defined in the groupBy URL option, in order to generate the final URL.
In case you would want to generate an URL for a different category page, you will have to state it explicitly, i.e. url('blog.categories', { category: 'another-category' }).
We use then the markdown tag, in order to render item.__content as HTML:
{% markdown %}{{ item.__content }}{% endmarkdown %}
Note that due to some Nunjucks/marked bug, markdown blocks shall always be completely left aligned, i.e. no tab/indentation at all before {% markdown }, sorry.
Further explanation regarding other templates used in this example will come soon.