Andi Smith

Getting Started With Assemble

The site you are visiting right now was created with Assemble, a static site generator for the Grunt task runner. This blog is part one of a multi part series looking at how to create your own blog with this tool.

If you're unsure what a static site is, that's ok. It's a site where the pages are compiled, constructed and stored before the user requests them, usually at build time. So when the user hits the page, they are just requesting the pre-generated HTML output. Static sites can still use JavaScript for client side functionality, so AJAX GET requests, animations and interactivity are still all possible - but there is no dynamic data.

With the redesign of my blog, I wanted to move from Wordpress to a static site so I could simplify my setup and so I could write my posts in Markdown. Wordpress is a great blog CMS, but it can be difficult to insert code in to blog posts. In Markdown, I have full control over how each of my posts look.

Assemble vs Jekyll

Originally when I began the redesign I used Jekyll - a static site generator from the creators of Github. But I found Jekyll had a couple of things I wasn't happy about.

The main reason was because of a couple of limitations in Jekyll. I wanted to have multiple sets/collections of posts on my site - a set of posts about my blog; a set of posts for my portfolio; and another set of posts; and I wanted to use the lists of posts on multiple pages. I also wanted to have the freedom to re-order them. In Jekyll you can only have one set of posts and the filenames must be prefixed by a date (which is how they are ordered). Happily, in Assemble you can have as many as you like in any order you require.

In Jekyll you can only have one set of posts in descending date order. In Assemble you can have multiple in any order.

The JavaScript purist in me wanted to run everything in JavaScript, and Jekyll has a dependency on Ruby. At the time I was having to run Grunt and then run Jekyll to build my site. I really wanted to combine these two processes in to one and use grunt-connect and grunt-watch to ensure immediate generation of new content. There is now a Grunt task for generating Jekyll sites which solves this issue.

Assemble Website

Assemble is still fairly new and features are being added as they are requested. Being on the very bleeding edge can be a little frustrating when something that should work doesn't and I found myself having to come back to Assemble every few weeks to see if it was now possible for me to do what I wanted to do with it. Happily, Assemble is in active development and raising an issue in Github generally results in a response and the right functionality being added.

Assemble is in active development and raising an issue in Github generally results in functionality being added.

There's lots of documentation for Assemble but what you want is often hidden away so deep you have to know exactly what to look for. The documentation and example repositories never really cover the use case of something like creating a normal blog (hence these posts) - there is an example Assemble blog repository but it requires both the post files and a JSON file with a list of the posts which means you have to maintain two areas to write a new post. I really didn't want to do this.

So is Assemble right for you? It depends on your use case. If you just want one stream of posts and to be up and running in a few hours then I'd probably recommend Jekyll. If you're comfortable with JavaScript; want more flexibility and feel like you'd be happy writing additional helpers for the Handlebars templating language then Assemble may be what you're looking for.

If you want more flexibility than Jekyll, then Assemble may be what you're looking for.

This series of posts will aim to guide you through how to create a site not dissimilar to the one here. By the time we've finished the series, you'll have a site that includes:

  • A homepage.
  • A blog section (and the knowledge of how to create new sections).
  • Categories.
  • Pagination.
  • The ability to add custom CSS and JavaScript on a per blog basis.
  • Comments from Disqus and code syntax highlighting from Prism.
  • A RSS feed.
  • A contact form.

How Assemble works

Before we start building in Assemble it is important to know how it works.

A page is constructed from the data we make available to it. It takes the Markdown content and YAML data we specify in our Markdown file and combines it with any extra data files we have specified, together with a number of other useful data pieces such as the filename.

This data combination creates a JavaScript object that we can output and manipulate on our page using Handlebars templating.

Every page requires a layout, which acts as a container. Inside the layout, we can include multiple partials that include segments of our page - for example, we may have our site navigation in a partial, so it can be included in multiple layouts or even at the top and bottom of our site. Partials can be included within partials.

How Assemble works

As shown in this diagram, the layout forms the shell of the page. Inside, partials contain modules of code such as the header, navigation and sidebar. The sidebar contains partials itself such as social, latest and categories. The page content (or the post content) is included inside the post partial.

With that in mind, let's begin!

Starting With Assemble

This tutorial assumes you already have some knowledge of how Grunt works, and that you have already installed grunt-cli. If you don't, here's a couple of articles to take a look at:

Creating an empty project in Grunt

Assuming you don't already have an empty project folder, open up Windows Command Prompt or Mac Terminal and navigate to your normal workspace directory. Create a new project folder using mkdir:

mkdir assemble-blog

Move in to the assemble-blog directory and run npm init to create an initial package.json file:

cd assemble-blog
npm init

We'll need to tell our project we want to use Grunt by including it in our package.json, so run the following command to add Grunt:

npm install grunt --save-dev

We will also want to use grunt-contrib-connect, which is a task for running a local server with Grunt. Assemble doesn't require a server to run (it is a static site solution afterall), it reduces complications later.

npm install grunt-contrib-connect --save-dev

Next, we'll want to create a file called Gruntfile.js within our project directory, with the following contents:

