Writing an Awesome Build Script with Grunt

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.

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:

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.

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.

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.

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.

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.

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.

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.

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!

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.

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.

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.

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.

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!