Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Last active May 6, 2016 03:35
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rich-Harris/9201255 to your computer and use it in GitHub Desktop.
Save Rich-Harris/9201255 to your computer and use it in GitHub Desktop.
The one-size-fits-all key to Grunt.js happiness

This page: http://bit.ly/grunt-happy

One of the complaints people sometimes have about Grunt.js is that the configuration files can grow unwieldy when you have more than a couple of tasks. And you have to explicitly load each of those tasks.

Except that you don't. Here is the one-size-fits-all key to Grunt.js happiness.

1. Set up your project folder

project
|- Gruntfile.js
|- grunt
|  |- config
|  |- tasks
|- node_modules
|- package.json
|- src

This is a fairly typical project folder - we've got a src subfolder with our code in it, plus a package.json file specifying dependencies (and development dependencies) along with our project name and version number etc. We've got a node_modules subfolder courtesy of npm - which has installed the modules specified in package.json.

And we've got a Gruntfile.js. We'll come back to that.

Finally, we've created a grunt subfolder, with two subfolders beneath that - config and tasks.

2. Set up your config

Ordinarily, your Gruntfile.js would look something like this:

module.exports = function ( grunt ) {

    'use strict';

    grunt.initConfig({
        pkg: grunt.file.read( 'package.json' ),
        jshint: {
            // jshint config...
        },
        concat: {
            // concat config...
        },
        uglify: {
            // uglify config...
        }
    });

    grunt.loadNpmTasks( 'grunt-contrib-jshint' );
    grunt.loadNpmTasks( 'grunt-contrib-concat' );
    grunt.loadNpmTasks( 'grunt-contrib-uglify' );

    grunt.registerTask( 'default', [ 'jshint', 'concat', 'uglify' ]);

};

The first thing we're going to do is take out those config options and put them in our grunt/config folder. In that folder, create a uglify.js file:

project/grunt/config/uglify.js:

module.exports = {
    // uglify config...
};

If your task configs use the grunt object themselves in some way, export a function that takes grunt as its argument, and returns the config object:

module.exports = function ( grunt ) {
    return {
        // use the `grunt` object in here, e.g. to read files
    };
};

Do the same for concat and jshint, or whatever tasks you need configs for.

3. Set up your tasks

We can do something similar with custom tasks. For example, create a grunt/tasks/default.js file:

project/grunt/tasks/default.js:

module.exports = function ( grunt ) {
    grunt.registerTask( 'default', [ 'jshint', 'concat', 'uglify' ]);
};

4. Update your Gruntfile.js

Now that we've done steps 1-3, we can replace the contents of Gruntfile.js with the following:

project/Gruntfile.js:

/* The one-size-fits-all key to Grunt.js happiness - http://bit.ly/grunt-happy */

/*global module:false*/
module.exports = function ( grunt ) {

    'use strict';

    var config, dependency;

    config = {
        pkg: grunt.file.readJSON( 'package.json' )
    };

    // Read config files from the `grunt/config/` folder
    grunt.file.expand( 'grunt/config/*.js' ).forEach( function ( path ) {
        var property = /grunt\/config\/(.+)\.js/.exec( path )[1],
            module = require( './' + path );
        config[ property ] = typeof module === 'function' ? module( grunt ) : module;
    });

    // Initialise grunt
    grunt.initConfig( config );

    // Load development dependencies specified in package.json
    for ( dependency in config.pkg.devDependencies ) {
        if ( /^grunt-/.test( dependency) ) {
            grunt.loadNpmTasks( dependency );
        }
    }

    // Load tasks from the `grunt-tasks/` folder
    grunt.loadTasks( 'grunt/tasks' );

};
  1. First, it creates a config object with a pkg property that reads the package.json file, so that we can use tags like <%= pkg.version %> in our source files.
  2. Then, it reads all the files in the grunt/config folder and adds them as properties to the config object, and initialises grunt with said object.
  3. Thirdly, it reads all the modules listed in your package.json file's devDependencies property. Any that begin grunt- are assumed to be grunt plugins, and are loaded.
  4. Finally, it loads any custom tasks found in the grunt/tasks folder.

That's it! Any time you do npm install --save-dev grunt-whatever, the task will automatically be loaded - no need to manually add npm.loadNpmTask('grunt-whatever'). You just need to add a whatever.js file in your grunt/config file. And your custom tasks can grow as hairy and hacky as you like, because the smell will be contained in a single task file rather than overpowering the rest of your Gruntfile.js.

@creynders
Copy link

There are tasks for those :)
jit-grunt or load-grunt-tasks for auto loading of all grunt tasks.
grunt-generate-configs for auto generating config files from a "classic" grunt file.
And load-grunt-configs for auto loading said config files.

(Disclaimer: I wrote the last two)

@Rich-Harris
Copy link
Author

Ha, I should have guessed! 'There's a grunt task for that' is the new 'there's an app for that'. The grunt-generate-configs task looks pretty handy for existing projects, I'll give that a go next time I need to clean something up. The code I put up here is more for starting new projects.

I wasn't aware of jit-grunt, thanks. Going to spend some time with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment