De Voorhoede

front-end developers

We want more intelligent and clever ways to deliver our work. Grunt has helped us achieve that.

Modular build tasks with Grunt

As developers we all know that the less time we lose on repetitive tasks, the better. It's no wonder open source projects like Grunt, Gulp, and more recently Broccoli, have become more and more popular.

Developers want to focus on challenges and experimenting with new things, not on the batch work.

At De Voorhoede we are no different from the common developers. We want more intelligent and clever ways to deliver our work. And Grunt has helped us achieve that.

We've been working cleverly for some time now. But when we did it, it was on projects we started from scratch. Everything was thought of and structured as components, as we find that to be the best approach to modern web development.

We can't always start a project from the beginning, and sometimes we have to maintain older projects. And that was the case for the project we'll be discussing here.

This project is a project that our team has been working on for sometime, with quite a high developer rotation (including other teams). We had different developing phases, stopped for a few months, changed from SVN to Git, had no automation tasks and some other minor setbacks.

These last sprints we had a bit of a delay from the back-end party so we decided that it would be a great opportunity to improve our workflow.

(keep in mind that this was an intranet-kinda project)

Project folder structure

Our initial project folder structure resembled something like this:

■ some_party_once_involved/
    ■ data/
    ■ docs/
    ■ src/
        ■ fonts/
        ■ images/
        ■ scripts/ (a lot of javascript files)
        ■ styles/ (a few less files)
        index.html

It was a solid and commonly used file structure, but we were editing everything directly on the final files and were dependent on MAMP for the server. And worst of all, there was no automation. Each time the team wanted to push a new version to the set-top box (our final testing device), we had to:

  1. Push the changes to the repository
  2. Ask for the responsible developer to merge all the changes
  3. He would then connect to the VPN and then upload the files via FTP

This was clearly not productive. We could do the local testing but not the final test environment as fast as we wanted.

We called for our friend Grunt.

New folder structure

With the introduction of Grunt on our project we changed our structure a bit:

■ _root*
    ■ data/
    ■ docs/ (jsDoc generated documentation)
    ■ grunt/
    ■ node_modules/
    ■ src/
        ■ fonts/
        ■ images/
        ■ scripts/
        ■ styles/
    ■ web/
        ■ src/
            ■ fonts/ (symlink to fonts on parent directory)
            ■ images/ (symlink to images on parent directory)
            ■ scripts/ (concat+uglify single javascript file)
            ■ styles/ (single minified css file)
        index.html

Configuring the node module connect to /web we were ready to get to work in no time.

The magic of grunt/

This is what our grunt folder looks like:

■ grunt/
    ■ configuration/
        jshint.js
        sftp.js
        index.js
        ...
    ■ tasks/
        deploy.js
        develop.js
        test.js

configuration/

On the configuration directory we had all individual node module configurations. Example forless.js:

function getConfiguration(grunt, taskList) {
    'use strict';
    var configuration = {
        pkg: grunt.file.readJSON('./package.json')
    };
    var length = taskList.length;
    var task;
    while (length--) {
        task = taskList[length];
        configuration[task] = require('./' + task)(grunt);
    }
    return configuration;
}
module.exports = getConfiguration;

We would then load all the configurations for the node modules on our index.js file:

function getConfiguration(grunt, taskList) {
    'use strict';
    var configuration = {
        pkg: grunt.file.readJSON('./package.json')
    };
    var length = taskList.length;
    var task;
    while (length--) {
        task = taskList[length];
        configuration[task] = require('./' + task)(grunt);
    }
    return configuration;
}
module.exports = getConfiguration;

tasks/

On the tasks directory we had all the main tasks for developers to use.

Example for test.js:

module.exports = function (grunt) {
    'use strict';
    grunt.registerTask(
        'test',
        'Concatenates and minifies source files and sends it to the set-top box.',
        function () {
            grunt.task.run([
                'concat:distribution',
                'uglify',
                'copy:distribution',
                'less:distribution',
                'compress',
                'sftp'
            ]);
        }
    );
};

Did you notice the sftp task?

Exactly! Running this task, everybody on the team was able to quickly deploy the whole project to our testing device.

So much automation

Apart from the obvious benefits from deployment related tasks, we also improved:

  1. Live reload of Javascript / CSS
  2. Working with concatenated javascript + sourcemaps (closer to the real scenario)
  3. Separation of working directory and preview stage
  4. Better code on the fly with JSHint and CSSHint
  5. Happier faces :)

The introduction of automation tasks is always a good idea. Don't let the idea of 'old projects must stay how the are so they don't break', prevent you from improving it.

Always improve your workflow even if it's only for minifying a simple CSS file. The gains in the long run are always positive.

Looking ahead

Grunt is really good for what it was made for, single tasks! But its not ideal for development.

Let’s say you’re using Grunt to build an application written with CoffeeScript, Sass, and a few more such compilers. As you develop, you want to edit files and reload the browser, without having to manually rebuild each time. So you use grunt watch, to rebuild automatically. But as your application grows, the build gets slower. Within a few months of development time, your edit-reload cycle has turned into an edit-wait-10-seconds-reload cycle. source

So in the future we are looking to move the development tasks to Gulp or even Broccoli. They are both new (especially broccoli) so we are looking forward to see what improvements they can bring us.

After some investigation on symlink of folders on windows systems, we concluded that we needed to create a separate task, just for windows developers that we had on the team.

The task for them consisted in copying the symlink files at the beginning. The result turned out to be bad. Some had slow machines which turned every grunt develop task into a 10 second job. That was unacceptable.

We later found out that Grunt already had the solution to that: grunt-contrib-symlink.