Learning Assemble, Step 2 - Basic Templating

2014-10-23

Our first Assemble example was nicely minimal, but sadly not very useful or realistic. In this post, we'll flesh out our Assemble templating a bit more to convert from "Hello, Assemble!" to something just slightly more realistic. This will include:

  • Designing a simple folder structure
  • Adding some more pages
  • Using layouts and partial Handlebars templates

Simple Folder Structure

The first thing we need is a more realistic folder structure that will grow to hold a modest number of page templates, layouts, static asset files, and other stuff. We'll keep 'output' as the root of the output file tree for now. I recommend a 'source' folder, roughly following some of the conventions established in Assemble boilerplate projects:

.
|-- Gruntfile.js
|-- package.json
|-- source
|    |-- templates
|         |-- layouts
|         |    +-- default.hbs
|         |-- pages
|         |    |-- index.hbs
|         |    |-- features.hbs
|         |    |-- pricing.hbs
|         |    |-- about.hbs
|         +-- partials
|              +-- header.hbs
+-- output

This structure includes some new files we will create below. Our three template folders will hold Handlebars template files:

  • pages - to hold the primary content for individual pages, we'll add these below.
  • partials - reusable snippets of content, useful for headers, footers, script includes, and more.
  • layouts - to pull all of the above together

Why 'source/templates' and not just 'templates'? Our project will inevitably grow to be more than just Handlebars templates. In the future, I expect source to have many additional folders for things like stylesheets, client-side javascript, images, etc.

Updating Gruntfile for Structure

Now we will modify our Gruntfile to reflect the new folder structure.


module.exports = function (grunt) {
    grunt.initConfig({
        assemble: {
            hello: {
                files: [
                    {
                        expand: true,
                        cwd: 'source/templates/pages',
                        src: '**/*.hbs',
                        dest: 'output/'
                    }
                ]
            }
        }
    });
    grunt.loadNpmTasks('assemble');
};

We only changed the page file specification on lines 11-15. We have asked for all of the page files to be moved to the root of the output folder, relative to their place in the source pages folder. See Grunt File Globbing Patterns for more on the syntax.

If you run grunt assemble at this point, you should regenerate our awesome "Hello, Assemble!" page.

New Pages

We need a few more pages to make our static site a bit more realistic. Let's add some simple pages for Features, Pricing, and About Us. The content of these pages assumes the template we will farther below. All of these go in our source/templates/pages directory.

features.hbs

---
title: "Features"
---
<h1>{{ title }}</h1>
<p>Our features are the best</p>

pricing.hbs

---
title: "Pricing"
---
<h1>{{ title }}</h1>
<p>Our product is totally worth $1,000</p>

about.hbs

---
title: "About Us"
---
<h1>{{ title }}</h1>
<p>This is the About page content</p>

Templates, Layouts, and Partials

So far our templating has been less than impressive. We will add a simple partial template for the header, a layout, and modify our original index.hbs page to reflect the changes.

Header Partial (header.hbs)

A partial Handlebars template can be as simple as a text include, and that's all we're going to do for now. We'll figure out how to dynamically generate this type of navigation in a later post. Use the following for source/templates/partials/header.hbs:

<div>
    <a href="/">Home</a> |
    <a href="/features.html">Features</a> |
    <a href="/pricing.html">Pricing</a> |
    <a href="/about.html">About</a>
</div>

Layout (default.hbs)

The layout page will combine our various partials with our page content. This typically means it contains the outline of an HTML page, with many includes for partials for navigation, scripts, and other components.

<!DOCTYPE html>
<html>
<head>
    <title>Learning Assemble, Step 2 - Basic Templating</title>
</head>
<body>
{{> header }}
{{> body}}
</body>
</html>

Home Page (index.hbs)

Now let's revisit our home page template. Where we used to have the complete HTML structure, we now only need the body content, so it looks pretty lean:

---
title: "Home"
---
<h1>{{ title }}</h1>
<p>This is the home page</p>

Updating Gruntfile for Layouts and Partials

We need a bit more Assemble configuration to use the partial template and layout we just created. We'll modify our Gruntfile as follows:


module.exports = function (grunt) {
    grunt.initConfig({
        assemble: {
            hello: {
                options: {
                    layoutdir: 'source/templates/layouts',
                    layout: 'default.hbs',
                    partials: 'source/templates/partials/**/*.hbs'
                },
                files: [
                    { expand: true, cwd: 'source/templates/pages', src: '**/*.hbs', dest: 'output/' }
                ]
            }
        }
    });
    grunt.loadNpmTasks('assemble');
};

We specified three options for Assemble:

  1. layoutdir - the directory to search for named layouts.
  2. layout - the default Handlebars layout, for pages that do not specify a named layout. All of our pages are using the default for now.
  3. partials - a file specification for partial Handlebars template files.

Try running grunt assemble again. You should now generate all four output pages, each with a complete HTML structure.

Running "assemble:hello" (assemble) task
Assembling output/about.html OK
Assembling output/features.html OK
Assembling output/index.html OK
Assembling output/pricing.html OK
>> 4 pages assembled.

Done, without errors.

Although we have generated some output, we've only been looking at the output in a text editor. In the next post, we'll get a preview feedback cycle going.

For the code, please see the learning-assemble repository on GitHub. The branch step02-basic-templating contains the code from this post.

Next: Learning Assemble, Step 3 - Grunt Workflow