Javascript developers have been hiding under a browser rock for more than a decade now. Originally a language running only in browsers, it now has moved into its own, with engines like v8 and platforms like NodeJS, Meteor and VertX running on the server, and many MVC platforms running client-side on HTML5 web applications and mobile applications, it’s a true client/server language.
As these platforms have flourished, so too have tools around them – we already discussed bower
and npm
, two tools that help you download and install dependencies. But what about tools to build your application, run tests, distribute minified versions and run quality checks? Enter Grunt.
Grunt – it’s tooling for Javascript
Grunt is a task runner. It has several built-in tasks, and can be extended to encompass whatever tools you desire. Generally the tools are installed via npm
as Javascript and Grunt APIs.
Typical grunt tasks include linting, building (assembling) your runtime application, running test suites, executing your web application, minifying and concatenating your scripts and CSS files, running SCSS jobs, and the like.
Grunt syntax is Javascript, as with our other tools. It has a very specific DSL, which we’ll review in our samples below. It uses npm for its dependencies, and expects a package.json
file with these minimum requirements:
{ "name": "yourProjectName", "version": "0.1.1", // that's whatever version you want "devDependencies" : { "grunt" : "~0.4.2" } }
Installing Grunt
If this is your first time running Grunt, it will prompt you that the grunt-cli
package is not installed. Install this globally on your machine:
npm install -g grunt-cli
Once you’ve created your package.json
file in the root of your project, you can install Grunt with npm install
, which as we saw in our prior post, installs all dependencies in the package file:
npm install
The structure of a Grunt build file
Your grunt build file is created with the name Gruntfile.js
(Gruntfile.coffee
is also allowed for you coffeescript afficianados). Let’s look at a sample:
module.exports = function(grunt) { grunt.task.registerTask('mytask', function() { grunt.log.writeln('this is my first task'); }); grunt.registerTask('default', ['mytask']); };
This simple Gruntfile creates a task, calls it mytask
, and uses Grunt’s log API to write a message to the console. It is then registered as part of the default tasks to run when grunt fires up without any additional arguments. Let’s run it:
$ grunt Running "mytask" task this is my first task Done, without errors.
Grunt is all about plugins
Grunt is pretty much a low-level runner in Javascript without the plugins. In other words, you’ll want to install plugins to get anything significant done. Good news? There are TONS of them. Let’s add one of the most typical ones to do a good old-fashioned linting of our project:
npm install grunt-contrib-jshint --save-dev
For those who read my last post – --save-dev
adds the package to the package.json
file in the devDependencies
section. Now we can register it in Grunt and add it to our default tasks. We’ll also add a config section to the Gruntfile to customize where we are searching for our Javscript files:
module.exports = function(grunt) { grunt.initConfig({ jshint: { all: ['src/**/*.js'] } }); grunt.task.registerTask('mytask', function() { grunt.log.writeln('this is my first task'); }); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.registerTask('default', ['mytask', 'jshint']); };
You’ll note we had to do several things to activate JSHint:
- Used
npm install grunt-contrib-jshint --save-dev
to install the plugin in ourpackage.json
file - Load the
grunt-contrib-jshint
plugin withloadNpmTasks
- Add the
jshint
task to our default task list - Configure the targets for hinting. We have selected anything ending in
.js
in thesrc
directory, recursively
Now we can run Grunt again:
$ grunt grunt-demos [release-1.5●●●] % grunt Running "mytask" task this is my first task Running "jshint:all" (jshint) task Linting src/bad-for-lint.js ...ERROR [L4:C12] W025: Missing name in function declaration. function(a, b) { Warning: Task "jshint:all" failed. Use --force to continue. Aborted due to warnings.
To just run jshint
itself, we can execute it as a task directly:
$ grunt jshint Running "jshint:all" (jshint) task Linting src/bad-for-lint.js ...ERROR [L4:C12] W025: Missing name in function declaration. function(a, b) { Warning: Task "jshint:all" failed. Use --force to continue. Aborted due to warnings.
In either case, our project failed due to jshint failing. You may not wish to include jshint in your build You can modify this by changing the configuration per the plugin documentation.
Testing with Karma
Now we’ll sneak in another great npm-based tool, Karma – a test runner that can automate Jasmine or Mocha test suites. We’ll write a simple Javascript file and test it with Jasmine. Let’s install it:
npm install karma --save-dev
We’ll use the grunt-karma
task runner too:
npm install grunt-karma --save-dev
(The reason the word contrib isn’t in the project name is that it’s not a package supported by the Grunt team).
Our sample JavaScript file to test (src/calculator.js):
var calculator = { add: function(a, b) { return a + b; }, subtract: function(a, b) { return a - b; } }
Our test in Jasmine format (test/calculator-spec.js):
describe("The calculator class", function() { it("should add two numbers", function() { expect(calculator.add(10, 12)).toBe(22); }); it("should subtract two numbers", function() { expect(calculator.subtract(10, 12)).toBe(-2); }); });
We need to add our Karma config file too (karma.conf.js):
module.exports = function(config) { config.set({ basePath: '', frameworks: ['jasmine'], files: [ 'src/**/*.js', 'test/**/*.js' ], exclude: [], port: 8080, logLevel: config.LOG_INFO, autoWatch: false, browsers: ['Chrome'], singleRun: true }); };
Finally, we configure Grunt:
module.exports = function(grunt) { grunt.initConfig({ jshint: { all: ['src/**/*.js'], options: { reporter: require('jshint-stylish') } }, karma: { unit: { configFile: 'karma.conf.js', } } }); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-karma'); grunt.registerTask('default', ['jshint', 'karma:unit']); };
Notice we killed our sample task, and made our default build require that we’ve passed successful linting, AND we ran our tests:
$ grunt Running "jshint:all" (jshint) task ✔ No problems Running "karma:unit" (karma) task INFO [karma]: Karma v0.10.9 server started at http://localhost:8080/ INFO [launcher]: Starting browser Chrome INFO [Chrome 32.0.1700 (Mac OS X 10.9.1)]: Connected on socket b959LkBrB5GT_EiYrrC2 Chrome 32.0.1700 (Mac OS X 10.9.1): Executed 1 of 2 SUCCESS (0 secs / 0.017 secsChrome 32.0.1700 (Mac OS X 10.9.1): Executed 2 of 2 SUCCESS (0 secs / 0.018 secsChrome 32.0.1700 (Mac OS X 10.9.1): Executed 2 of 2 SUCCESS (0.098 secs / 0.018 secs) Done, without errors.
Nice, eh? Turns out we can run individual tasks as well so if we want to kick off karma alone, we can issue grunt karma:unit
(or since we only have one karma configuration, we can just issue grunt karma
).
Going further
You can use a ton of Grunt tasks to minify/uglify your CSS and Javascript code, concatenate multiple source files, and a lot more. Visit the plugins page on the Grunt website and browse around. More importantly, start downloading sample GitHub projects for Node and client-side applications, and you’ll find Grunt at their heart.
In our next blog post, we’ll pull all of these things together to build a sophisticated web application using the Yeoman project creator/manager tool, which generates Bower, Karma, Grunt, and npm configuration files.
For more information on Grunt, visit the website at gruntjs.com.