Modern web development requires managing CSS, JavaScript, HTML, and other assets, and things can get out of hand quickly. Grunt has become the standard for managing all of the tasks related to modern development, from concatenating files to minifying files for production. Unfortunately, most documentation on the Web focuses on how to cut and paste configuration files together. That’s not very helpful.
In this talk you'll learn how to use Grunt for a variety of purposes as we explore how it all works. We’ll cover how to develop Grunt tasks, how to work with files, how Multitasks work, and how to use Grunt and its plugin system to manage the development of a single page app that uses CoffeeScript, Sass, and Angular. When we’re done you’ll know exactly how Grunt works so you can use it on your own projects right away.
4. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
What is Grunt?
Grunt is a task runner written in JavaScript. It has advantages and disadvantages to other build tools.
5. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
However, one of the worst ways to use Grunt is to just copy and paste people’s examples that you find online. There are lots of tutorials out there to
follow, but before you start copying configurations around, you should learn how Grunt handles things under the hood.
6. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Prerequisites
SO to do that let’s work with Grunt from the ground up. In order to do this you’ll need two things: You’ll need NodeJS installed and you’ll need to be
comfortable with the shell. The stuff we’ll do here today is code that works on my Mac. But it’ll work everywhere.
!
7. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Initial Setup
$
npm
install
grunt-‐cli
$
cd
yourwebsite
$
npm
init
8. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
The nom init command brings you through a wizard that asks a few questions about your project. The answers to these questions can be used by Grunt or
other apps later. For example, Grunt can make your application name available as a variable when it writes files. You could also use this file as the source
of your app’s version number.
9. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
package.json
{
"name": "awesomeco",
"version": "0.0.1",
"description": "This is my basic site",
"main": "index.html",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "Brian Hogan",
"license": "MIT"
}
Here’s how it works. That command creates the file package.json which contains all the things we specified. And what’s really important about this is that
this file can also list the dependencies of our app.
10. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
So if we try to run the `grunt` command we get told that it can’t find “local grunt.”
11. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Installing Grunt to a
project
$
npm
install
grunt
-‐-‐save-‐dev
The grunt-cli command we installed earlier lets us use the grunt command. But that doesn’t actually install Grunt. It just installs a command line tool that
is designed to talk to the version of Grunt that is installed as an app dependency.
12. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
And so we install Grunt to our project and then when we try to run Grunt again it says we need a Gruntfile.
13. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Gruntfile.js
'use strict'
module.exports = function(grunt){
};
The Gruntfile is how we configure our tasks. Our Gruntfile looks like this.
!
The expression ‘use strict’ tells the JavaScript interpreter to be more strict on how it processes rules, raises errors, etc.
14. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
A task
'use strict';
module.exports = function(grunt){
grunt.registerTask("hello", function(){
// your code here
});
};
This is a task declaration. We “register the task” by giving it a name and a callback function that gets run. It’s like an event listener.
15. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
“Hello world”?
grunt.registerTask('hello', function(){
console.log("Hello World");
});
There’s a simple task. Works great!!
18. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Arguments
grunt.registerTask('greet', function(name){
grunt.log.writeln("Hello " + name);
});
Code originally shown live during talk.
19. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Errors?
grunt.registerTask('add', function(firstNumber, secondNumber){
firstNumber = Number(firstNumber);
secondNumber = Number(secondNumber);
if(isNaN(firstNumber)) grunt.warn("Nope! Why do you fail?????");
if(isNaN(secondNumber)) grunt.warn("Nope! Why do you fail?????");
var answer = firstNumber + secondNumber;
grunt.log.writeln(answer);
});
Code originally shown live during talk.
20. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
External
'use strict';
module.exports = function(grunt){
grunt.registerTask('praise', function(){
var praise = [
"You're awesome!",
"You are the best developer ever!",
"You deserve a raise!",
"You are good looking!",
"Everyone likes you!"
];
grunt.log.writeln(praise[Math.floor(Math.random() * praise.length)]);
});
};
Code originally shown live during talk.
21. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Loading External Tasks
grunt.loadTasks('tasks');
Code originally shown live during talk.
22. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Chaining
grunt.registerTask('default', ['praise', 'hello', 'greet:Brian']);
grunt.registerTask('build', ['praise', 'hello']);
Code originally shown live during talk.
23. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Simple tasks
• grunt.registerTask
• arguments
• external tasks (grunt.loadTasks)
• Task chaining
• default tasks
So, we have the grunt.registerTask command to define a tasks. We can also define tasks that take arguments. We can have tasks defined outside of the
Gruntfile and load those in, and we can define a task that calls other tasks. We can even have a default task that gets called when we type `grunt`.
24. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Configuration
grunt.initConfig({
mainFile: 'index.html'
});
Grunt tasks are better when they’re configured. We can set up a configuration object and add some stuff to it.
!
25. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
grunt.config.get
grunt.registerTask('printFile', function(){
var file = grunt.config.get('mainFile');
console.log(file);
});
Then we use grunt.config.get to grab the value out!
27. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Config Demo
grunt open:index.html
Chrome Firefox Safari
Let’s use configuration to create a task that opens a page in all the browsers We’ll create a configuration for this and then write a task that launches the
browsers that we configure.
28. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Config
grunt.initConfig({
browsers: [
'open -a Firefox',
'open -a Safari',
'open -a "Google Chrome"'
]
});
Code originally shown live during talk.
29. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
grunt.registerTask('open', function(file){
var browsers = grunt.config.get('browsers');
var exec = require('child_process').exec;
for (var index = 0; index < browsers.length; index++) {
// tell Grunt to wait
var done = this.async();
var command = browsers[index] + ' ' + file;
grunt.log.writeln("Running " + command);
var process = exec(command, function (error, stdout, stderr) {
if (error) {
if(error.code !== 0){
grunt.warn(stderr);
grunt.log.writeln(error.stack);
}
}
// tell Grunt to get going again.
done();
});
}
});
Code originally shown live during talk.
30. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Multitasks
grunt build
basic version full version
Grunt’s multitasks let us build a single task that has many targets. Let’s say we’re doing our own JS framework. We want to build a basic version and a
version that includes jQuery since our library depends on that. We’ll create two versions with one task.
31. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Our library
aquery
lib
a.js
b.js
vendor
jquery.js
So we’ll organize our code like this: our javascript files get split up into their own little files. We’ll put them in the lib/ folder. Then we’ll put jQuery in the
vendor folder.
32. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Config
grunt.config.init({
build: {
aquery: {
src: ['lib/a.js', 'lib/b.js'],
dest: 'dist/aquery.js'
},
aqueryWithJquery: {
src: ['lib/a.js', 'lib/b.js', 'vendor/jquery.js'],
dest: 'dist/.aqueryFull.js'
}
}
});
Multitasks can look at a task’s configuration for source and destination files. Here we’re specifying both versions.
33. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
The Task
grunt.registerMultiTask('build', 'Concatenate files.', function() {
var output = '';
this.files.forEach(function(filegroup) {
var sources = filegroup.src.map(function(file){
return(grunt.file.read(file));
});
output = sources.join(';');
grunt.file.write(filegroup.dest, output);
});
});
Then we use grunt’s multitask declaration, give it the same name as the configuration. and then we can iterate over the files. Grunt has utilities we can use
to open the files, read the contents, and write new files. It’s pretty cool!
35. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Workflows with
plugins!
One of the things that makes Grunt so attractive is its huge library of plugins.
36. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Our plan
sass sass/style.sass stylesheets/style.sass
cssmin stylesheets/style.sass stylesheets/style.sass
37. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Using A Grunt Plugin
• $ npm install grunt-contrib-whatever
• grunt.loadNpmTasks(‘whatever');
• Add configuration.
38. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Plugin Config
grunt.initConfig({
// other plugin configuration
concat: {
options: {
separator: ';',
},
dist: {
src: ['src/intro.js', 'src/project.js', 'src/outro.js'],
dest: 'dist/built.js',
},
},
// other plugin configuration.
});
Here’s an example of a plugin config. But see… the examples in all the docs just tell you to paste a bunch of config options right in the main configuration.
39. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
grunt.config()
grunt.config('concat', {
options: {
separator: ';',
},
dist: {
src: ['src/intro.js', 'src/project.js', 'src/outro.js'],
dest: 'dist/built.js',
},
});
But instead we can use grunt.config to put the code for each plugin in its own configuration block. This makes things much easier to maintain.
40. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Plugins we need
• grunt-contrib-sass
• grunt-contrib-cssmin
• grunt-contrib-watch
So here are the plugins we’ll configure.
42. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Sass
grunt.loadNpmTasks('grunt-contrib-sass');
grunt.config('sass', {
app: {
files: {
'stylesheets/style.css' : ['sass/style.scss']
}
}
});
Code originally shown live during talk.
43. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Watch for changes!
sass stylesheets/style.sass
Changes to
style.scss
We also want to watch for changes.
44. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Watch
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.config('watch', {
styles: {
files: ['sass/**/*.scss'],
tasks: ['sass']
}
});
46. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Build
cssmin stylesheets/style.sass
build
sass
So then all we need to do is make a task that calls these parts. And you can do that with a task chain.
47. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Other Plugins?
• grunt-contrib-jshint
• grunt-contrib-less
• grunt-contrib-concat
• grunt-contrib-coffee
• grunt-contrib-uglify
There are tons of Grunt plugins out there for you to work with. The JSHint plugin will scan your code for syntax and formatting issues. The Less plugin will
let you use Less, another CSS preprocesssor. Into CoffeeScript? use that! You can even make your JavaScript smaller using Uglify. Or you can write your
own Grunt plugin.
48. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Grunt
• Simple tasks
• Multitasks
• Plugins
Grunt is the standard for JavaScript projects right now. Simple tasks are easy to define. Multitasks make it very easy to build a single task that builds
multiple outputs, and plugins make Grunt incredibly flexible.
49. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
Grunt is there for you, to be used to automate everything that you need. If you have to do some repetitive task, you owe it to yourself to automate
everything.
50. Brian P. Hogan
twitter: @bphogan
www.bphogan.com
http://pragprog.com/book/bhgrunt/
More? Here’s a shameless plug. In a couple of weeks, this book will be available for you to own. And the two best questions will get a free ebook copy
when it’s released.