Front-end Process - Flat Builds and Automation, Part 3: Grunt Tasks
Matt Bailey / +Matt / June 12, 2013

resizedimage240283 grunt logo 2

In part one we discussed what a flat build is and why to use it. In part two I outlined how to set up your dev environment, and how to scaffold a project using Yeoman. In this third part we will look at how to install and configure some grunt tasks.

The default Yeoman webapp generator contains the basics of what you need to get started, but I have started using a couple of extra grunt tasks to augment my automation process - assemble and grunticon.

*Update

In order to make things a little easier to follow, here is the full Gruntfile.js

Step 1 - Install and Set up Assemble

In their own words, “Assemble makes it dead simple to build modular sites and components from reusable templates and data”. Assemble is extremely powerful and flexible. I highly recommend checking out their example projects to get a feel for what’s possible. For the purposes of this article I will keep things simple and explain how to set up some basic pages, include some external partials and pass metadata between the templates.

First, install the assemble node module - navigate to the root of your project folder and run:

npm installassemble --save-dev

Then create the folder structure for your template files:

myproject/
`-- templates/
    |-- layouts/
    |-- pages/
    `-- partials/

Now we need to start creating our templates. Open 'index.html' and re-save it into the 'layouts' folder, but re-name it 'default.hbs' ('.hbs' means it’s a handlebars file, a super-cool templating system that assemble uses).

Then, in the folder called 'pages' create two files called 'index.hbs' and 'mypage.hbs', and in the folder called 'partials' create a file called 'header.hbs'.

In 'default.hbs', in the body (tag) section of the page add:

{{> header }}
{{> body }}

This tells assemble (using handlebars syntax) where to load the header partial, and where to load the content (from the 'pages' folder).

In 'index.hbs' and 'mypage.hbs' add whatever regular html content you like. Assemble also allows you to pull in blocks of content in markdown format. Again, refer to the assemble example projects for more details.

You could then add some YAML Front Matter, (or an external data file - simply match the page name, i.e. 'mypage.json') for page metadata. In its most simple form the YFM could look something like this:

---
title: Home
---

or

---
title: My Page
---

This should be added at the very top of the file before any of the html.

(Note: The three dashes above and below the metadata are required)

You can then use this metadata in any of your template files. For example, open 'header.hbs' and enter the following code:

<h1>{{title}}</h1>

This means that each template you create in the 'pages' folder will pass its title variable into 'header.hbs' when it’s included - super useful and avoids repetition, i.e. you only need to use one header partial for all your pages. The possibilities with YAML Front Matter and Handlebars are super flexible - again, I would recommend looking at the assemble examples, but to give you an idea take a look at this navigation example:

