Stop being the grunt, use the grunt
Oh hey, these are some notes. They'll be hidden in your presentation, but you can see them if you open the speaker notes window (hit 's' on your keyboard).With that command, Grunt linted my files, minified CSS & Javascript, ran unit tests, fired up an express server, and watches any changes to css files to automatically update my browser.
You've used some sort of task runner before. Think:
(Most) Anything a human can do, Grunt can do (better)
This file is used by Node.js and list the project dependencies and their versions
{ "name": "AutomateMe", "version": "0.1.0", "devDependencies": { "grunt": "~0.4.2", "grunt-contrib-jshint": "~0.6.3", "grunt-contrib-nodeunit": "~0.2.0", "grunt-contrib-uglify": "~0.2.2" } }
A Javascript file that wraps a grunt function and configures a series of plugins. Any valid javascript can go here.
Every Gruntfile (and gruntplugin) uses this basic format, and all of your Grunt code must be specified inside this function:
module.exports = function(grunt) { // Do grunt-related things in here };
Most Grunt tasks rely on configuration data defined in an object passed to the grunt.initConfig method. The <% %> template strings may reference any config properties, configuration data like filepaths and file lists may be specified this way to reduce repetition
grunt.initConfig({ foo_files: ['./*.js'], bar_files: ['./*.css'], //concat config for concatenating files //command line via 'grunt concat' concat: { foo: { // concat task "foo" target options and files go here. files: '<%= foo_files %>' }, bar: { // concat task "bar" target options and files go here. files: '<%= bar_files %>' }, }, }Enlarge
Any plugins you use or tasks you want to import, you use as such:
// Load the plugin that provides the "concat" task. grunt.loadNpmTasks('grunt-contrib-concat'); //Register the task to run grunt.registerTask('compile', ['clean', 'concat', 'jshint', 'karma', 'uglify', 'preprocess', 'shell:build']); //OPTIONAL //Loads .js files in ./tasks directory grunt.loadTasks("tasks"); //tasks folder - compile.js module.exports = function(grunt) { grunt.registerTask('compile', ['clean', 'concat', 'jshint', 'karma', 'uglify', 'preprocess', 'shell:build']); };Enlarge
You can define custom tasks with Javascript or multiple tasks with targets
module.exports = function(grunt) { // A very basic default task. grunt.registerTask('log', 'Log some stuff.', function() { grunt.log.write('Logging some stuff...').ok(); }); };Enlarge
grunt.initConfig({ log: { foo: [1, 2, 3], bar: 'hello world', baz: false } }); grunt.registerMultiTask('log', 'Log stuff.', function() { grunt.log.writeln(this.target + ': ' + this.data); });Enlarge
You can think of Grunt options as the command line parameters you pass to grunt for calling tasks. They can be used anywhere in
module.exports = function(grunt) { //grunt express --host=server.raisemore.com var host = grunt.option('host'); };Enlarge
module.exports = function(grunt) { //Standard javascript - retrieve a command line parameter //Say the user typed 'grunt --foo=bar' var foo = grunt.option('bar') || 'fubar'; grunt.initConfig({ // Arbitrary non-task-specific properties. my_property: 'whatever', our_file_list: ['./js/*.js'], pkg: grunt.file.readJSON('package.json'), // jshint task configuration here - you can specify several targets per task jshint: { all: { options: { smarttabs: false //specify options for just this target }, files: '<%= our_file_list %>' //Special Grunt syntax to get a config variable }, just_a_few: { files: ['./js/index.js', './js/nav.js'] } // specify global options for all targets options: { smarttabs: true, eqnull: true, eqeqeq: false } } } }); // Load the plugin that provides the "jshint" task. grunt.loadNpmTasks('grunt-contrib-jshint'); // Default task(s). grunt.registerTask('default', ['jshint']); grunt.registerTask('custom', 'This is how you define a custom task.', function(arg1, arg2){ //Javascript here folks }); };Enlarge
Grunt mainly runs from its command line interface in the working directory the Gruntfile
npm install -g grunt-cli
If your working directory has a Gruntfile - just install project dependencies and view the defined tasks!
npm install grunt --help
Create your package.json file - contains all project depenencies
npm init
Here I'd recommend taking the sample Gruntfile from their website, and start adding in tasks as you see fit
Find the plugin you want to use, and just run npm install
//We use the --save-dev flag to save this entry to our package.json file npm install grunt-contrib-uglify --save-dev
Now that we added a plugin via npm, we must include in Gruntfile
grunt.loadNpmTasks('grunt-contrib-watch');
The best thing about Grunt is the hundreds of plugins that are easy to configure and use. Lets dive in a few common tasks
Install the plugin
npm install grunt-contrib-uglify --save-dev
//...snip previous config... uglify: { subset: { files: { 'dest/output.min.js': ['src/input1.js', 'src/input2.js'] } }, all: { files: { 'dest/all.min.js': [''] } } } }); grunt.loadNpmTasks('grunt-contrib-uglify');Enlarge
grunt uglify
Specify which uglify task target to run
grunt uglify:subset grunt uglify:all
Just like we saw at Thunder Plains, we can use Sass with the sass ruby gem and the grunt sass plugin.
gem install sass npm install grunt-contrib-sass --save-dev
sass: { dist: { files: { './src/stylesheets/styles.css': './src/stylesheets/styles.scss' } } }
grunt sass
Lets watch our SASS / JS / CSS files and when they change, automatically compile or minify.
Using the Grunt Watch plugin, we can monitor file changes and execute tasks on file change events.
//Command Line to install plugin npm install grunt-contrib-watch --save-dev //Gruntfile to load plugin grunt.loadNpmTasks('grunt-contrib-watch');
// ...snip prior config... watch: { main: { files: [ 'Gruntfile.js', 'js/reveal.js', 'css/reveal.css' ], tasks: 'default' }, edits: { files: [ 'css/editable.css' ], options: { livereload: 35729, } } },Enlarge
Include livereload.js file - server pushes file changes to browser
/* Edit the CSS here and click save to live reload */ #editable_css { color: white; border: solid 1px red; }Click me to update CSS with LiveReload
Grunt has a wide variety of plugins to assist with testing. Perhaps a good solution would be to set up watch to see any javascript changes, and on those changes execute the unit tests
Run jasmine specs headlessly through PhantomJS.
jasmine: { pivotal: { src: 'src/**/*.js', options: { specs: 'spec/*Spec.js', helpers: 'spec/*Helper.js' } } }Enlarge
Run QUnit unit tests in a headless PhantomJS instance.
// Project configuration. grunt.initConfig({ qunit: { all: ['test/**/*.html'] } });Enlarge
Run all your unit tests for Jasmine/QUnit/Mocha through multiple browsers Chrome/Firefox/Safari/Phantom
karma: { options: { configFile: 'karma.conf.js', runnerPort: 9999, browsers: ['Chrome', 'Firefox'] }, continuous: { singleRun: true, browsers: ['PhantomJS'] }, dev: { reporters: 'dots' } }Enlarge
It is worth mentioning that there is a Grunt plugin for Git-hooks - enforce testing on users before they commit.
githooks: { all: { 'pre-commit': 'test' } }Enlarge
Say you want to resize images, or minify them. There are Grunt Plugins for those.
image_resize: { android_small: { options: { height: 426, width: 320 }, files: { //Destination : source './android/res/drawable/splash.png': resize_file } }, android_normal: { options: { height: 470, width: 320 }, files: { //Destination : source './android/res/drawable-mdpi/splash.png': resize_file } }, android_large: { options: { height: 640, width: 480 }, files: { //Destination : source './android/res/drawable-ldpi/splash.png': resize_file, './android/res/drawable-hdpi/splash.png': resize_file } }Enlarge
imagemin: { static: { options: { optimizationLevel: 3 }, files: { 'dist/img.png': 'src/img.png', // 'destination' : 'source' 'dist/img.jpg': 'src/img.jpg', 'dist/img.gif': 'src/img.gif' } }, dynamic: { files: [{ expand: true, cwd: 'src/', src: ['**/*.{png,jpg,gif}'], dest: 'dist/' }] } }Enlarge
Theres a Yeoman generator to help you write your own Grunt plugins - https://github.com/yeoman/generator-gruntplugin