Task Automation Manager

Why Task Automation?

Task automation allows for a more efficient development workflow.

Rather than having to manually execute repetitive tasks, developers can use task automation managers and a myriad of community plugins that are easy to learn and use.

Grunt vs. Gulp

Currently the two most prominent task automation managers are Grunt and Gulp. While both achieve the same goal, they take different paths to get there.

1. Task Setup

Code over configuration refers to the style in which Gulpfiles are written. Gruntfiles consist mainly of a configuration object and task definitions. Gulpfiles look more like code written in Node.js.

To see the main difference in task setup, let's look at an example.

We're going to create a task that concatenates and minifies all Javascript files.

Gruntfile.js

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      dist: {
        src: ['client/app/app.js', 'client/app/**/*.js'],
        dest: 'client/dist/scripts.js'
      }
    },
    uglify: {
      options: {
        compress: true,
        mangle: true
      },
      my_target: {
        files: {
          'client/dist/scripts.js': 'client/dist/scripts.js'
        }
      }
    }
  });

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

  grunt.registerTask('default', ['concat', 'uglify']);
}
  1. Grunt requires configuring the different plugins in the object passed into the grunt.initConfig(configObject) method.

  2. Then the plugins need to be loaded individually in the grunt.loadNpmTasks(nameOfPlugin) method.

    This plugin will load all Grunt NPM tasks at once: load-grunt-tasks

  3. Finally, tasks must be registered by using the grunt.registerTask(nameOfTask, [taskOne, taskTwo]) method. In the example above, we registered a default task so just running grunt will execute the 'concat' and 'uglify' tasks.

Gulpfile.js

var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');

gulp.task('buildJS', function() {
  return gulp.src('client/app/**/*.js')
    .pipe(concat('scripts.js'))
    .pipe(uglify())
    .pipe(gulp.dest('client/dist/'));
});

gulp.task('default', ['buildJS']);
  1. Gulp requires loading the plugins by using the native NodeJS require(nameOfPlugin) function.

  2. Gulp tasks are registered using the gulp.task(nameOfTask, callbackFn) method.

    • If the callbackFn accepts another callback as a parameter or returns either a stream or a promise, the tasks can be run asynchronously. In the example above, the callbackFn returns a stream.

    • gulp.src(globs) specifies the source files we want to run the tasks on. The globs parameter can be a string or an array of strings. This method allows us to pipe files matching the glob(s) to plugins.

    • The first plugin concatenates all the files matched by the glob into one scripts.js file.

    • The scripts.js file is then piped into the uglify plugin which will minify the contents of the file.

    • Finally, the concatenated and minified file is piped into the client/dist/ directory using the gulp.dest(directoryPath) method.

  3. You set the buildJS task as the default task and run the gulp command or you can call run the buildJS task directly by running gulp buildJS.

The Gulpfile is more concise than the Gruntfile. In larger projects with more tasks, a giant Grunt configuration object may be confusing to navigate.

Back to top

2. Task Execution

Grunt runs tasks in sequence while Gulp will run tasks with maximum concurrency.

For example, if you wanted to run these tasks:

  1. Concatentate all Javascript files

  2. Minify the concatentated script file

  3. Compile SASS to CSS

  4. Watch all CSS and JS files for changes

the Grunt task would look like this:

grunt.registerTask('buildApp', ['concat', 'uglify', 'sass', 'watch']);

where each task would run after the preceding task is complete. This ensures that the defined task sequence is the sequence in which the tasks will execute.

Gulp uses a module called orchestrator for sequencing with maximum concurrency. What orchestrator will do is launch all the tasks specified at the same time. So if you were to mimic the Grunt task above:

gulp.task('buildApp', ['concat', 'uglify', 'sass', 'watch']);

the order that the tasks complete isn't guaranteed because all tasks will start at once. The uglify task might complete before the concat task.

Gulp does provide a way to execute tasks in a specified sequence using dependencies in tasks.

In the example above, to run all tasks in the desired sequence, specify the preceding task as a dependency as the second parameter when defining a task.

gulp.task('concat', ...);

// this task is dependent on the `concat` task completing
gulp.task('uglify', ['concat'], ...);

// this task is dependent on the `uglify` task completing
gulp.task('sass', ['uglify'], ...);

// this task is dependent on the `sass` task completing
gulp.task('watch', ['sass'], ...);

Note: The example above is just to demonstrate how Gulp works. It would be much more common to have the concat and uglify tasks defined in a single task, where the output of concat would be piped to uglify.

