Grunt vs Gulp: Battle of the Build Tools

In JavaScript land, things move fast and technology advances at a crazy pace. Most modern applications are built using technologies like Vue.js and React, making use of module bundlers like Webpack. Webpack does a lot for you and handles things like bundling, minification and source maps automatically (or through the use of plugins) making old-school task runners like Grunt and Gulp redundant. Or so you might think.

In reality, however, many projects don’t require a full-blown module bundler like Webpack but still need a way to compile their front-end assets. Webpack wasn’t designed to be a task runner and so it isn’t easy to configure it to build assets without bundling modules. So task runners like Grunt and Gulp still have their place and we still use both here at Delicious Brains as build tools for different products we develop.

In this article, we’re going to look at both Grunt and Gulp to see what their differences are and look at the pros and cons of using each as a build tool for front-end assets.

How are Grunt and Gulp Different?

Before we look at how these build tools are different it’s worth noting that they are both designed to do the same thing: automate tasks that you would otherwise have to do manually. These tasks can include:

  • Compiling Less/Sass to CSS
  • Concatenating and minifying CSS and JavaScript
  • Linting code
  • Optimizing images
  • Running tests
  • Much more…

Both of these tools will help you automate the compiling of your front-end assets and make your builds more consistent and reliable, mainly through the use of plugins contributed by the community.

Grunt is probably the more intuitive of the two build tools as it works by defining tasks in a single configuration file called Gruntfile.js. You can specify the configuration for each task and Grunt will run each task in sequence. This makes Grunt files easy to read and understand.

Gulp, on the other hand, focuses on the “code over configuration” principle by making each task definition a JavaScript function. There is no configuration involved up-front (although functions can normally take configuration values) and you can then chain functions together to create your build script. One big advantage of doing this is that Gulp can make use of node streams, meaning that Gulp doesn’t have to write intermediary files to disk. The upshot of this is that Gulp is usually much faster than Grunt, although it can have a slightly steeper learning curve because of the use of streams and promises.

Build Scripts

One of the easiest ways to compare Grunt vs Gulp is to create the same build script using each tool and comparing the configuration files and performance. So let’s do that.

We’ll create a Gruntfile.js and a Gulpfile.js that both do the following:

  • Compile Sass to CSS
  • Concatenate and minifying CSS and JavaScript
  • Optimize images

Before we begin the steps to create the build files I should point out that I’ve created a GitHub repo as a demo of what we are testing here with all of the necessary files and some demo content if you want to check it out.

First, make sure we have both the Grunt and Gulp CLIs installed on our machine so we can run them:

npm install -g grunt-cli gulp-cli

Next, let’s install the packages we require for both Grunt and Gulp to be run. You can do this manually by running npm install {package} --save-dev or by copying the package.json from the repo and just running npm install:

{
    "name": "grunt-vs-gulp",
    "version": "1.0.0",
    "devDependencies": {
        "grunt": "^1.0.1",
        "grunt-contrib-concat": "^1.0.1",
        "grunt-contrib-cssmin": "^2.2.1",
        "grunt-contrib-imagemin": "^2.0.1",
        "grunt-contrib-uglify": "^3.1.0",
        "grunt-sass": "^2.0.0",
        "gulp": "^3.9.1",
        "gulp-clean-css": "^3.9.0",
        "gulp-concat": "^2.6.1",
        "gulp-imagemin": "^3.3.0",
        "gulp-sass": "^3.1.0",
        "gulp-uglify": "^3.0.0"
    }
}

For Grunt, we need to create a Gruntfile.js in the root of our project. It looks like this:

module.exports = function (grunt) {

    grunt.initConfig({
        sass: {
            dist: {
                files: {
                    'dist-grunt/css/style.css': 'assets/scss/style.scss'
                }
            }
        },
        concat: {
            css: {
                files: {
                    'dist-grunt/css/styles.css': [
                        'dist-grunt/css/style.css',
                        'assets/css/test.css'
                    ],
                },
            },
            js: {
                files: {
                    'dist-grunt/js/scripts.js': [
                        'assets/js/test1.js',
                        'assets/js/test2.js'
                    ],
                },
            },
        },
        cssmin: {
            dist: {
                files: {
                    'dist-grunt/css/styles.min.css': ['dist-grunt/css/styles.css']
                }
            }
        },
        uglify: {
            dist: {
                files: {
                    'dist-grunt/js/scripts.min.js': ['dist-grunt/js/scripts.js']
                }
            }
        },
        imagemin: {
            dynamic: {
                files: [{
                    expand: true,
                    cwd: 'assets/img/',
                    src: ['**/*.{png,jpg,gif}'],
                    dest: 'dist-grunt/img'
                }]
            }
        }
    });

    grunt.loadNpmTasks('grunt-sass');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-imagemin');

    grunt.registerTask('default', ['sass', 'concat', 'cssmin', 'uglify', 'imagemin']);

};

