Why every other article about Grunt misses the point

Have you ever heard about Grunt? If you follow frontend technologies you've heard for sure. So you probably know that Grunt is a JavaScript task runner and that there is a huge repository of Grunt plugins. With thousands of plugins in one place it's really tempting to use grunt-contrib-*_ tasks for everything. In this article I show that preferring custom tasks in favor of community plugins leads to more concise and manageable code.

The Hello World for Grunt tutorials is copying files using grunt-contrib-copy task. You can easily find 20k+ articles regarding this topic, but the truth is you don't need grunt-contrib-copy at all. Grunt gives you this functionality out of the box:

module.exports = function(grunt) {
  grunt.registerTask('copy', function() {
    grunt.file.copy('src/application-build.js',
                    'target/application.js');
  })
};

In the example above I defined a custom task called copy which copies one file from src to target directory using Grunt API. You can run it with command grunt copy. It doesn't rely on any dependencies beyond Grunt.

What if you want to do something more robust than simply copying a file. Let's say you want to minify your JavaScript. There is a Grunt task called grunt-contrib-uglify which suggests doing stuff like below (the listing is from the official documentation of this task):

grunt.initConfig({
  uglify: {
    my_target: {
      options: {
        beautify: true
      },
      files: {
      'dest/output.min.js': ['src/input.js']
      }
    },
    my_advanced_target: {
      options: {
        beautify: {
          width: 80,
          beautify: true
        }
      },
      files: {
        'dest/output.min.js': ['src/input.js']
      }
    }
  }
});

Do you think it's awesome? What will happen if you need to add yet another target? The grunt.initConfig() will quickly become unmanageable. Let's write the same logic in a form of custom tasks:

var UglifyJS = require("uglify-js");

module.exports = function(grunt) {
  function uglify(sources, destination, width) {
    var output = UglifyJS.minify(sources, { output: { width: width, beautify: true } });
    grunt.file.write(destination, output.code);
  }
  grunt.registerTask('my_target', function() {
    uglify('src/input.js', 'dest/output.min.js');
  });
  grunt.registerTask('my_advanced_target', function() {
    uglify('src/input.js', 'dest/output.min.js', 80);
  });
};

Here we referred directly to the UglifyJS2 library. The task grunt-contrib-uglify does the same thing under the hood, so we've just removed one abstraction layer and one source of potential bugs. Moreover we extracted the code responsible for minification to a function. This approach gives you all the good stuff which comes with functions - you can name it, reuse it, refactor, move to the external file etc.

The problem with tutorials about Grunt is that they describe plugins more than Grunt itself. I encourage you to get familiar with Grunt API and to give custom tasks a shoot before going blindly to the community plugins (which are usually less reliable than they claim to be).

This website, like many others, uses small files called cookies to help us provide social media features and to analyse our traffic.
You consent to our cookies if you continue to use this website.

OK