TECH.insight

Switching from Grunt to Gulp

Monday 16 March 2015

If your project is using Grunt but you want to try Gulp, this should help you make the switch

Ah the front end build process, coveted territory of the front end developer. Here, the task runner is king and there are many claims to the throne. Grunt was the first task runner to go mainstream and has the largest community that continues to grow. While Gulp is relatively new, it is gaining a strong and dedicated following. This article will not attempt to crown one king, or queen, over the other. Instead, we’ll explore a sample project started with Grunt since it is the most popular, and try to do the same thing in Gulp.

To begin we’re going to compare two configuration files that perform the same tasks. First lets look at the Grunt implementation.

grunt.initConfig({

   // combine all JavaScript files into one named all.js
   concat: {
      'public/dist/all.js': ['public/js/*.js']
   },

   // minifiy the combined JavaScript
   uglify: {
      'public/dist/all.min.js': ['public/dist/all.js']
   },

   // preprocess project LESS files to CSS
   less: {
      development: {
         files: {
            "dist/app.css": "public/styles/app.less"
         }
      }
   },

   // keep monitoring our JavaScript files for changes
   watch: {
      files: ['gruntfile.js', 'public/js/*.js'],
      tasks: ['concat', 'uglify']
   }
});

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

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

The Gruntfile.js file starts with a large config object passed to grunt.initConfig(). In it we’re setting up four tasks that are then loaded via grunt.loadNpmTasks() and registered to task name default.

Now let’s look at how these same tasks are performed in a Gulp implementation in gulpfile.js.

var gulp = require('gulp'),
    concat = require('gulp-concat'),
    rename = require('gulp-rename'),
    uglify = require('gulp-uglify'),
    less = require('gulp-less');

gulp.task('build-js', function(){
  return gulp.src('public/js/*.js')
    .pipe(concat('all.js'))
    .pipe(rename('all.min.js'))
    .pipe(uglify())
    .pipe(gulp.dest('public/dist'));
});

gulp.task('css', ['build-js'], function () {
   gulp.src('public/styles/app.less')
      .pipe(less())
      .pipe(gulp.dest('public/dist'));
});

gulp.task('watch', function() {
  gulp.watch('public/js/*.js', ['build-js']);
});

gulp.task('default', ['build-js', 'css', 'watch']);

In Gulp we’re performing the same tasks as we did in Grunt. When either of these builds is run, the resulting files will be essentially the same. On the surface these two configuration files look fairly similar (and they are), but they are very different in approach.

The Gulp approach can be explored further by looking closely at our first task: what’s going on here?

gulp.task('build-js', function(){
  return gulp.src('public/js/*.js')
    .pipe(concat('all.js'))
    .pipe(rename('all.min.js'))
    .pipe(uglify())
    .pipe(gulp.dest('public/dist'));
});

Right away, Gulp registers the task and the task is defined as a function call. This is different from Grunt, which does everything by passing configuration options to plugins. In Grunt, you simply have to configure tasks, where Gulp gives you more control over what and how the tasks do the work. Let’s look at the above code even more closely:

return gulp.src('public/js/*.js')

The function parameter above is called a glob. Glob patterns can be thought of as regular expressions, but have a much simpler syntax. They are essentially strings that match a file or number of files. The gulp.src() function takes the glob and returns a stream of vinyl files that can be piped to plugins.

return gulp.src('public/js/*.js')
    .pipe(concat('all.js'))

This is where things get interesting. We required the gulp-concat plugin at the top of our gulpfile.js. Here, we are passing the string for what we want to name our final output file. But how does the gulp-concat plugin know which files to combine? Our pipe() function will pass the stream of vinyl files from gulp.src() to it.

The pipe() function is not part of Gulp at all – it’s a component of Node.js. A core concept of Gulp is the ability to pipe files through node streams without ever reading or writing to disk (until we want it to). The intermediary files are mocked in this format called vinyl.

In the above example we are piping the output of gulp.src() to the plugin gulp-concat. That output then gets piped to:

    .pipe(rename('all.min.js'))
    .pipe(uglify())

This will rename our concatenated JavaScript file and minify its contents. The output from that is again piped to:

    .pipe(gulp.dest('public/dist'));

This will write the files to the dist folder. However, gulp.dest() re-emits all data passed to it, so we could continue to pipe to more plugins if we so wished.

Now that we understand the basic components of Gulp tasks, let’s look at the next one.

gulp.task('css', ['build-js'], function () {
   gulp.src('public/styles/app.less')
      .pipe(less())
      .pipe(gulp.dest('dist'));
});

Nothing too fancy here – this task will take the glob of our LESS-format file, pipe it to the plugin and save the output to our dist folder. You may notice one difference between this and our build-js task: in the latter, we returned our stream and here we do not. You’ll also notice we’re setting the build-js task as a dependency via the second parameter.

In Gulp, a task can be made asynchronous if its function accepts a callback, returns a promise, or returns a stream. Otherwise, Gulp will not wait for dependencies to finish and tasks will run concurrently.

In our example, there is no real need to have the build-js task complete before running our CSS preprocessor, except to illustrate the point. There may become a need to have some tasks finish before others are run.

In exploring Gulp, we’ve learned about three of its four major methods: task, src and dest. The final method is watch.

You can read more about their configuration in the Gulp API documentation.

gulp.task('watch', function() {
  gulp.watch('public/js/*.js', ['build-js']);
});

gulp.task('default', ['build-js', 'css', 'watch']);

In their basic form, the remaining items from our config file are fairly straightforward and differ little from Grunt. The watch task takes a glob representing the files to monitor, and an array of tasks to run. Watch comes as a core method in Gulp, so we don’t have to include it as a plugin. Finally, we set up our default task, to be run when we simply type gulp in the command line.

If your project is using Grunt but you want to try Gulp, this article should help you make the switch. With Grunt you can take advantage of thousands of plugins – most of them on GitHub – with straightforward configurations. Not having to do lots of I/O from the disk helps Gulp perform tasks very quickly and is a great strength. As with most front-end tools, each comes with its own considerations and left for the developer to choose the one best suited to the project.

About The Author

Josh is a Principal Application Developer at HBC Digital, parent company of Saks 5th Ave and Lord & Taylor. Years of front-end development of large commercial websites have turned him into a CSS and JavaScript magician with a passion for elegant, scalable code. In his spare time he enjoys living in New York City and discovering new weekend brunch spots.