Grunt is a fantastic build system for web development, but it can be tricky to set up. In this guide, you’ll learn to configure Grunt to build a modern web project. When you’re done, your Gruntfile will be able to:
- Copy files from a source directory to a build directory
- Remove build files
- Compile Stylus files and add vendor prefixes to them
- Compile CoffeeScript
- Minify CSS and JavaScript
- Compile Jade
- Automatically build source files when they’re modified
- Run a development server
Getting started
If you haven’t already done so, install Node.js and NPM. You also need to install the Grunt command line interface by running npm install -g grunt-cli
. This allows you to run the grunt
command from anywhere on your system.
Create a package.json
with the content below.
json
{
"name": "grunt_tutorial",
"description": "An example of how to set up Grunt for web development.",
"author": "Landon Schropp (http://landonschropp.com)",
"dependencies": {
"grunt": "0.x.x",
"grunt-autoprefixer": "0.2.x",
"grunt-contrib-clean": "0.5.x",
"grunt-contrib-coffee": "0.7.x",
"grunt-contrib-connect": "0.4.x",
"grunt-contrib-copy": "0.4.x",
"grunt-contrib-cssmin": "0.6.x",
"grunt-contrib-jade": "0.8.x",
"grunt-contrib-jshint": "0.6.x",
"grunt-contrib-stylus": "0.8.x",
"grunt-contrib-uglify": "0.2.x",
"grunt-contrib-watch": "0.5.x"
},
"engine": "node >= 0.10"
}
This file defines your project as an NPM package and declares your project’s dependencies. Each dependency has a version number. For example, grunt-contrib-copy: "0.4.x"
tells NPM to install the latest 0.4 version of the grunt-contrib-copy
package. Run npm install
in your console to install the dependencies.
Copy
A good build script always keeps the source code separate from the build files. This separation allows you to destroy the build without affecting your source and prevents you from accidentally editing the build.
To get started, you’ll make Grunt copy the files from a source
directory to a build
directory. Create a Gruntfile.js
file and paste the following into it:
javascript
module.exports = function(grunt) {
// configure the tasks
grunt.initConfig({
copy: {
build: {
cwd: 'source',
src: [ '**' ],
dest: 'build',
expand: true
},
},
});
// load the tasks
grunt.loadNpmTasks('grunt-contrib-copy');
// define the tasks
};
Let’s break this down. In Node, when you require
a module, the modules.exports
function is called and the result is returned. By setting modules.exports
` in the Gruntfile, you’re telling Node to return a function that defines the Grunt configuration. grunt.initConfig
is a method which takes one argument: an object whose properties configure individual Grunt tasks.
Inside the Grunt configuration, you’ve added the configuration for a copy
task. This task has one subtask, called build
. In Grunt, some tasks, called multi-tasks, can have several subtasks which can be called separately. For copy
, you don’t need this feature, but it’s still required to have at least one subtask.
Inside the build
subtask is Grunt’s files array format. This format is one of the ways Grunt allows you to provide the source files to a task. cwd
points to a directory the source files are relative to, and src specifies the source files. '**'
is a globbing pattern that tells Grunt to match any file. dest
is where Grunt will output the result of the task. You’ve set it to "build"
to tell grunt to copy the content to the build directory. If there is a source/index.html
file, this configuration will output build/index.html
. Finally, you set the expand
paramter to true
to enable all of these options.
grunt.loadNpmTasks("grunt-contrib-copy");
tells Grunt to load the tasks from the grunt-contrib-copy
package. This gives us a copy
command, which you can run by typing grunt copy
into your console.
Clean
Now that you have a build
directory, it’s time to write a task that wipes it clean. After the copy configuration, add the following:
javascript
clean: {
build: {
src: [ 'build' ]
},
},
Just like copy
, you have a clean
target with the task’s configuration. The src
of the clean
configuration is set to "build"
to remove the build
directory.
After grunt.loadNpmTasks("grunt-contrib-copy");
, load the clean
task, which will allow you to run grunt clean
from the console.
javascript
grunt.loadNpmTasks('grunt-contrib-clean');
Build
Wouldn’t it be great if you had a build
task that would remove your old build before copying over the new source files? Let’s add one!
javascript
// define the tasks
grunt.registerTask(
'build',
'Compiles all of the assets and copies the files to the build directory.',
[ 'clean', 'copy' ]
);
The registerTask
method creates a new task. The first argument, "build"
, defines the name of the task. The second is a description of the task. The last is an array of tasks that will be run. The build
task runs the clean
task followed by the copy
task.
Stylus
Stylus is a nifty language that compiles to CSS. It enhances CSS in several ways, including adding variables, nesting and functions.
javascript
stylus: {
build: {
options: {
linenos: true,
compress: false
},
files: [{
expand: true,
cwd: 'source',
src: [ '**/*.styl' ],
dest: 'build',
ext: '.css'
}]
}
},
This is slightly different than the other task configurations. There’s still a build
subtask, but it now has two properties: options
and files
. options
specifies how we want the task to behave. We’ve added two options: compress
determines if the CSS output should be compressed and linenos
adds the line numbers of the selectors in the source Stylus files.
files
takes the same file array mapping format as before. This will run the task on all the files in the source
directory that end with .styl
. ext
changes the extension of the output files to .css
.
Now that the stylus
task outputs the CSS files to the build
directory, there’s no reason to have copy the Stylus files to the build
directory anymore. Let’s modify the copy
configuration to prevent that.
javascript
copy: {
build: {
cwd: 'source',
src: [ '**', '!**/*.styl' ],
dest: 'build',
expand: true
},
},
The !
at the beginning of the path prevents grunt from including files that match the pattern. Don’t forget to add "stylus"
to the build
task.
javascript
grunt.registerTask(
'build',
'Compiles all of the assets and copies the files to the build directory.',
[ 'clean', 'copy', 'stylus' ]
);
Autoprefixer
Autoprefixer is a plugin that adds vendor prefixes to CSS3 properties after the Stylus files are compiled to CSS. It’s a great replacement for libraries like Nib and Compass.
Go ahead and add the autoprefixer
configuration.
javascript
autoprefixer: {
build: {
expand: true,
cwd: 'build',
src: [ '**/*.css' ],
dest: 'build'
}
},
Noticing a pattern? This configuration is very similar to the other tasks. One notable difference is cwd
and dest
are both set to "build"
. This makes autoprefixer
output the files to the same folder that it reads them from, which replaces the original files.
As before, you also need to load the Autoprefixer task.
javascript
grunt.loadNpmTasks('grunt-autoprefixer');
Rather than shove all of the CSS tasks into build
, create a new task for stylesheets and add that task to build.
javascript
// define the tasks
grunt.registerTask(
'stylesheets',
'Compiles the stylesheets.',
[ 'stylus', 'autoprefixer' ]
);
grunt.registerTask(
'build',
'Compiles all of the assets and copies the files to the build directory.',
[ 'clean', 'copy', 'stylesheets' ]
);
CSS Minification
Passing a bunch of bulky CSS files to the client can really slow down a website’s loading time. Luckily, the grunt-contrib-cssmin
package minifies CSS files and combines them into a single file. Once again, start with the configuration.
javascript
cssmin: {
build: {
files: {
'build/application.css': [ 'build/**/*.css' ]
}
}
},
Instead of using the files array format, this configuration uses Grunt’s files object format, which maps several files to a single destination. All of the CSS files in the build
directory will be minified and output to build/application.css
.
Load the package and add the CSS minification to the stylesheets
task.
javascript
grunt.loadNpmTasks('grunt-contrib-cssmin');
javascript
grunt.registerTask(
'stylesheets',
'Compiles the stylesheets.',
[ 'stylus', 'autoprefixer', 'cssmin' ]
);
CoffeeScript
CoffeeScript is a fantastic language that compiles to JavaScript. It has clean, beautiful syntax, includes classes and hides a lot of JavaScript’s uglier aspects.
Adding CoffeeScript to the project is easy! First, add the configuration.
javascript
coffee: {
build: {
expand: true,
cwd: 'source',
src: [ '**/*.coffee' ],
dest: 'build',
ext: '.js'
}
},
This pulls in the source CoffeeScript files, changes their extensions to .js
and outputs them to the build
directory. Next, load the grunt-contrib-coffee
package.
javascript
grunt.loadNpmTasks('grunt-contrib-coffee');
Add a scripts
task and add that to the build
task.
javascript
grunt.registerTask(
'scripts',
'Compiles the JavaScript files.',
[ 'coffee' ]
);
grunt.registerTask(
'build',
'Compiles all of the assets and copies the files to the build directory.',
[ 'clean', 'copy', 'stylesheets', 'scripts' ]
);
Once again, you’ll need to add an exception to copy
so the CoffeeScript files are not copied into the build
directory.
javascript
copy: {
build: {
cwd: 'source',
src: [ '**', '!**/*.styl', '!**/*.coffee' ],
dest: 'build',
expand: true
},
},
Uglify
Like cssmin
, UglifyJS minifies JavaScript files and combines them into a single file. Here’s the configuration:
javascript
uglify: {
build: {
options: {
mangle: false
},
files: {
'build/application.js': [ 'build/**/*.js' ]
}
}
},
By default, UglifyJS will replace the names of variables and functions in your scripts with shorter names. This is handy if your project’s code is self-contained, but if it’s shared with another project it can cause problems. Setting mangle to false
turns off this behavior.
Like the cssmin
task, this task also uses the files object format.
Load the package and add "uglify"
to the scripts
task.
javascript
grunt.loadNpmTasks('grunt-contrib-uglify');
javascript
grunt.registerTask(
'scripts',
'Compiles the JavaScript files.',
[ 'coffee', 'uglify' ]
);
Cleaning up
When you run grunt build
, in addition to build/application.css
and build/application.js
, all the other CSS and JavaScript files are hanging around in the build
directory. Since you don’t need them, add subtasks to remove them to the clean
configuration.
javascript
clean: {
build: {
src: [ 'build' ]
},
stylesheets: {
src: [ 'build/**/*.css', '!build/application.css' ]
},
scripts: {
src: [ 'build/**/*.js', '!build/application.js' ]
},
},
When running a task, if you don’t specify a subtask, Grunt will run them all. If you run grunt clean
from the console, it will run clean:build
, clean:stylesheets
and clean:scripts
. This isn’t a problem because if the clean
task can’t remove a file, it just ignores it.
Notice how build/application.css
and build/application.js
are excluded from the stylesheets
and scripts
subtasks. You don’t want to delete those false after all of your hard work!
Update the tasks to use the appropriate subtasks.
javascript
// define the tasks
grunt.registerTask(
'stylesheets',
'Compiles the stylesheets.',
[ 'stylus', 'autoprefixer', 'cssmin', 'clean:stylesheets' ]
);
grunt.registerTask(
'scripts',
'Compiles the JavaScript files.',
[ 'coffee', 'uglify', 'clean:scripts' ]
);
grunt.registerTask(
'build',
'Compiles all of the assets and copies the files to the build directory.',
[ 'clean:build', 'copy', 'stylesheets', 'scripts' ]
);
Jade
Jade is a templating language that makes writing HTML fun. Add Jade to your project by using the grunt-contrib-jade
package.
javascript
jade: {
compile: {
options: {
data: {}
},
files: [{
expand: true,
cwd: 'source',
src: [ '**/*.jade' ],
dest: 'build',
ext: '.html'
}]
}
},
Like the stylus
and coffee
tasks, jade
is configured using the files array format. Notice the data
object inside options
? This object is passed to each template when the Jade files are compiled. It’s handy for things such as creating separate development and production builds or generating dynamic content.
As before, you need to add an exception to the copy
task to prevent Jade files from being copied.
javascript
copy: {
build: {
cwd: 'source',
src: [ '**', '!**/*.styl', '!**/*.coffee', '!**/*.jade' ],
dest: 'build',
expand: true
},
},
Don’t forget to load grunt-contrib-jade
and add it to `build`.
javascript
grunt.loadNpmTasks('grunt-contrib-jade');
javascript
grunt.registerTask(
'build',
'Compiles all of the assets and copies the files to the build directory.',
[ 'clean:build', 'copy', 'stylesheets', 'scripts', 'jade' ]
);
Watch
Your Gruntfile is really starting to shine, but wouldn’t it be nice if you didn’t have to run grunt build
every time you made a change? With grunt-contrib-watch
, you don’t need to! Let’s configure a task that will watch your source code for changes and automatically build them.
javascript
watch: {
stylesheets: {
files: 'source/**/*.styl',
tasks: [ 'stylesheets' ]
},
scripts: {
files: 'source/**/*.coffee',
tasks: [ 'scripts' ]
},
jade: {
files: 'source/**/*.jade',
tasks: [ 'jade' ]
},
copy: {
files: [ 'source/**', '!source/**/*.styl', '!source/**/*.coffee', '!source/**/*.jade' ],
tasks: [ 'copy' ]
}
},
The stylesheets
, scripts
and jade
subtasks watch the Stylus, CoffeeScript and Jade files for changes and runs their respective tasks. The copy
task watches all of the remaining files in the application and copies them to the build directory.
Again, you’ll need to load the grunt task.
javascipt
grunt.loadNpmTasks('grunt-contrib-watch');
Development server
No web development environment is complete without a development server. The grunt-contrib-connect
package is a full-featured static file server that’s perfect for your project.
javascript
connect: {
server: {
options: {
port: 4000,
base: 'build',
hostname: '*'
}
}
}
You’ve configured the server to host the build
directory on port 4000. By default, Connect will only host the site on localhost
, which restricts you from accessing the server outside of your computer. Setting hostname
to "*"
allows the server to be accessed from anywhere.
As before, you’ll also need to load the NPM task.
javascript
grunt.loadNpmTasks('grunt-contrib-connect');
If you try to run grunt connect
from the command line, the server runs and then stops right away. This is because by default the grunt connect task does not run indefinitely. You’ll learn how to fix this in the next section.
Default
Wouldn’t it be great if you had a task that combined all of the other tasks together in one? A default
task is perfect for this.
javascript
grunt.registerTask(
'default',
'Watches the project for changes, automatically builds them and runs a server.',
[ 'build', 'connect', 'watch' ]
);
The default
task runs `build` to create an initial build. Then it starts the Connect server. Finally, it runs watch
to watch the files for changes and build them. Since watch
runs until it’s killed, the Connect server will run indefinitely. Run grunt
in your console and navigate to http://localhost:4000 to see your project!
Conclusion
We’ve covered a lot in this tutorial, there’s so much more Grunt can do. For a complete list of all of the plugins available to Grunt, check out the Grunt plugins site. Happy Grunting!