Watch on YouTube: youtu.be/s5eBIie03ls

Are you still using bash scripts to compile your browser code? Say hello to GruntJS!

A quick thanks to @iammerrick for teaching me everything I know about Grunt

Intro

You can think of Grunt as the connect of build tasks - it allows you to specify how something is built and in what order.

Just like connect, Grunt is, of itself, almost nothing. It's a more of a convention than a real thing, y'know?

The real meat of grunt is in the tasks (just like the real meat of connect is in the middleware).

Conventionally, npm packages that are named with a grunt- prefix (i.e. grunt-pakmanager) are 3rd party grunt tasks, whereas packages prefixed with grunt-contrib- (i.e. grunt-contrib-jade) are part of the grunt core.

Browser Apps

The current market where grunt really shines is in building single-page (or few-page) browser apps.

Everybody has a different religion when it comes to what to use, but we all pretty much agree that html, css, and javascript all suck terribly and we use some other format or program to help us cope.

Pick your poisen. I pick jade, less, and pakmanager.

Standard tasks

You can easily search for all of the tasks available on npm that have gruntplugin in the keywords from http://gruntjs.com/plugins.

If you search for jade, for example, you'll end up at https://npmjs.org/package/grunt-contrib-jade which has a nice README.md detailing the options.

All modules can have different options, but fortunately many common conventions have taken hold across them all.

Example: LESS -> CSS

Just because it's extremely simple, let's start with a simple setup that only compiles from LESS to CSS.

Let's assume we already have a project with this file structure

~/
    - my-project/
        - package.json
        - src/
            - style.less
        - public/
            - .gitkeep

Install Grunt

sudo npm install -g grunt-cli

npm install --save-dev grunt
npm install --save-dev grunt-contrib-less

Add Gruntfile

Now we need to create ~/my-project/Gruntfile.js:

module.exports = function (grunt) {
  "use strict";

  // Configuration
  grunt.initConfig({});
};

And with that we can successfully... do nothing

grunt --help

Add a task to compile LESS to CSS

And now need to go back and add a task ~/my-project/Gruntfile.js:

module.exports = function (grunt) {
  "use strict";

  // Configuration
  grunt.initConfig({
      "less": {
          "development": {
              files: {
                "public/style.css": "src/style.less"
              }
          }
        , "production": {
              files: {
                "public/style.min.css": "src/style.less"
              }
            , options: {
                  yuicompress: true
              }
          }
      }
  });

  // Task Loading
  grunt.loadNpmTasks('grunt-contrib-less');
};

Now with that little bit of config we have some tasks we can run

grunt --help

grunt less:development
grunt less:production

NOTE:

You should know that the targets (development and production) could just have well been called dev and dist or foo and bar. The name has no meaning.

Also note that the name of the files array, the mapping of { "compiled-file.bin": "source-file.src" }, and using an options hash are all common conventions.

Nothing is enforcing those conventions aside from goodwill.

Create a 'build' task

Something about having a bulid process that isn't called build just doesn't feel rigt, so let's change that:

~/my-project/Gruntfile.js:

module.exports = function (grunt) {
  "use strict";

  // Configuration...
  // ...

  // Task Loading
  // ...
  grunt.registerTask('build', ["less:development", "less:production"]);
};

I think this will satisfy my CDO

grunt build

Ahh, much better

Custom tasks

Of course, Grunt may not have pre-built tasks for all the things that you want to do.

The great news is that's it's dirt-simple to add a custom task in quick-n-dirty fashion and then red-green-refactor your way to publishing it (if you want to).

No pressure.

Registering a function

Snippet of Gruntfile.js:

// Configuration
grunt.initConfig({
  // ...
  , "pakmanager": {
        "browser": {
            "files": { "pakmanaged.js": "lib/browser.js" }
        }
      , "node": {
            "files": { "dist.js": "lib/server.js" }
        }
    }
});


// Task Loading
// ...
grunt.registerMultiTask('pakmanager', 'Packages CommonJS modules into a single file', function () {
  // pakmanager defaults to building package.json.main
  grunt.util.spawn({"cmd": "pakmanager", args: ["-e", this.target, "build"]}, this.async());
});
grunt.registerTask('build', ["less:development", "less:production", "pakmanager"]);

Moving that function to a file

mkdir -p grunt-tasks/
touch grunt-tasks/pakmanager.js

In addition to moving the task out to it's own file, I'm also adding options for handling the files hash.

grunt-tasks/pakmanager.js:

module.exports = function (grunt) {
  "use strict";

  grunt.registerMultiTask('pakmanager', function () {
    var files = this.files
      ;

    function pakmanage(f) {
      var source = f.src
        , destination = f.dest
        ;

      grunt.util.spawn(
          {
              "cmd": "pakmanager"
            , args: [
                  "-e"
                , environment
                , "build"
                , source
                , destination
              ]
          }
        , this.async()
      );
    }

    files.forEach(pakmanage, this); // preserving this-ness
  });
};

Snippet of Gruntfile.js:

// Task Loading
// ...
grunt.loadTasks('grunt-tasks/');
grunt.registerTask('build', ["less:development", "less:production", "pakmanager"]);

NOTE: You can have any number of tasks in that folder.

Creating a gruntplugin

npm install -g grunt-init
git clone git://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin

mkdir -p ~/grunt-pakmanager
pushd ~/grunt-pakmanager

grunt-init gruntplugin

The questions you will need to ask probably look something like this:

Please answer the following:
[?] Project name (grunt-pakmanager)
[?] Description (The best Grunt plugin ever.)
[?] Version (0.1.0)
[?] Project git repository (git://github.com/aj/node-pakmanager.git)
[?] Project homepage (https://github.com/aj/node-pakmanager)
[?] Project issues tracker (https://github.com/aj/node-pakmanager/issues)
[?] Licenses (MIT) Apache2
[?] Author name (AJ ONeal)
[?] Author email (awesome@coolaj86.com)
[?] Author url (http://coolaj86.com)
[?] What versions of grunt does it require? (~0.4.1)
[?] What versions of node does it run on? (>= 0.8.0)
[?] Do you need to make any changes to the above before continuing? (y/N)

The last part is to add in the core functionality of our gruntplugin to ~/grunt-pakmanager/tasks/pakmanager.js, which basically entails copying and pasting from the ealier ~/my-project/grunt-tasks/grunt-pakmanager.js.

On the lookout (watch files and rebuilding on change)

// Configuration
grunt.initConfig({
    // ...
  , "watch": {
        "all": {
            "files": ["**.less", "**.jade", "**.js"]
          , "tasks": ["build"]
        }
      , "less": {
            "files": ["style.less", "styles/*.less"]
          , "tasks": ["less:development"]
        }
      , "jade": {
            "files": ["index.jade", "includes/*.jade"]
          , "tasks": ["jade:development"]
        }
      , "js": {
            "files": ["lib/*.js"]
          , "tasks": ["pakmanager:browser"]
        }
      ,
    }
});

// Task Loading
// ...
grunt.loadNpmTasks('grunt-contrib-watch');
// ...

Now I can watch everything all at once with grunt watch:all or I can just watch individual subsets of tasks using screen and having each of grunt watch:less, grunt watch:jade, and grunt watch:js in separate screens.


By AJ ONeal

If you loved this and want more like it, sign up!


Did I make your day?
Buy me a coffeeBuy me a coffee  

(you can learn about the bigger picture I'm working towards on my patreon page )