module.exports = function (grunt) {
  'use strict';

  grunt.initConfig({
    pkg: grunt.file.readJSON('./package.json'),

    connect: {
      dev: {
        options: {
          port: 8000,
          base: './dist/'
        }
      }
    }
  });

  /* load every plugin in package.json */
  grunt.loadNpmTasks('grunt-contrib-connect');

  /* grunt tasks */
  grunt.registerTask('default', ['connect']);

};

Installing Assemble

Now we have a base project directory with grunt and grunt-contrib-connect installed, run the following command:

npm install assemble --save-dev

Once Assemble has been installed, we need to create our project structure. Our directory layout is completely configurable so it can match your requirements, but I found the following works well:

Directory structure

I like to group the parts that compile the pages in to a folder called 'bonnet' (as in car bonnet - let's look under the bonnet to see how the car works), but you could call it anything.

I also include a folder called _pages which is where root level page content will live to stop the content directory getting cluttered.

Rather than manually create this folder and file structure, you can download a zip of the structure here.

We need to tell the Assemble task about our structure, so let's open up our Gruntfile.js and make some changes.

We'll need to load the Assemble task and include a section for Assemble within our grunt.initConfig object. At it's simplest, and if you've followed the structure I've suggested above, your Grunt file should look like this:

module.exports = function (grunt) {
  'use strict';

  grunt.initConfig({
    pkg: grunt.file.readJSON('./package.json'),

    connect: {
      dev: {
        options: {
          port: 8000,
          base: './dist/'
        }
      }
    },

    assemble: {
      options: {
        layout: 'page.hbs',
        layoutdir: './src/bonnet/layouts/',
        partials: './src/bonnet/partials/**/*.hbs'
      },
      posts: {
        files: [{
          cwd: './src/content/',
          dest: './dist/'
          expand: true,
          src: ['**/*.hbs', '!_pages/**/*.hbs']
        }, {
          cwd: './src/content/_pages/',
          dest: './dist/'
          expand: true,
          src: '**/*.hbs'
        }]
      }
    }
  });

  /* load every plugin in package.json */
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('assemble');

  /* grunt tasks */
  grunt.registerTask('default', ['assemble', 'connect']);

};

Like most Grunt tasks, Assembles accepts an options object and any number of tasks. Here we've created a task called 'posts' which will (surprise, surprise) process all our posts.

Within our options section, we have added paths to match the directory structure we created earlier and have included the settings Assemble requires to get up and running.

  • layoutdir: Should be set to the path where any layouts we create will be located.
  • layout: This is the default layout that should be used for our files, the file will be suffixed with .hbs.
  • partials: Should be set to the path where any partial layouts we create will be located.

Our posts task files object contains two arrays with file settings.

The first tells Assemble to look for all our content pages (with the extension .hbs), which is any file not in the '_pages' directory. Once these files are processed they will be placed in the './dist/' folder.

The second tells Assemble to compile the contents of the '_pages' directory. We also want these files to be placed in the './dist' folder, so we include the '_pages' page in our 'cwd'.

Now we've set up Grunt, it's time to add some files!

Creating our base site

To get us started, let's create some files that will construct our site:

The layout

src/bonnet/layouts/page.hbs contains three partials - the site header, the page content and the site footer. We include a partial in Assemble using {{>partial-name}}.

{{>site-header}}
  <div class="page-content">
    <h2>{{ this.page.title }}</h2>
    {{#markdown}} 
      {{> body}} 
    {{/markdown}}
  </div>
{{>site-footer}}

The partials

src/bonnet/partials/site-header.hbs contains the top of our HTML document, and it is just static HTML (for the moment):

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>My Site</title>
  </head>
  <body>
    <div class="header">
      <h1>My Site</h1>
    </div>

Finally, we need to create src/bonnet/partials/site-footer.hbs which contains the bottom of our HTML document, and again is just static HTML:

  </body>
</html>

With our site building blocks completed, it's time to add some content!

The content

To start with, we will add two content files. The first is the homepage of the site and the second will be a test blog post. Our content files contain YAML (Yet Another Markup Language) for page data before using traditional Markdown for their content. If you've not used YAML before don't panic, it should be easy to see how it works from the examples below.

src/content/_pages/index.hbs will look like this:

---
title: "Welcome"
posted: 2014-01-28
---

Welcome to my site, built in Assemble!

Our first post located at src/content/blog/first-post.md will look like this:

---
title: "First!"
posted: 2014-01-28
---

This is my first post!

With all this complete, it's time to see what it looks like when generated. Back on your Terminal/Command Prompt pointing at your project directory type:

grunt assemble

Let's see what we've got!

If it has worked correctly, Assemble should generate two pages - dist/blog/first-post.html and dist/index.html. And when we open up our site (at http://localhost:8000/) in our browser, we should get this:

Assemble directory structure

Beautiful!

If you've not been coding, you can retreive our progress so far from Git by running:

git clone https://github.com/andismith/assemble-blog-template.git
git checkout v1
npm install

Coming Up

Now we've got up and running with the basics, we're ready to look in to bigger and better things. The next post covers creating a list of posts in Assemble - and is available right now!

Stay tuned!