<nav class="nav-wrapper"role="navigation">
    <ul class="nav nav--stacked">
        <li{{#is title "Home"}} class="active"{{/is}}><a href="index.html"title="Home">Home</a></li>
        <li{{#is title "My Page"}} class="active"{{/is}}><a href="mypage.html"title="My Page">My Page</a>
            {{#is title "My Page"}}
            <ul class="sub-nav">
                <li><a href="linkone.html"title="Link One">Link One</a></li>
                <li><a href="linktwo.html"title="Link Two">Link Two</a></li>
            </ul>
            {{/is}}
        </li>
    </ul>
</nav>

You can see that it’s possible to create conditional blocks of code to, for example, add the class '.active' to the current menu item:

{{#is title "Home"}} class="active"{{/is}}

To find out more about what handlebars can do, check out the helpers lib that is included as part of assemble.

Step 2 - Configure Assemble

To add assemble to your build process you will need to enable and configure it by adding the following code to 'Gruntfile.js', around line 303 (which can be found in the root directory of your project):

grunt.loadNpmTasks('assemble');

Then, add the following configuration to the 'grunt.initConfig()' section (I usually do this after line 196, so just before the 'useminPrepare' config section):

assemble: {
    options: {
        flatten: true,
        layout: '<%= yeoman.app %>/templates/layouts/default.hbs',
        partials: ['<%= yeoman.app %>/templates/partials/*.hbs'],
    },
    pages: {
        files: {
            '<%= yeoman.app %>/': ['<%= yeoman.app %>/templates/pages/*.hbs', '!<%= yeoman.app %>/templates/pages/index.hbs']
        }
    },
    index: {
        files: {
            '<%= yeoman.app %>/': ['<%= yeoman.app %>/templates/pages/index.hbs']
        }
    }
},

This code basically sets up assemble and tells it what files/folders it should work with. In a nutshell it defines which file to use for the main layout template, where to locate the partials, where the page content can be found (but exclude 'index.hbs'), and which file contains the index page content.

We then need to add assemble to the 'build' task (which happens when you run 'grunt') and to the 'watch' task (which happens when you run 'grunt server').

So, on about line 42 edit the 'livereload' section of the 'watch' config to look like this:

livereload: {
    options: {
        livereload: LIVERELOAD_PORT
    },
    files: [
        '<%= yeoman.src %>/templates/{,*/}*.hbs',
        '{.tmp,<%= yeoman.src %>}/css/{,*/}*.css',
        '{.tmp,<%= yeoman.src %>}/js/{,*/}*.js',
        '<%= yeoman.src %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
    ],
    tasks: ['assemble']
}

Effectively what we're doing is telling livereload to watch for changes to the '.hbs' files rather than '.html' files, and to run the 'assemble' task.

Then edit the 'build' task to it looks like this (about line 363):

grunt.registerTask('build', [
    'clean:dist',
    'assemble',
    ...

If you now run 'grunt' (run 'grunt' at least once before you run 'grunt server') you should find that two files are created in your app folder - 'index.html' and 'mypage.html'. Also, as part of the build task it will have copied these files into the 'dist' folder. If you open these files you’ll see that the values you put in the 'title' metadata of the front matter have been passed into the 'h1' tag of the header. Magic!

One thing to remember is that from now on you should only edit the '.hbs' files in the 'templates' folder and not any of the '.html' files in the root of your 'app/' folder - these will be overwritten every time you run the build task.

This tutorial only covers the basics, but assemble really is hugely flexible - if you can imagine it, assemble can probably do it.

Step 3 - Install, Set up & Configure Grunticon

Grunticon allows you to create one folder of master SVG images and then have these converted into three different css formats - SVG data urls, png data urls, and a third fallback CSS file with references to regular png images (that it also creates - how cool is that!). It even creates some javascript that works out what your browser supports and serves up the relevant style sheet asynchronously.

Install the grunticon node module - navigate to the root of your project folder and run:

npm installgrunt-grunticon

Once you’ve installed the grunticon node module you need to set up a folder and put an SVG image in it - Inside the folder called 'images' create another folder called 'svg-src'. Then inside that folder put an SVG image. Any will do - I just created a simple test file in Illustrator and called it 'test.svg'. Remember, the size of your artboard in Illustrator is the size of the SVG, so you may want to crop it down to the size of your graphic.

Now you need to enable and configure grunticon in 'Gruntfile.js'.

After the line of code that enables assemble (so about line 323) add the following:

grunt.loadNpmTasks('grunt-grunticon');

Next you need to configure the task. After the 'svgmin' config, so about line 247, add the following code:

grunticon: {
    myIcons: {
        options: {
            src: '<%= yeoman.app %>/images/svg-src/',
            dest: '<%= yeoman.app %>/images/svg-dist/'
        }
    }
},

This tells the grunticon task where the SVG source and destination folders are.

Next you need to modify the 'svgmin' task config so it knows where your 'svg-src' folder is. Simply add 'svg-src/' to the end of the 'cwd' and 'dest' paths:

svgmin: {
    dist: {
        files: [{
            expand: true,
            cwd: '<%= yeoman.app %>/images/svg-src/',
            src: '{,*/}*.svg',
            dest: '<%= yeoman.dist %>/images/svg-src/'
        }]
    }
},

Almost there now. You need to modify the 'copy' task config so that it also takes into account the new 'svg-src' and 'svg-dest' folders:

copy: {
    dist: {
        files: [{
            expand: true,
            dot: true,
            cwd: '<%= yeoman.app %>',
            dest: '<%= yeoman.dist %>',
            src: [
                '*.{ico,txt}',
                '.htaccess',
                'images/!(svg-src)/**',
                'styles/fonts/*',
                'webfonts/*'
            ]
        }]
    }
},

I’ve basically changed the 'src' parameter so that it will copy the whole images folder and anything inside it except the 'svg-src' folder.

Finally you need to add 'grunticon' to the 'build' task. On about line 360 add the code after the 'assemble' task, so it should look something like this:

grunt.registerTask('build', [
    'clean:dist',
    'assemble',
    'grunticon',
    ...

Now when you run 'grunt' you should find that the 'svg-dest' folder gets created and it should contain a loader file, a preview file, a selection of css files and folder of png images, all done automagically!

So that’s the grunticon task set up, but you’ll also need to do a few things to use SVGs in your templates. I may write a more indepth tutorial on this at a later date, but I’ll do a quick summary to get you started.

Step 4 - Use The Grunticon Generated SVGs in Your Templates

Open 'grunticon.loader.txt', copy the contents and paste them just before the closing head tag in 'app/templates/layouts/default.hbs'.

What I’ve found is that you also need to slightly modify the paths specified in this block of code, so where it says '/app/images/...' change it to just 'images/...' (Note - no starting forward-slash). You need to do this so that it looks for the css files relative to the location of the current page. In your final 'dist' folder '/app/' means nothing - it doesn’t exist. There may be a way to get round this in the grunticon config, but I haven’t worked it out yet.

Next, open one of your pages, for example 'index.hbs' and add the following code:

<div class="icon-test"></div>

If you remember, I called my SVG image 'test.svg'. Grunticon creates classes prefixed with '.icon-' and then adds the name of your file (not including the extension). So if your SVG image was called 'my-logo.svg', the class created by grunticon would be '.icon-my-logo'.

If you now run 'grunt server' you’ll notice that initially it looks like the code above hasn’t worked. However, there’s one more thing you need to do, and that’s add a width and height in your css for the SVG image - basically the SVG is there, but the div it’s applied to has no dimensions so you can’t see it.

Open 'app/styles/main.scss'. Assuming you said 'yes' to installing Bootstrap for Sass you should see a line of code that says:

@import 'sass-bootstrap/lib/bootstrap';

For now, comment this line out. This is because Bootstrap applies a load of styling to anything with a class prefixed with '.icon-', which will cause the styling to go a bit crazy.

Then below this add the following code (the width and the height need to be the width and height of your SVG image):

.icon-test {
    width: 100px;
    height: 100px;
}

If you now save 'main.scss', and you have 'grunt server' running, you should see your test SVG file appear. Otherwise, run 'grunt' and then, once the build process has finished, in your browser open the 'index.html' file found in your 'dist' folder.

And that’s the basics of grunticon.

Notes:

  • assemble makes it easy to build modular sites and components from reusable templates and data.
  • grunticon takes a folder of SVG files and outputs them to CSS in 3 formats: SVG data urls, png data urls, and a third fallback CSS file with references to regular png images.

Further Reading

For more information on using SVG images (there's no excuse not too these days), I highly recommend reading these two articles:

Also, if you would like a bit more information on setting up and using Grunt in general you could have a look at another article I wrote, called A Beginner’s Guide to Grunt.

Article Index