Automate your development tasks using Grunt
Marco Franssen

Loading...
Marco Franssen

Grunt is a useful Node.js package for automating development and continuous integration tasks. The Grunt ecosystem has lots of packages available on npm, which helps us quickly set up a development or CI environment.
Grunt tasks commonly use two configuration properties: files, which specifies the files a task runs on, and options, which contains task-specific settings. File patterns support globbing and minimatch to match files based on the expression you provide.
Which tasks could you use in your projects? How do you configure and run them? In this article, I'll walk you through an example Gruntfile and show you how to automate tasks when your files change.
If you're working on Node.js projects, Grunt is well suited to tasks such as linting your JavaScript with JSHint, running unit tests, and performing static code analysis.
Grunt also fits other web development projects. For example, you can minify JavaScript, compile Less files to CSS, minify images, or replace small image URLs in your CSS with Base64-encoded images.
We've only scratched the surface. If you're working on .NET projects, you can use Grunt there too. The npm registry has tasks for compiling your code with MSBuild, running NUnit and MSpec tests, and calculating code coverage with OpenCover. Grunt simplifies configuring and running these tasks.
What if there's no Grunt task for the thing you'd like to do? There's also a package that can run command-line or shell jobs. You can use it to run your existing command-line tools through Grunt.
With a little JavaScript knowledge, you can write your own Grunt task. That's what I did when I wrote the grunt-dotnet-mspec and grunt-dotnet-codecoverage tasks. Both are available on GitHub, although I haven't implemented the full feature set yet. I accept pull requests to improve them, but I won't go into writing your own Grunt tasks in this post.
Enough chitchat for now. Let's look at a small Gruntfile with some example task configurations.
module.exports = function (grunt) {
"use strict";
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
meta: {
banner:
'/*! <%= pkg.name %> - v<%= pkg.version %> <%= grunt.template.today("yyyy-mm-dd") %> */',
},
secret: grunt.file.readJSON("secret.json"),
jshint: {
options: {
jshintrc: ".jshintrc",
},
gruntfile: ["Gruntfile.js"],
serverside: ["server/src/**/*.js", "server/*.js"],
clientside: ["js/cors/*.js", "js/*.js", "!js/thirdparty/**"],
tests: ["test/**/*.js"],
},
concat: {
options: {
stripBanners: true,
banner: "<%= meta.banner %>",
},
javascript: {
files: {
"build/js/myawesomeapplication.js": [
"js/thirdparty/jquery.iframe-transports.js",
"js/app.js",
"js/validation.js",
"js/controller.js",
"js/router.js",
"js/modules/**/*.js",
],
},
nonull: true,
},
},
uglify: {
options: {
banner: "<%= meta.banner %>",
compress: {
drop_console: true,
sequences: true, // join consecutive statemets with the “comma operator”
properties: true, // optimize property access: a["foo"] → a.foo
dead_code: true, // discard unreachable code
drop_debugger: true, // discard “debugger” statements
unsafe: false, // some unsafe optimizations (see below)
conditionals: true, // optimize if-s and conditional expressions
comparisons: true, // optimize comparisons
evaluate: true, // evaluate constant expressions
booleans: true, // optimize boolean expressions
loops: true, // optimize loops
unused: true, // drop unused variables/functions
hoist_funs: true, // hoist function declarations
hoist_vars: false, // hoist variable declarations
if_return: true, // optimize if-s followed by return/continue
join_vars: true, // join var declarations
cascade: true, // try to cascade `right` into `left` in sequences
side_effects: true, // drop side-effect-free statements
warnings: true,
},
report: "gzip",
},
javascript: {
files: {
"build/js/myawesomeapplication.min.js": [
"build/js/myawesomeapplication.js",
],
},
},
},
});
grunt.loadNpmTasks("grunt-contrib-jshint");
grunt.loadNpmTasks("grunt-contrib-concat");
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.registerTask("default", [""]);
grunt.registerTask("minify", ["concat", "uglify"]);
};I won't explain each task configuration in detail here. The task documentation on npm and GitHub covers these options, so you can find more information there.
You can run a Grunt task using grunt-cli, which you can install globally with npm.
npm install -g grunt-cliThen you can run Grunt tasks in your shell (the Node.js command prompt on Windows) using the following command.
grunt tasknameThis runs the specified task using the options and files in your Gruntfile. A task can also have multiple targets, so you can run it with different options or on different files. To specify a target, append its name to the task name with a colon.
grunt taskname:targetnameFor example, with the Gruntfile above, we can run all targets of the JSHint task:
grunt jshintThis runs all the configured JSHint targets.
That sounds cool, right? We can now run preconfigured tasks manually by typing simple commands. But can we take this one step further? We can define a task in our Gruntfile that runs several other tasks, such as a ci task for everything our continuous integration build needs. This lets us run multiple tasks with one command.
Example:
grunt.registerTask("ci", ["jshint", "concat", "uglify", "less", "cssmin"]);This task can be executed using grunt ci.
You could run this on your Jenkins server whenever new code is pushed, or locally whenever you want to run all these tasks. Of course, manually running tasks after every file change isn't ideal on your local machine. We want to automate that too, using the grunt-contrib-watch task.
Grunt-contrib-watch lets us watch files for changes and run other Grunt tasks automatically. For example, you can lint your JavaScript when *.js files change or compile your Less files to CSS when *.less files change.
Example:
module.exports = function (grunt) {
"use strict";
grunt.initConfig({
//other tasks left for brevity
watch: {
scripts: {
files: "js/**/*.js",
tasks: ["jshint", "mocha:unittest", "phantom"],
},
less: {
files: "less/**/*.less",
tasks: ["less"],
},
styles: {
files: "css/**/*.css",
tasks: ["cssmin"],
},
},
});
grunt.loadNpmTasks("grunt-contrib-watch");
};Now that you know what Grunt can do and how to use it, I'd like to hear about your experiences and favorite Grunt packages in the comments. Your suggestions can help other readers too. Thanks for reading, and have fun playing around with Grunt!
Marco Franssen
Build a reproducible Debian Wheezy VM with Packer and VirtualBox in this 2014 tutorial, using a JSON template and preseed file for unattended installation.
Marco Franssen
Write synchronous and asynchronous Node.js tests with Mocha, Chai assertions and Sinon spies, stubs and fake timers, then run them with Grunt.
Marco Franssen
Learn how to initialize, install, update, and publish Node.js packages with npm in this 2013 walkthrough, including development dependencies and private packages.
Marco Franssen
Build a simple Node.js web server with Express, use npm packages and nodemon during development, and serve an HTML page from a static public folder.