How to make a simple static blogging platform using Grunt and Jade.

How-to: make a simple static blogging platform using Grunt and Jade.

14 Jan 2015

There are many robust static blogging platforms available (see here for a list of the most popular). None of them felt quite right for me, so I decided to develop a really simple system that would allow me the most flexibility in drafting articles. This tutorial walks through the barebones blogging platform behind this website. Now I caution: this is not a framework. It's a hack, but nevertheless, I thought it was pretty cool what some simple scripts in Jade and Grunt could do.

Objectives

Setup

Dependencies

This project has minimal dependencies. It's been shown to work with:

Directory layout and overview

Here is how the root directory will eventually be laid out.

Basic Templates

  • root/
    • src/
      • components/
        • _post.jade
      • posts/
        • example_post_1/
          • index.jade

Each blog post will follow a Jade template, _post.jade. In this example, the template contains only the html, head, and body tags as well as a back button. This is also a place to potentially place a site-wide theme.

Also, not all blog posts need to use this template! This is a completely optional file, only included because it's good form.

root/src/components/_post.jade
html
  head
    block title 
      title Post Title
  body
    block content
      h1 Content Should Here
      .back
         a(href="../../posts.html") Back to all posts

Feel free to change these templates around as much as you'd like, leveraging all the tricks that Jade provides.

root/src/posts/example_post_1/index.jade
//- 
  {
    "location":"example_post_1",
      "title":"First Post!",
      "blurb": "To new beginnings!",
      "date":"2015-01-14"
  }
 
extends ../../components/_post.jade
 
block title
  title First Post!
 
block content
  h2 First Post!
  p To new beginnings!

At the top of our blog post, we have a JSON object, commented out. This information will be used to generate the blog index page.

Gruntfile.js

  • root/
    • src/
      • components/
        • _post.jade
      • posts/
        • example_post_1/
          • index.jade
        • posts.json
    • node_modules/
    • package.json
    • Gruntfile.js

Gruntfile.js goes through directories in src/posts/ and adds the JSON found in each index.jade into one big JSON, src/posts/posts.json. Compelling sections are explained here. The complete Gruntfile.js can be downloaded below in the zip.

compileAll

This section compiles all Jade files that are NOT associated with the blog. We need to make sure that Jade doesn't try to naively compile src/_posts.jade, our blog posts, or any drafts just yet.

root/Gruntfile.js
 compileAll: {
   options: {
     pretty: true,
   },
   files: [{
     expand: true,
     src: ["**/*.jade", "!**/_*.jade", "!posts/**/*.jade"], 
     cwd: "src/",
     dest: "dist/", 
     ext: ".html"
   }]
 },
compilePosts
root/Gruntfile.js
compilePosts: {
  files: [{
    expand: true,
    src: ["**/*.jade", "!**/_*.jade"], 
    cwd: "src/posts/",
    dest: "dist/posts/", 
    ext: ".html"
  }],
  options: {
    pretty: true,
    processContent: function(content) { 
        var postDatabaseFile = "src/posts/posts.json";
        if (grunt.file.exists(postDatabaseFile)) {
          var database = grunt.file.readJSON(postDatabaseFile);
        } else {
          var database = {'posts': {} }
        } 
        var posts = database['posts']; 
        
        var postDetails = content.split('//-')[1].split('}')[0] + '}'; 
        var postJSON = JSON.parse(postDetails);
        location = postJSON['location'];
        posts[location]= postJSON;
 
        grunt.file.write(postDatabaseFile, JSON.stringify(database, null, 2));
 
      return content;}
  }
},

compilePosts goes through every post in src/posts/ and updates src/posts/posts.json through Grunt-Contrib-Jade's processContent option. All post metadata is stored in this JSON file.

Each post is identifed in src/posts/posts.JSON file by its "location" metadata, which must match its directory location in src/posts/.

(Personally, I wanted to have this information in a file that I could easily read instead of being passed directly onto the next section.)

compilePostsPage

This section compiles our posts page. We send in data by reading src/posts/posts.json in the data option provided by Grunt-Contrib-Jade. Our template for the post directory handles how this data is displayed.

root/Gruntfile.js
 compilePostPage: {
  files: {
     "dist/posts.html": "src/_posts.jade"
   },
   options: {
     pretty: true,
     data: function(dist, src) { 
       return require("./src/posts/posts.json");
     }
   }
 }

_posts.jade

  • root/
    • src/
      • components/
        • _post.jade
      • posts/
        • example_post_1/
          • index.jade
        • posts.json
      • _posts.jade
    • dist/
      • posts/
      • posts.html
    • node_modules/
    • package.json
    • Gruntfile.js

_posts.jade takes in data from src/posts/posts.json and formats it into a directory page, dist/posts.html, listing all posts. In this example, I wrote a simple script in Jade to sort the posts by date written.

root/src/_posts.jade
ul
-var sortedPosts = []
-for(var key in posts)
  -sortedPosts.push(posts[key])
-sortedPosts.sort(function(a,b){ var aDate = new Date(a.date); var bDate = new Date(b.date); return (aDate.valueOf() - bDate.valueOf());}) 
each post in sortedPosts.reverse()
  -var link = post.location
  -var title = post.title
  -var blurb = post.blurb
  -var dateObj = new Date(post.date)
  -var date = dateObj.toDateString()
  li
    a(href="posts/"+link+"/index.html")
      h3!= title
    p.blurb!= blurb
    .date!= date

Access post within Jade during compliation. Here, each element in post is placed in an array to take advantage of Array.sort to sort posts by date created. (I apologize for how long this line is. Jade does not allow multiline blocks). To add a metadata field that you want to display, you would only need to add to the JSON of each post and add a section here to handle that new field.

Download

Have fun!

Example.zip
Back to all posts