Programming single-page applications is great to build modern web application. Publishing NPM packages are awesome if you write Node.js softwares. What's the link between this two subjects? Both require a complex tool chain (asset managemenet, CSS-preprocessor files compiling, test preparation, JS files concatenation, etc.) to publish your software. In this post, we are going to have look to a new trend: the task runners. They are the new generation of Makefiles for the JS world.
Task runners are a set of tools to make build operation clean and well documented. They provide helpers to manage operations on the project file system via the command line.
In this article, we'll discuss about the most popular tools of the moment: Grunt, Cake, Gulp and Broccoli. For each presentation, we'll illustrate it with a build file for Americano a package of our own. The use case is simple. Americano is a lib composed of one file written with Coffeescript. We want to convert it to Javascript before publishing it to the NPM registry. Here is how looks the file tree:
bin/americano
tests/tests.coffee
Cakefile
README
main.coffee
package.json
We want to convert the main.coffee
file to main.js
. It's the only operation we'll perform in this example.
Grunt
Grunt is the most popular task runner in the Node.js world. The download count from the NPM registry is from far the higher. Grunt built is based on a Gruntfile located at the root of your project. Most of the features require the installation of a specific plugin.
Plugins are based on configuration via a Javascript object. Unless you want to write your own plugin, you mostly write no code logic. The community is very active and you will find a lot of resources about Grunt.
$ npm install -g grunt-cli
$ npm install –-save-dev grunt@0.4.4
$ npm install –-save-dev grunt-contrib-coffee@0.10.0
$ grunt
Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
coffee: {
compile: {
files: {
'./main.js': './main.coffee' }
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.registerTask('default', ['coffee']);
};
Cake
Cake is shipped with Coffeescript and is not supported without it. Its philosophy is the opposite of Grunt.
There, almost everything is described through code logic. It's nice to write small task in a very straightforward way or to write your own task runner on top of it. Of course it requires a Cakefile to describe available tasks. There is no plugin system so you must write every task logic yourself.
To make it short, it's perfect for small operation and doesn't require any new dependencies if you already work with Coffeescript. But for non very simple task it will make your life hard quickly.
$ npm install -g coffee-script
$ cake build
Cakefile
exec = require('child_process').exec
task "build", "Compile coffee files to JS", →
console.log "Compile main file..."
command = "coffee -c main.coffee"
exec command, (err, stdout, stderr) ->
if err
console.log "Error: \n #{err}"
process.exit 1
else
console.log "Compilation succeeded."
Gulp
Let's go back to pure JS task runners with Gulp. This one works the same way as Grunt (one Gulpfile + plugins) but tend to make things simpler by relying on a code logic based on pipes. The idea is that your code is a list of files that require a serie of operation to be applied. The result of each operation is piped to the next one. Plugins are pre-built operation.
The result? A more flexible code than with Grunt, less code required and a lot of plugins to make your life easier. On the counterpart it doesn't provide clean configuration file and, for the moment, the community is less active.
$ npm install -g gulp
$ npm install –-save-dev gulp@3.6.2
$ npm install –-save-dev gulp-coffee@1.4.3
$ gulp
Gulpfile.js
var gulp = require('gulp');
var coffee = require('gulp-coffee');
gulp.task('scripts', function() {
return gulp.src(['./main.coffee'])
.pipe(coffee())
.pipe(gulp.dest('./'));
});
gulp.task('default', ['scripts']);
Broccoli
Last but not least, Broccoli follows the Gulp principle: tasks based on code logic and plugin system for helpers. This time there is no notion of pipe. It's about filters you apply to a tree of file. Basically, it requires to select a folder, then apply to it home-made functions of plugin ones. Broccoli relies more on the command line for parameters.
The direct result is that it produces the most concise code of all the build tools discussed here. Unfortunately the code base is not fully stable yet and the community is less active than the Gulp and Grunt ones.
$ npm install -g broccoli
$ npm install –-save-dev broccoli@0.12.0
$ npm install –-save-dev broccoli-coffee@0.1.0
$ brocoli build ./build
Brocfile.js
var filterCoffeeScript = require('broccoli-coffee');
module.exports = filterCoffeeScript('src', {}); // Default task is automatically set.
Overall
What is our favorite tool? Broccoli! It's the most
flexible tool and the more concise one. It's easier to learn than Grunt and make file selection easier than with Gulp. Cake stays the tool of choice for very simple usecase like the one of this exemple. It won't overload you with dependencies for such a small stack. So, once Broccoli will be totally stable and more documented, it will probably the tool we'll recommend. Until that we prefer Gulp to it. But, if you like to have a lot of resource about your tool, Grunt is the better choice.
So, what about you? What build tool do you prefer? Feel free to tell us what you think in the comments!
NB: I didn't mention Brunch. It's a great build tool too and is great to quickstart. But it acts more as framework by providing you with project template. You can use it for your custom project but it looks less interesting in that case (it's based on configuration, so it has the same flaws as Grunt about flexibilty and verbosity).