- 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
Key Takeaways
- Grunt Setup: Begin by installing Node.js, NPM, and the Grunt CLI. Set up your project as an NPM package and specify dependencies in `package.json` to ensure all necessary Grunt plugins are installed.
- Task Configuration: Utilize `grunt.initConfig` to define tasks such as copying files, cleaning directories, and compiling Stylus, CoffeeScript, and Jade files. Each task can have multiple configurations using subtasks.
- Automation and Efficiency: Integrate plugins like Autoprefixer for CSS, Uglify for JavaScript minification, and CSSMin for CSS compression to optimize output files, enhancing the performance of the final product.
- Development Workflow: Implement the `watch` task to monitor changes in the source files and automatically rebuild the project, and use `grunt-contrib-connect` for running a local development server, accessible from any device on the network.
- Build Customization: Create custom tasks using `grunt.registerTask` to sequence operations like cleaning up directories, compiling assets, and copying files, allowing for a modular and maintainable build script that can be easily extended or modified.
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 runningnpm 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 asource
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 abuild
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 abuild
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 theautoprefixer
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, thegrunt-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
Likecssmin
, 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 rungrunt 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 thegrunt-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 rungrunt 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. Thegrunt-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? Adefault
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!Frequently Asked Questions (FAQs) about Writing Awesome Build Scripts with Grunt
What is Grunt and why is it important in writing build scripts?
Grunt is a JavaScript task runner that automates repetitive tasks like minification, compilation, unit testing, and linting. It’s built on Node.js and uses a command-line interface to run custom tasks defined in a file known as the Gruntfile. Grunt is crucial in writing build scripts because it simplifies the process of managing and automating tasks, thus increasing productivity and reducing the chances of errors.
How do I install Grunt on my system?
To install Grunt, you first need to have Node.js and npm (Node Package Manager) installed on your system. Once you have these, you can install Grunt by running the command npm install -g grunt-cli
in your terminal. This installs the Grunt command line interface (CLI) globally on your system.
What is a Gruntfile and how do I create one?
A Gruntfile is a JavaScript or CoffeeScript file that sits in the root directory of your project. It’s where you define and configure tasks that Grunt will run. To create a Gruntfile, create a new file in your project root directory and name it Gruntfile.js. Inside this file, you’ll define your tasks and configurations.
How do I define tasks in a Gruntfile?
Tasks in a Gruntfile are defined within the grunt.initConfig()
method. Each task is a property of this method and can have multiple targets. Each target can have its own set of configuration options. For example, a simple task to minify JavaScript files might look like this:grunt.initConfig({
uglify: {
my_target: {
files: {
'dest/output.min.js': ['src/input1.js', 'src/input2.js']
}
}
}
});
How do I run Grunt tasks?
To run Grunt tasks, you use the grunt
command followed by the name of the task. For example, to run the uglify
task defined in the previous question, you would use the command grunt uglify
in your terminal.
Can I run multiple Grunt tasks at once?
Yes, you can run multiple Grunt tasks at once by passing multiple task names to the grunt
command. For example, grunt task1 task2 task3
. You can also define a default task that runs multiple tasks by using the grunt.registerTask()
method.
How do I use plugins in Grunt?
Grunt has a wide range of plugins that can be used to perform various tasks. To use a plugin, you first need to install it using npm, for example npm install grunt-contrib-uglify --save-dev
. Then, you load it in your Gruntfile using the grunt.loadNpmTasks()
method.
How do I handle errors in Grunt?
Grunt has a built-in error handling mechanism. If a task encounters an error, Grunt will immediately stop executing and display the error message in the terminal. You can also use the --force
option to force Grunt to continue executing tasks even if one fails.
Can I customize the Grunt CLI options?
Yes, you can customize the Grunt CLI options by using flags. For example, the --verbose
flag will display detailed logging information, while the --no-color
flag will disable colored output.
How do I keep my Gruntfile organized?
As your project grows, your Gruntfile can become quite large and difficult to manage. To keep it organized, you can split your tasks into separate files and folders using the load-grunt-config
plugin. This allows you to maintain a clean and organized Gruntfile, regardless of the size of your project.
Landon is a freelance developer specializing in web and mobile applications. He's passionate about building simple things people love to use.