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.
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.That 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]
Leave a Reply