Task Runners, A comparison between Grunt, Gulp, Broccoli and Mimosa

Note: This article was written and researched in January 2015, but I just got around to publishing it today (April 5th, 2015).

Task Runners make your life easier when developing for the front-end by automating tasks. Except for when you have to configure them for the first time. Try not to lose your sanity.

Grunt

Everyone knows or has heard of Grunt. Here’s an example Gruntfile.js:

//wrapper function
module.exports = function(grunt) {

  // Project and task configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      // will compress the js
      build: {
        src: 'assets/js/local/bootstrap.js',
        dest: 'assets/js/production/bootstrap.min.js'
      }
    },
    less: {
      // for development purposes
      development: {
        options: {
          //Compress output by removing some whitespaces.
          compress: false,
          yuicompress: true,
          // Set the parser's optimization level. 
            //The lower the number, the less nodes it will create in the tree. 
            //This could matter for debugging, or if you want to access 
            //the individual nodes in the tree.
          optimization: 2
        },
        files: {
          // destination file and source file
          "assets/css/main.css": "assets/less/main.less" 
        }
      }
    },
    watch: {
      styles: {
        // which files to watch
        files: ['assets/less/main.less'], 
        // which tasks to run when a watched file event occurs.
        tasks: ['less'],
        options: {
          // Whether to spawn task runs in a child process. 
            // Setting this option to false speeds up the reaction
            // time of the watch (usually 500ms faster for most) 
            // and allows subsequent task runs to share the same 
            // context. Not spawning task runs can make the watch 
            // more prone to failing
          nospawn: true
        }
      },
      scripts: {
        files: ['assets/js/local/bootstrap.js'],
        tasks: ['uglify'],
        options: {
          nospawn: true
        }
      }
    }
  });

  // Load the plugins and tasks
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-less')  
  grunt.loadNpmTasks('grunt-contrib-watch')

  // register task(s).
  grunt.registerTask('default', ['watch']);

};

You basically put all your options into a wrapper function. The first and biggest chunk is the initial config, you pass in a configuration object with the tasks you want to automate. After that you load the plugins and finally register tasks. In this case, when you run “$ grunt” on the terminal, the watch task will be fired.

Gulp

According to Gulp’s site, its features are:

  • Easy to use: Yeah, once you get how to use it it’s kind of easy to use.
  • Efficient: It says it makes use of Node’s streams to make more efficient disk operations.
  • High Quality: In reference to their plugins, they say their plugins stay simple. This is very true. Each plugin does one thing only and it does it well.
  • Easy to learn: Well, not if you just look at their site, I had to search a tutorial to really understand how to use it. But I had a simple set up in about an hour or two of reading their site + the tutorial.

Here’s the set-up (gulpfile.js), it compiles and minifies the Less and uglifies the js with a watch task looking for changes.

//Load the plugins 
var gulp = require('gulp'),
    less = require('gulp-less'),
    minifycss = require('gulp-minify-css'),
    uglify = require('gulp-uglify'),
    rename = require('gulp-rename'),
    notify = require('gulp-notify');

// compile less and minify.
// this can run from Terminal with $ gulp styles.
gulp.task('styles',function (){
    return gulp.src('assets/less/main.less')
        //We use .pipe() to pipe the source file(s) to a plugin.
        //compile the less
        .pipe(less())
        //output it in 
        .pipe(gulp.dest('assets/css'))
        //put the min suffix
        .pipe(rename({suffix: '.min'}))
        //do the actual minifying
        .pipe(minifycss())
        //and output that in the same folder
        .pipe(gulp.dest('assets/css'))
        .pipe(notify({ message: 'Styles task complete' }));

});

//uglify (compress) js
gulp.task('scripts', function() {
    return gulp.src('assets/js/local/bootstrap.js')
        .pipe(rename({suffix: '.min'}))
        //uglify it
        .pipe(uglify())
        .pipe(gulp.dest('assets/js/production'))
        .pipe(notify({ message: 'Scripts task complete' }));
});

//watch files and performs tasks when an event happens.
gulp.task('watch', function() {
    // Watch .less files
    gulp.watch('assets/less/main.less', ['styles']);
    // Watch .js files
    gulp.watch('assets/js/local/**/*.js', ['scripts']);
})

Just run “$ gulp watch” on the terminal and the watch task starts.

Gulp says it’s code over configuration. To be honest I like this way more than the look on Grunt’s configuration.

Gulp’s philosophy is to use a series of connected tubes instead of a big truck. By this they mean plugins should be small and do one thing only. You can then connect these tubes together to achieve more complex tasks.

In terms of the final result, I can do the tasks I did in grunt so they are the same in this matter. Grunt probably has way more plugins though.

Important notes on streams

  • Stream instances are basically Unix pipes.
  • Unix pipes (and so nodejs’ streams) improve performance on I/O operations.
  • The node stream module’s primary composition operator is called .pipe()
  • You can plug the output of one stream to the input of another.
  • Grunt’s way of doing things is more disk heavy resulting in lower performance than Gulp.

Gulp has only 5 functions you need to learn

  • gulps.task(name, fn)
    define a task by passing its name and a function.
  • gulp.run(tasks…)
    Runs all tasks
  • gulp.watch(glob, fn)
    Runs a function when a file that matches the glob changes
  • gulp.src(glob)
    This returns a readable stream which can then be piped to other streams (plugins)
  • gulp.dest(folder)
    Returns a writable stream. Objects piped into this are saved to the file system.
From the slideshow: http://slides.com/contra/gulp#/
From the slideshow:
http://slides.com/contra/gulp#/

