Grunt.js – The Javascript Task Runner – Anatomy of a Grunt project



Grunt.js – The Javascript Task Runner – Anatomy of a Grunt project

0 1


Dont-Be-a-Grunt-Use-Grunt-Slides

Presentation about what Grunt.js is and why you should use it - presented at OKC.js in Dec 2013.

On Github jbavari / Dont-Be-a-Grunt-Use-Grunt-Slides

Grunt.js

The Javascript Task Runner

Josh Bavari / @jbavari

Why use a task runner?

In one word: Automation

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).

I typed 'grunt serve'

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.

It's not a 'new' thing

Its the new thing

You've used some sort of task runner before. Think:

  • Make
  • Rake
  • Ant / Maven
  • Quartz

What can you automate?

(Most) Anything a human can do, Grunt can do (better)

  • Minification
  • Compilation
  • Unit Testing
  • Linting
  • Sass or LESS
  • Preprocessing
  • Live-reloading
  • Image resizing

And more...

Anatomy of a Grunt project

Two main things needed

  • A package.json file to list dependencies
  • A Gruntfile to define the tasks

Anatomy of a package.json file

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"
    }
}

Anatomy of a Gruntfile

A Javascript file that wraps a grunt function and configures a series of plugins. Any valid javascript can go here.

A Gruntfile has:

  • The wrapper function
  • Project and task configuration
  • Loading Grunt plugins and tasks
  • Custom tasks
  • Other Javascript as needed

The wrapper function

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
};

Project and task configuration

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

Loading Grunt Plugins and Tasks

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

Custom Tasks

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

Multi Tasks

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

Grunt Options

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

Gruntfile complete example

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

Getting started is easy

  • Install Grunt
  • Create Gruntfile.js
  • Create package.json

Installing Grunt

Grunt mainly runs from its command line interface in the working directory the Gruntfile

npm install -g grunt-cli

Working with existing Grunt project

If your working directory has a Gruntfile - just install project dependencies and view the defined tasks!

npm install
grunt --help

Preparing a new Grunt project

Create your package.json file - contains all project depenencies

npm init

Create your Gruntfile

Here I'd recommend taking the sample Gruntfile from their website, and start adding in tasks as you see fit

Adding Plugins is easy too

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

Register the plugin in Gruntfile

Now that we added a plugin via npm, we must include in Gruntfile

grunt.loadNpmTasks('grunt-contrib-watch');

Lets get dirty in the mud

The best thing about Grunt is the hundreds of plugins that are easy to configure and use. Lets dive in a few common tasks

Code Minification with UglifyJS

Install the plugin

npm install grunt-contrib-uglify --save-dev

Configure the plugin & load npm task

//...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

Run the task!

grunt uglify

Specify which uglify task target to run

grunt uglify:subset
grunt uglify:all

Getting Sassy

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

Put the sassy pants on

sass: {
  dist: {
    files: {
      './src/stylesheets/styles.css': './src/stylesheets/styles.scss'
    }
  }
}

Run it

grunt sass

Automation on File Modification

Lets watch our SASS / JS / CSS files and when they change, automatically compile or minify.

Grunt Watch Plugin

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');
                        

Gruntfile Config for 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

LiveReload

Include livereload.js file - server pushes file changes to browser

#editable_css - edit my css below and watch me change
/* 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

Automating Unit Testing

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

Jasmine

Run jasmine specs headlessly through PhantomJS.

jasmine: {
    pivotal: {
      src: 'src/**/*.js',
      options: {
        specs: 'spec/*Spec.js',
        helpers: 'spec/*Helper.js'
      }
    }
  }
Enlarge

QUnit

Run QUnit unit tests in a headless PhantomJS instance.

// Project configuration.
grunt.initConfig({
  qunit: {
    all: ['test/**/*.html']
  }
});
Enlarge

Karma

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

Git Hooks

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

Image Tasks

Say you want to resize images, or minify them. There are Grunt Plugins for those.

Image resizing

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

Image Minification

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

Now, write your own

Theres a Yeoman generator to help you write your own Grunt plugins - https://github.com/yeoman/generator-gruntplugin

References