Running gulp watch will run the concat task, then the uglify task, then the sass task, and finally the watch task.

However, the above example does not utilize the benefits of maximum concurrency. Total time to complete all tasks will be shortened if unrelated tasks run at the same time, rather than sequentially.

The example above can be re-written like so:

gulp.task('concat', ...);

// this task is dependent on the `concat` task completing
gulp.task('uglify', ['concat'], ...);

gulp.task('sass', ...);

// this task is dependent on the `sass` task and the 'uglify' task completing
gulp.task('watch', ['sass', 'uglify'], ...);

Now running gulp watch will launch both the sass and uglify tasks at the same time. Since the compilation of SASS to CSS is not related to the minification of Javascript files, it is okay to start these tasks streams concurrently for faster execution time.

Note: 1. The next version of Grunt will also use the orchestrator module so the speed advantage that Gulp offers might be offset. 2. To replace the somewhat convoluted sequencing through dependencies, the next version of Gulp will use the undertaker module which will allow you to specify whether you want to run tasks in sequence (gulp.series) or in parallel (gulp.parallel).

Back to top

3. I/O

One of Gulp's distinguishing features is it's usage of Node.js streams.

With Grunt, each task needs to read the relevant files, transform the contents, and write the results to a temporary folder, so that the next task is able to repeat this process. The problem with this is that opening and closing files and creating a temporary folder are "expensive" operations.

Streams allow for the output of a task to be piped to the input of the next task. This is accomplished in Gulp by using vinyl and vinyl-fs.

According to the documentation, "Vinyl is a very simple metadata object that describes a file." Along with the vinyl-fs adapter, this allows for transformations to happen on the vinyl object which is all done in memory.

Note: If this is at all confusing, you've run into one of the cons of Gulp. Gulp does require some understanding of streams and if you come from a non-Node.js background, this may be another concept you have to learn.

What does this mean for performance?

Grunt took 207 ms to complete and Gulp took a little over 73 ms.

When dealing in milliseconds, this difference may seem negligible, but when working with larger projects and more tasks, the difference may grow.

Note: These results should be taken with a grain of salt because the way both task runners measure time may be slightly different. However, in researching this topic, it seems that other developers have seen similar outcomes. I've included those articles in the Additional Reading section in bold.

Back to top

4. Error Handling

Error handling is probably Gulp's biggest weakness.

Node.js streams will stop working upon encountering an error. For certain tasks such as linting, this is undesirable because you would want the entirety of the source code to have been run through the linting task. If an error occurs somewhere in the first half of the files passed through the task, the process will exit and the second half would never have been "linted."

There is currently a monkey patch that modifies the pipe function to not exit upon erroring.

One of the creators of Gulp has admitted that this is an issue which will be addressed in the next major release.

Grunt doesn't use streams and has a more reliable fail API for error handling. It provides options such as --force to continue the task and --stack to output a stacktrace.

Back to top

5. Community

"There are three kinds of lies: lies, damned lies and statistics." - Mark Twain

Grunt's community may be its biggest strength. It is the older of the two and as of June 2016, Grunt has 5,759 plugins compared to Gulp's 2,458. This is not an insignificant difference.

With that in mind, recent trends may show that Gulp is on the rise.

Note: Statistics can be deceptive. For example, if Gulp had more downloads than Grunt the past month, that may be more indicative of Grunt's market saturation rather than Gulp's popularity. Stats over a shorter period of time are even less reliable. I will just present the numbers and you may decide if they are significant in your decision.

Note: Grunt's statistics start from it's release in 11/2012 to 06/2016. Gulp's statistics start from it's release in 07/2013 to 06/2016.

Note: Watching a repository means all pull requests and issues will be show up in the news feed. Starring a repository is meant to track and show appreciation for a project without receiving activity notifications. Forking a repository copies a project and suggested changes to the original project can be made via a pull request.

Back to top

Conclusion

In the end, choosing a task runner comes down to personal preference. If you are more familiar with Grunt or if the majority of your team knows Grunt but not Gulp, it might not make sense to make the switch.

However, we at CodeSoju, in our attempt to outline best practices in creating enterprise applications, are of the opinion that the benefits of Gulp outweigh the benefits of Grunt. The more readable task configurations, increased performance benefits, and a growing community are why we recommend Gulp for your task automation needs.

Back to top

Additional Reading

Comparisons

Articles in bold contain speed comparisons.

Grunt

Gulp

Back to top

Last updated