Broccoli

Yet Another Food Task Runner… JK. This Task Runner is still in beta so it obviously doesn’t have the support Grunt has.

Visiting the official site https://github.com/broccolijs/broccoli left me very confused. Much like when reading the main page of Gulp. So again I searched for a separate article.

Reading tutorials and articles on Broccoli gave me some important info on this task runner. With Grunt, when your app starts getting big, the build times on Grunt get bigger as well. Grunt rebuilds everything when it is on watch duty and something changes. It doesn’t have knowledge on what the dependencies are on a file it watches. Broccoli on the other hand when run, it will figure out which files to watch and what to rebuild when a file changes.

One big change with Broccoli from the other task runners is that it doesn’t work with files, it uses trees. A tree is a directory with files, and sub-directories with files, and so on. Plugins output trees instead of files and also take trees as input.

Like Gulp, Broccoli defines tasks in a code logic way, instead of Grunt’s configuration look.

When you run “$broccoli serve” broccoli defaults to making its own testing server and you can then run another command after testing your build to actually deploy it. The files in your project aren’t changed like in Grunt and Gulp

Here’s the Brocfile.js for my example.

//Load plugins
var compileES6 = require('broccoli-es6-concatenator')
var uglifyJavaScript = require('broccoli-uglify-js')
var compileLess = require('broccoli-less-single')
var mergeTrees = require('broccoli-merge-trees')

//compile less, this assigns a TREE to the var styles.
var styles = compileLess(['assets/less'],'main.less','/assets/css/main.css');


//export a merge of the styles tree with the public tree (contains index.html)
module.exports = mergeTrees([styles, 'public']);

Super concise code. You run “$ broccoli serve” and what broccoli will do is create its own server.Screen Shot 2014-12-18 at 8.28.20 AMThat server will have, in this case, the tree “styles” merged with the tree “public”. In other words it will only have the index.html with the css (compiled from less).

Whenever you make a change to the less file it will be compiled automatically by broccoli and pushed to the testing server. You don’t need to specify a watch task, its already incorporated.

I have to say this tool looks very promising, but wow, its severely undocumented. I can’t really wrap my head around it, I spent way too much time just trying to do the uglify part. It’s in beta so i hope they really put effort on explaining how to use their tool.

Mimosa 

Now it’s all about cocktail drinks? OK. Mimosa is: “A lightning fast build tool for modern web development”.

I had never heard of it before, but it’s got a very good site, with awesome documentation. I understand a lot  by reading it. I don’t have to search elsewhere to actually understand how to configure the tool. They provide examples, they provide paths to take depending on how you want to implement the tool. Furthermore, when you create a mimosa file in your project by running “$ mimosa config” on the terminal, they even provide you with a documentation of the config file (example).

While configuring the project, running “$ mimosa watch” will generate very detailed errors if there are, so you’re not lost on what the error was and where it happened.

For both the build and watch commands, Mimosa provides an --minify flag that will run Uglify2 over JavaScript assets.

Here’s the mimosa-config.js file:

exports.config = {

    // What modules to use
    modules: ["less","minify-js","minify-css"],

    //watch
    watch: {
        sourceDir: "assets",
        compiledDir: "public",
        javascriptDir: "javascripts"
    }
}

Simple, just specify what modules to use and what to watch. When you run “$ mimosa watch -m” it will start a watch task which will look for changes in the watched files. If its a less file it will compile it and minify it. If its a js file it should uglify it but for some reason it didn’t work when I tried.

So which one do I choose?

Personally, I liked Gulp more than the rest.  It’s code over configuration look makes it easier to read. The pipe system reminds of jquery’s, where you can chain operations and follow step by step what is actually going on. There’s good documentation and support. It’s also being actively developed. For this simple example, it does everything Grunt does. There are a lot of plug-ins.

Broccoli seems to be a better version of Gulp when it comes to compactness and faster rebuild times but it is severely lacking in documentation which turned me away from it. You also need to create a separate “public” folder where the trees will be outputted, I personally don’t like that.

Mimosa seemed like a very good candidate, good documentation and error tracing. But for some reason implementing something trivial like a js uglify wouldnt work.

Edit: 3 months after this article was written, I am still using Gulp. It’s gotten more support and there’s more learning material out there. It’s chaining of operations make it easy to plug and play with a plug-in. 

If you liked what you read, consider subscribing to my blog in order to get notified whenever I publish another article. No spam!
[wp_email_capture_form]


Comments

7 responses to “Task Runners, A comparison between Grunt, Gulp, Broccoli and Mimosa”

  1. David Bashford Avatar
    David Bashford

    FYI, you likely just needed to add “copy” to the modules array for the mimosa one to work. It’s the “compiler” for things that just need to be copied from assets to public.

    1. Jean-Pierre Sierens Avatar
      Jean-Pierre Sierens

      Thanks for this!

  2. donnetox Avatar
    donnetox

    Nice!

  3. Thanks you for this article.
    Actually i am still fond of Gulp, but it seems taht Brunch is efficient and easy to use too.

  4. Pretty decent summary man! I tried Broccoli out a bit ago and found the same problem as you, documentation is awful. If I have to spend most of my time trying to track down how to do something instead of doing it, it’s a waste. Especially since task runners are suppose to reduce unnecessary time waste.

  5. I found most task runners to be extremely non trivial to setup. This is one of the main reasons I created Feri, the easy to use build tool for web files. With Feri, you can be up and running within 10 minutes for most use cases. :D https://www.npmjs.com/package/feri

    If I could not use Feri for any reason I too would prefer Gulp vs. any of the other popular solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *