Search

Pony Foo

Ramblings of a degenerate coder

Grunt Tips and Tricks

(6 comments)reading time: , published

I've been meaning to compile a list of tips and tricks to improve you Grunt workflows, so here it is!

In a Pinch
  • Always --save-dev
  • Heroku Custom Buildpack
  • Forget grunt.loadNpmTasks
  • Spread out watch
  • Use a nice JSHint reporter
  • Keep your Gruntfile organized!
  • Investigate

These are explained and detailed below.

grunt.png

Always --save-dev

Grunt should never be executed after deployments. Keeping build packages separated from the application's dependencies is best practice.

Heroku Custom Buildpack

Heroku isn't an excuse to place Grunt packages as dependencies, either. Simply use a custom buildpack to solve this.

For existing applications, you need to add an environment variable:

heroku config:add BUILDPACK_URL=https://github.com/mbuchetics/heroku-buildpack-nodejs-grunt.git

For new applications, just specify the buildpack:

heroku create myapp --buildpack https://github.com/mbuchetics/heroku-buildpack-nodejs-grunt.git

Next, create a 'heroku' task alias, which will run the build on the Heroku servers. If the build fails, the application won't get deployed, this doubles as some sort of CI safeguard.

grunt.registerTask('heroku', ['jshint', 'build']);

Check out the buildpack's documentation if you need it to work in different environments.

Forget grunt.loadNpmTasks

tasks.png

Use load-grunt-tasks to load your Grunt plugins.

npm i --save-dev load-grunt-tasks

Then, rather than all the calls to grunt.loadNpmTask(), use this one liner.

require('load-grunt-tasks')(grunt);

Never forget to register a Grunt plugin again!

Spread out your watch

Instead of single monolithic watch target that rebuilds the entire project, use targets to run specific parts of your build, reducing the reactions to little bursts, instead of painfully slow builds.

Detailed example:

watch: {
    rebuild: {
        tasks: ['build:rebuild'],
        files: ['Gruntfile.js', 'build/**/*.js']
    },
    jshint_client: {
        tasks: ['jshint:client'],
        files: ['src/client/js/**/*.js']
    },
    jshint_client_tests: {
        tasks: ['jshint:client_tests'],
        files: ['test/client/**/*.js']
    },
    jshint_server: {
        tasks: ['jshint:server'],
        files: ['src/srv/**/*.js', 'app.js']
    },
    jshint_server_tests: {
        tasks: ['jshint:server_tests'],
        files: ['test/server/**/*.js']
    },
    jshint_server_support: {
        tasks: ['jshint:server_support'],
        files: ['Gruntfile.js', 'build/**/*.js', 'deploy/**/*.js']
    },
    test_client: {
        tasks: ['karma:unit_background:run'],
        files: ['src/client/js/**/*.js', 'test/client/**/*.js']
    },
    test_server: {
        tasks: ['mochaTest:unit'],
        files: ['src/srv/**/*.js', 'app.js', 'test/server/**/*.js']
    },
    images: {
        tasks: ['images:debug'],
        files: ['src/client/img/**/*.{png,jpg,gif,ico}']
    },
    css: {
        tasks: ['css:debug'],
        files: ['src/client/css/**/*.styl', 'bin/.tmp/sprite/*.css', 'bower_components/**/*.css']
    },
    js_sources: {
        tasks: ['copy:js_sources'],
        files: ['src/client/js/**/*.js']
    },
    js_bower: {
        tasks: ['copy:js_bower_debug'],
        files: ['bower_components/**/*.js']
    },
    views: {
        tasks: ['views:debug'],
        files: ['src/client/views/**/*.jade']
    },
    livereload: {
        options: { livereload: true },
        files: ['bin/public/**/*.{css,js}','bin/views/**/*.html']
    }
}

Use a nice JSHint reporter

The one I'm currently using is jshint-stylish, which is pretty nice and easy to configure. For example:

grunt.initConfig({
  jshint: {
    options: {
      reporter: require('jshint-stylish')
    },
    foo: {
      files: ['bar.js']
    }
  }
});

Done, pretty JSHint reports!

reporter.png

Keep your Gruntfile organized!

I like to separate my configuration in different files for each workflow: development, release, and deployment. You could use conventions and stuff like that to load different files, but I generally lean towards manually using require, and then merging the configuration together with Lo-Dash's _.merge.

Use the following line in your Gruntfile.

grunt.initConfig(_.merge.apply({}, _.values(require('./build/cfg'))));

Then, do something like this in your ./build/cfg/index.js file. Keep in mind the keys (manifest, dev, env, etc) aren't important.

module.exports = {
    manifest: {
        pkg: grunt.file.readJSON('package.json')
    },
    dev: require('./task/development.js'),
    env: require('./task/environment.js'),
    build: require('./task/build.js'),
    release: require('./task/release.js'),
    deploy: require('./task/deploy.js')
};

In each of those modules, configure any tasks you need to configure for that workflow. Keep in mind that merge works recursively. This enables you to configure different targets for the same task in different files!

Investigate

If something feels too clunky, ask around. Chances are, there is a better way to do things. My unbox project follows many of these practices, and a few more. You might want to check it out while in investigation mode.

Comments(6)

Ryan Nickell

I wasn't aware of jshint-stylish. Much better than the standard output. Thank you for improving my environment.

Nicolas Bevacqua

Glad I could help!

Yoshua Wuyts

I'm trying to seperate everything into seperate files, including my variables. Got any tips on how to properly handle variables in external files?

Nicolas Bevacqua

You mean environment variables? You should be looking at something like nconf for that.

Yoshua Wuyts

I'm building a custom grunt config that can be required by my other projects. What I'm trying to achieve is the following:

  • Save all directory paths in variables that can be passed to tasks.
  • Make the Gruntfile available as a dependency through NPM, being able to run it from the projects root dir.

I'm wondering if there's a way to leverage NPM to share config rather than manually updating my files whenever I need to make a change.

chris

Inspired by your approach, I wrote a Grunt plugin that lets you modularize plugin options and tasks in separate, reusable files. It's simpler and not quite as extensible as your approach, but it allows you to quickly assemble a Grunt workflow without fussing with your Gruntfile. If you use it with load-grunt-tasks, you can have a static Gruntfile that remains the same for every project. Thanks for the tips and inspiration!

https://github.com/chriszarate/grunt-load-options

Pony
Foo
Pony
Foo
Pony
Foo