I’ve spoken and written on the topic of static site generators a lot lately. Generally speaking, I tend to recommend going with Jekyll as the most stable and feature-rich option for building static sites available today. However, the fact that Jekyll is Ruby-based can be a deal-breaker for some developers, especially those not familiar with Ruby. The single most frequent question I get is: “Is there a good option based upon JavaScript and available via npm?”
In this article, first of a two-part series, we’ll cover one such option, Wintersmith. Wintersmith is a robust static site generator with one biggest impediment being (in my opinion): a lack of detailed documentation. Hopefully this article will help anyone interested in a JavaScript-based option available via npm to get started building static sites with Wintersmith.
The Sample Site
For this article, we’ll use the sample site I built and that you can find here. The sample is an Adventure Time! fan site that looks like the image below.
The goal of the project was to rebuild this site using a number of static site generators to compare them. While the site is intentionally simple, it contains a number of key features that we’ll look at in Wintersmith:
- Custom global metadata – the ability to set custom metadata global to the site that can be accessed and used in templates;
- Custom post metadata – the ability to set custom metadata on a per post basis that can be accessed when listing the post or in the display of the post itself;
- Data sets – the ability to add content that is not a post or page, but rather a custom data type (in the case of this sample that is the character data).
All of the character data, content and images used in the sample are from the Adventure Time! wiki. The design was based upon a free template from HTML5UP.
How to Set up Wintersmith
One of the benefits of Wintersmith being based on Node.js and npm is that the installation process is super simple. You have to execute the command below to install it (note: the sudo
isn’t necessary on Windows):
$ sudo npm install -g wintersmith
That’s it – you’re good to go! Now let’s create a new project.
Creating a Site
To create a new site using Wintersmith, enter the following command:
$ wintersmith new [project name]
For the example site, we’ll give the project a name of “wintersmithsite”. So the command to execute is:
$ wintersmith new wintersmithsite
It will generate a folder with the given project name that includes a bunch of files that we can modify to start building our website.
If we take a look at the generated files, we’ll see that Wintersmith places the configuration, the templates, and the plugins at the root level while the site files are placed within a folder named “contents”.
Testing the Site
To run the project on a local server, change directory and start a preview:
$ cd wintersmithsite
$ wintersmith preview
By default, the local server runs on port 8080, so we can open the site by browsing to http://localhost:8080
. We can specify a different port using the -p
option. Besides, by default, the server is verbose and will output detailed error messages and loaded resources to the console. There are a number of other options to the server which we can learn by entering the command:
$ wintersmith preview -help
The options can also be set within the site configuration file that is named config.json
, but for now, the defaults should work fine.
Templating Basics
Wintersmith uses Jade as its templating language by default. This tutorial will use it, but Wintersmith has a good number of plugins available if you prefer a different templating language.
Templates go in the “templates” folder in the root directory of the site. Jade is a very terse templating language – there are no brackets, no closing tags and indentation matters. Let’s look at some of the basics of how you can create templates using Jade.
Output Data
Jade provides multiple ways of outputting data from variables. The most common when building a site template is to set a tag equal to the value of a variable. For instance, the following example from templates/article.jade
will place the title of an article within a opening and closing <h2>
tags.
h2= page.title
By default the contents of the variable are escaped before outputting. This means that if it contains HTML, the tags will not be rendered in the output but, rather, displayed as plain text. When we need them unescaped, we have to add an exclamation point, as in this example from templates/article.jade
:
section.content!= typogr(page.html).typogrify()
We can do the same with attributes. The following example from templates/partials/homepagemiddle.jade
creates an <a>
tag with the href
attribute equal to the article’s URL.
a(href= article.url, class="image featured")
If you’re curious what variables are made available on a page object by default, the documentation lists them. I should note that the article
variable above is not a default but is the result of a loop, which we’ll discuss later on.
Another way to output variables using Jade is to use #{ variableName }
. When we do this, the content of the variable is escaped. There are no examples of this method within our sample.
If you want to output the contents of a variable unescaped, the syntax to use is !{ variableName }
. For example, when we are outputting the content of the body of a post, we’ll want any tags within it to be rendered. One example is taken from templates/partials/homepagemiddle.jade
:
| !{ typogr(article.intro).typogrify() }
The pipe preceding the previous line of code means that the content will be showed as plain text.
npm Modules
You may be wondering what the typogrify()
calls are all about. Well, one of the benefits of Wintersmith is that it supports the use of npm modules. The generated site includes three: typogr (which is what you see used above); Moment.js (to learn more about moment, you can read the article Managing Dates and Times Using Moment.js); and Underscore.
Let’s look at using Moment.js for formatting a date within a template, as in this example from templates/partials/homepagemiddle.jade
:
p= "Posted " + moment.utc(article.date).format('MMM DD, YYYY')
Moment.js offers a lot more functionality than just formatting, and all of that is available from within our templates. But, we aren’t limited to only Moment.js, as we can just add any npm module to the require
section of the config.json
for our site, npm install
it and use the module in our templates.
Includes
We’ll want to split up templates in order to make them more maintainable and reusable. We can do this using includes. This code from templates/index.jade
includes the templates/partials/header.jade
file (notice you do not need the .jade file extension):
include ./partials/header
Jade also supports inheritance, which can be used for creating similar, separate, and reusable blocks of template code. If you want more details on inheritance, check the documentation.
Conditionals
There are cases where you might want to display different aspects of a template based upon certain conditions. This can be done using conditionals in Jade. Jade supports if
, else if
, else
and unless
, which is effectively a negated version of if
.
This example from templates/partials/header.jade
only displays the banner if we are not on a page (every post in our site is a page, so this means only display it on the home page, index.html
):
if !page
section(id="banner")
header
h2 Explore the Land of Ooo...
p ...and its many kingdoms!
This conditional could also have been written as unless page
.
Jade also supports case
statement blocks. In case you want to learn more, please refer to the official documentation.
Looping
Looping is something we’ll do a lot of in our templates, whether we are looping through posts or data. For such needs, Jade supports both each
and while
loops.
The following example from templates/partials/homepagemiddle.jade
outputs all of our character data using an each
loop. In the middle of the home page, we display each character with its image, name, and description. The each
loop iterates over every object in the array and assigns it to the variable character
where we can access its properties.
each character in contents.characters
div(class="4u")
section(class="box")
span(class="image featured")
img(src= character.metadata.image)
header
h3= character.metadata.name
p= character.metadata.description
Unfortunately, there is no support for adding a limit or an offset to a loop. Instead, we can do this by combining variables and conditionals. In the following example, we are only showing the first two posts (similar to limit). Keep in mind that the lines setting variables (i
and articles
) have a preceding -
to indicate that they will run on the server during compile time. This means that there is no corresponding code generated in the output of the template.
- var i=0
- var articles = env.helpers.getArticles(contents);
each article in articles
- i++
if i<3
div(class="6u")
section(class="box")
a(href= article.url, class="image featured")
img(src= article.metadata.banner)
header
h3= article.title
p= "Posted " + moment.utc(article.date).format('MMM DD, YYYY')
| !{ typogr(article.intro).typogrify() }
footer
ul(class="actions")
li
a(href= article.url, class="button icon fa-file-text") Continue Reading
You will notice that we use env.helpers.getArticles(contents);
to get an array of articles in the contents/articles folder. This isn’t well documented from what I could tell, but this method comes from the paginator plugin, which can be configured in config.json
.
The next example and last example of this article replicates using both an offset and a limit to show the next five articles after the first two:
- var i=0
- var articles = env.helpers.getArticles(contents);
each article in articles
-i++
if (i>2) && (i<8)
li
span(class="date")
!=moment.utc(article.date).format('MMM')
strong= moment.utc(article.date).format('DD')
h3
a(href=article.url)= article.title
p= article.metadata.shortdesc
Conclusion
In this article I’ve introduced you to Wintersmith, which is a solid option if you’re looking for a Node.js-based static site generator. I covered how to install and get started with Wintersmith and also discussed some features of Jade, its default templating system. In the second installment, I’ll teach you how to create posts using the Markdown format, how to set custom Metadata, and also how to generate and deploy your static website.
As you’ve seen, one of the interesting aspects of Wintersmith is its ability to leverage npm modules. This offers developers a lot of choices when customizing their site or add additional functionality that they may need.
Brian Rinaldi is the Developer Content Manager on the Developer Relations team at Telerik. A large part of Brian’s work focuses on ensuring top notch content for the web development community on the Telerik Developer Network. Brian has been a web developer for over fifteen years. Recently, Brian founded a popular developer site called Flippin’ Awesome (now Modern Web) and serves as co-editor of Mobile Web Weekly. Brian writes regularly and also tweets the best articles, tutorials and new projects he finds @remotesynth.