Front-end Process - Flat Builds and Automation, Part 3: Grunt Tasks

Posted by Matt Bailey on 12 June 2013

Grunt - The Javascript Task Runner

 

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 install assemble --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:

{{title}}

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 install grunt-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


You can follow Matt on Twitter, Dribbble and Google Plus.

Comments (9)

  • Stefan Stefan on June 17th, 2013

    Thank you for this tutorial, it helped a lot.

    Could you maybe provide a link to a full Gruntfile.js? I have problems with the generation of the Assemble files during watch. The build is working, and reload is working, too, but the files don’t get written with updated data.

  • Sergey Sergey on June 19th, 2013

    It's all good and thanks for sharing this useful content. I found assemble via your series and am happy about it. ..however I find it useless relying on line numbers of something that changes constantly, isn't it?

  • Jon Park Jon Park on June 19th, 2013

    Thank you for such a great write-up, I'm trying to follow along, but am coming a-cropper when trying to configure Assemble.

    Do you have a reference for what I should be looking for 'around line 42' and 47? What I have in front of me doesn't seem to match up.

    (Currently going over the Grunt docs too).

    Thanks.

  • Matt Matt on June 25th, 2013

    Thanks for the comments guys, it's fantastic that people are getting some use out of this.

    Point taken about the line numbers, although if you follow the tutorial from start to finish the line numbers should all match up. If you do things slightly differently though then they might not, which is why I also described the code you need to look for. I know it can be a bit difficult to follow, but I tried to be as helpful as I could.

    That's a great idea about providing a link to the full Gruntfile.js though, so I've put a link below. I'll also include a reference to it in the article above.

    Gruntfile.js: https://gist.github.com/matt-bailey/5856917

  • Micah Micah on June 26th, 2013

    Hey Matt, in your Gist, it looks like your 'svgmin' task is missing the 'svg-src' folders, as you described in your tutorial. Or did you leave those off on purpose?

    Unfortunately, I've had problems trying to get the svg image to show up in the div.icon-test tag.

    Should the SVG file initially live in the '/images/' folder or within the '/images/svg-src/' folder? I created a folder within '/images/' folder called 'svg-src' as was pointed out in the example, but it gives me an error anytime I run 'grunt'. So I was able to bypass this error by putting the SVG file in the '/images/' folder instead, but it doesn't render anything now.

    Thanks for the tutorial, it's very valuable.

  • Matt Matt on June 26th, 2013

    @Micah - You're absolutely right, well spotted! I've updated the gruntfile to include 'svg-src' in the 'svgmin' config section.

    The master SVG files should be placed in /app/images/svg-src/. Providing everything is set up correctly, and I've not missed off bits of code from the tutorial ;) when you run 'grunt' everything you need will be automatically created.

    Let me know if that helps and if you have any other problems.

  • Micah Micah on June 26th, 2013

    Thanks for the update!

    Using Yeoman Beta 6 and testing with 2 different Yeoman webapp setups, I've had trouble with any SVG images when they live in the /svg-src/ directory.

    When the build script gets to the svgmin process, it spits out the following error:

    <blockquote><p>Warning: Running "svgmin:dist" (svgmin) task<br>
    Warning: svg2js: Invalid character entity Use --force to continue.</p>

    <p>Aborted due to warnings. Use --force to continue.</p>

    <p>Aborted due to warnings.</p></blockquote>

    I'm not sure what is causing this problem but it's been persistent.

  • bernard bernard on July 1st, 2013

    Hello,

    thanks for this great tutorial !
    i have the same problem as Micah.
    i tried to copy the new Gruntfile.js....but i still have an error when i "grunt" :

    >> TypeError: Cannot set property 'name' of undefined
    Warning: Task "default" not found. Use --force to continue.

    any ideas?

  • Matt Matt on July 11th, 2013

    @Micah @bernard - Unfortunately it's very tricky to help find out why errors might be occurring in your build processes. Grunt is a whole suite of tools, each made by different people and with different dependencies. If one of these tools gets updated then other things can stop working, or need to work slightly differently. All I can say is that at the time of writing everything was working, but I feel your frustration. I know this will sound like a bit of a cop out, but I think you're going to have to dig a little deeper yourselves - Read the error messages and see if they offer suggestions. If that doesn't work then google to see if any other people are having the same issue. I would also highly recommend checking out the github issues pages of the tools in question. I'm not a developer and have hit similar problems that you guys have. I've done all of the above steps to try and narrow down problems, and I've always managed to eventually figure out what's going on. Unfortunately working with command line tools is never going to a simple cut-and-paste job ;) Good luck!

Post your comment