As you can see the configuration of Grunt tasks isn’t very expressive but it is fairly easy to understand. Also because Grunt runs tasks in sequence we can make safe assumptions about things like files being available (e.g. dist-grunt/css/style.css has been compiled before running the concat task).

For Gulp we need to create a Gulpfile.js in the root of our project. It looks like this:

var gulp = require('gulp');
var sass = require('gulp-sass');
var concat = require('gulp-concat');
var cleanCSS = require('gulp-clean-css');
var uglify = require('gulp-uglify');
var imagemin = require('gulp-imagemin');

gulp.task('sass', function () {
    return gulp.src('assets/scss/**/*.scss')
        .pipe(sass().on('error', sass.logError))
        .pipe(gulp.dest('dist-gulp/css'));
});

gulp.task('css', ['sass'], function () {
    return gulp.src([
            'dist-gulp/css/style.css',
            'assets/css/test.css'
        ])
        .pipe(concat('styles.min.css'))
        .pipe(cleanCSS())
        .pipe(gulp.dest('dist-gulp/css'));
});

gulp.task('js', function () {
    return gulp.src('assets/js/**/*.js')
        .pipe(concat('scripts.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest('dist-gulp/js'));
});

gulp.task('img', function () {
    return gulp.src('assets/img/*')
        .pipe(imagemin())
        .pipe(gulp.dest('dist-gulp/img'));
});

gulp.task('default', ['css', 'js', 'img']);

As you can see it is much more expressive and allows us to group things in a more logical manner (e.g., grouping all CSS/JS related tasks). Also because of Gulp’s streams implementation, each task can be run in parallel which is part of the reason it is much faster than Grunt.

However, you do have to be careful with this as you need to make sure you don’t break any dependency chains. For example, here we require dist-gulp/css/style.css to exist in the css task, so we need to make sass a prerequisite of the css task to make sure it is run in sequence.

Performance

So now that we have both of these build scripts up-and-running, how do they actually perform?

Grunt
Grunt performance

Gulp
Gulp performance

  • Grunt: 1.6 secs
  • Gulp: 0.59 secs

These results aren’t surprising (as we expected Gulp to be faster) but it does go to show just how much faster Gulp is over Grunt. It’s worth noting that while imagemin took up a big percentage of the process time for both scripts, it seems to run much slower on Grunt but with better compression results. My guess is that this is probably caused by a different default config.

On a small project like ours, a difference of 1 second might not be a big deal. However, for bigger projects that speed difference is multiplied and Grunt can end up being much, much slower in total.

Verdict

So should you use Grunt or Gulp? As with all of these things, there isn’t really a “right” or “wrong” answer. It depends on the context and what you’re comfortable with. As a summary here are some pros and cons for each tool:

Grunt

Pros

  • Easy to learn and configure
  • Mature community with lots of plugins

Cons

  • Can become brittle for larger projects
  • Slower than Gulp

Gulp

Pros

  • Much faster because of the use of streams
  • More flexible due to expressive nature

Cons

  • Steeper learning curve

Personally, I tend to go to Gulp as my first port of call these days, but that doesn’t mean that Gulp is necessarily a better option. The Grunt community is still going strong and both tools look like they’re going to be around for a while yet.

I should mention that another up and coming alternative to task runners like Grunt and Gulp is simply using npm scripts with command line tools. Instead of wrapping tools in plugins to make them work the Grunt/Gulp way, you simply run the tools themselves and use npm scripts to create a build chain. If you’d like to learn more about using npm scripts let us know in the comments. (Check out my follow-up post on using npm scripts as a build tool)

Do you use a task runner like Grunt or Gulp to compile your front-end assets? Do you prefer Grunt/Gulp, if so why? Got any good build tool tips? Let us know in the comments.

About the Author

Gilbert Pellegrom

Gilbert loves to build software. From jQuery scripts to WordPress plugins to full blown SaaS apps, Gilbert has been creating elegant software his whole career. Probably most famous for creating the Nivo Slider.

  • I’ve been using Laravel Mix in all of my projects as a front-end to Webpack. Like practically every other project that comes out of the Laravel ecosystem, Mix’s defaults are opinionated and fit 95% of the use cases I’ve had for building and testing my front-end dev work. https://github.com/JeffreyWay/laravel-mix/tree/master/docs#readme

    • Same. I use Laravel Mix to scaffold components for vue.js and sass even on non-Laravel projects. Makes life easy.

    • Laravel Mix is great. I’ve never used it outside of Laravel but I agree it’s a worthy alternative.

  • Cyril

    … Wepack 🙂

  • Agl

    I use Codekit 🙂
    The best tool for a front-end developer 😉

  • Gulp! Previously I used Grunt for ages when it first came out, then switched to Gulp for a project once and ‘felt’ how fast it was in comparison so learned that. I’ve also used Webpack from time to time which I liked.

    Would love to get into the whole stripped down npm scripts thing somewhere down the line…

    I think the most important thing though is to choose a setup you understand, can work with, and customise/debug where necessary otherwise no matter how good the tool it will slow YOU (the most important part of the equation) down!

    • Gifford Nowland

      “Would love to get into the whole stripped down npm scripts thing somewhere down the line…”

      Do it, what are you waiting for! If you can type a command into the terminal you can use NPM build scripts, it’s basically the same thing XD

    • I totally agree Sean. The most important thing is that whatever you use works for you and your team.

  • gatchaman

    We just updated our dev systems to Sierra and blew up all out Gulp configs. There were just too many changes to node dependencies. Had to reconfig everything at a loss of a full day. Will keep old project as-is, but new projects will all be Codekit again. Not going to miss the redundant node_modules folders everywhere.

    • Yeh node_modules is a pain but I guess any JS based built tools require it. If CodeKit works for you then thats great!

  • gatchaman

    I also see Gulp and Grunt has steps backwards. There’s a strange, almost compulsive need, in web dev to make things more complicated than they need to be. If you told me in 1999 that command line tools would be this popular in 2017, I’d have laughed. Or cried…

  • Kevin Bauman

    I have started using NPM scripts for simpler projects. 31 lines in package.json was enough to watch files, compile Sass, run browser sync, and run autoprefixer at build time.

    I still use Grunt occasionally, and have been using Webpack for Vue.js projects. I do get the feeling though, that Webpack feels overly complex, and I feel coming back to a project after a year or so will be problematic due to the amount NPM dependencies.

  • rick gregory

    I use Codekit but I’m solo, on macOS and I don’t have complex requirements. For things like those described here, CK is plenty. Now, if I were part of a team where people might use other OSes on their dev machines and we all wanted to easily share task runner configs? Gulp.

  • Great post, I use both gulp and Grunt, but would love to know more about just using npm scripts 🙂

  • James Bundey

    moved from Grunt to Gulp because of speed. However, the options and flexibility of packages can differ, so I do still find myself running both on projects, specifically if you need to deploy to SFTP or FTP, where the Grunt packages are far more comprehensive.

    The great thing about both is that 90% of the time you require the same type of package management on projects, so once set-up it’s easily transferrable from project to project.

    • We still use both Grunt and Gulp. Like you say, it depends on the situation.

  • Gifford Nowland

    Why not just use NPM build scripts? Those “many projects [that] don’t require a full-blown module bundler like Webpack” are prime candidates for a quick and easy NPM build script. And since most web projects manage their dependancies with NPM anyway who needs yet ANOTHER bit of software to be a point of failure, keep up-to-date, manage, etc. It’s time we say goodbye to both Grunt and Gulp, they’ve been central at pushing the web forward but they’ve had their time.

    • If you’re comfortable using npm scripts (i.e. CLI tools) on smaller projects then feel free. I still think Grunt/Gulp have their place though. For bigger projects I can imagine npm scripts becoming messy.

      • Gifford Nowland

        I agree but the choice is honestly less a factor of the “size” of the project as much as the complexity of your build process. In my experience 90% of the projects I’ve been a part of in the last couple years (since NPM scripts became feasible) that were built on grunt or gulp would have been just as well if not better on vanilla NPM 🙂

  • I ended up doing a follow up post on using npm scripts as a build tool. Check it out at https://deliciousbrains.com/npm-build-script/

  • Nice quick and easy summary. I’ve used Grunt and I thought it’s not really worth the learning curve. I think npm is the way to go. Not sure but I prefer it even for smaller projects.