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.
We have a complete series on how to develop a WordPress plugin using Webpack & React. 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 take a 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.
Gulp vs Grunt: How are they 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 unit 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. At the time of writing this article, the Grunt plugin registry contained 6,250 plugins, whereas the Gulp plugin registry contained 4000+ different plugins. A considerable amount for both, although Grunt takes the lead here.
How do Grunt and Gulp work?
Grunt works by defining tasks in a file called Gruntfile.js
, structured in a very similar way to JSON. You can specify the configuration for each task and Grunt will run those in sequence. This file is easy to read and understand, making Grunt probably the more intuitive of the two build tools.
Gulp, on the other hand, focuses on the “code over configuration” principle by making each task definition a JavaScript function declared on a file called Gulpfile.js
. Gulp code is often much shorter than Grunt’s because there is no configuration involved up-front (although functions can normally take configuration values). You can then chain functions together, running in parallel or in series, to create your building script.
One big advantage 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.
Building the Scripts
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 file to CSS
- Concatenate and minifying CSS and JavaScript
- Optimize images
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 the following lines into package.json
and just running npm install
:
{
"name": "grunt-vs-gulp",
"version": "1.0.1",
"devDependencies": {
"grunt": "^1.1.0",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-cssmin": "^3.0.0",
"grunt-contrib-imagemin": "^4.0.0",
"grunt-contrib-uglify": "^4.0.1",
"grunt-sass": "^3.1.0",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.6.1",
"gulp-imagemin": "^7.1.0",
"gulp-sass": "^4.1.0",
"gulp-stats": "0.0.4",
"gulp-uglify": "^3.0.2",
"node-sass": "^4.14.1",
"time-grunt": "^2.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) {
const sass = require('node-sass');
const mozjpeg = require('imagemin-mozjpeg');
require('time-grunt')(grunt);
grunt.initConfig({
sass: {
options: {
implementation: 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: {
options: {
optimizationLevel: 5,
use: [mozjpeg()]
},
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 a 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).
Now for Gulp we need to create a Gulpfile.js
in the root of our project. It looks like this:
const gulp = require('gulp');
const sass = require('gulp-sass');
const concat = require('gulp-concat');
const cleanCSS = require('gulp-clean-css');
const uglify = require('gulp-uglify');
const imagemin = require('gulp-imagemin');
sass.compiler = require('node-sass');
function scss() {
return gulp.src('assets/scss/**/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('dist-gulp/css'));
}
function css() {
return gulp.src([
'dist-gulp/css/style.css',
'assets/css/test.css'
])
.pipe(concat('styles.css'))
.pipe(gulp.dest('dist-gulp/css'))
.pipe(concat('styles.min.css'))
.pipe(cleanCSS())
.pipe(gulp.dest('dist-gulp/css'));
}
function js() {
return gulp.src('assets/js/**/*.js')
.pipe(concat('scripts.min.js'))
.pipe(uglify())
.pipe(gulp.dest('dist-gulp/js'));
}
function img() {
return gulp.src('assets/img/*')
.pipe(imagemin([
imagemin.mozjpeg({optimizationLevel: 5})
]))
.pipe(gulp.dest('dist-gulp/img'));
}
exports.default = gulp.parallel(gulp.series(scss, 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. To prevent this, you can define tasks to be run in series as we did with the scss
and css
tasks. Doing it this way, we can be certain that dist-gulp/css/style.css
is already compiled when the css
task needs it.
Performance
So now that we have both of these build scripts up-and-running, how do they actually perform?
- Grunt Tasks: 1.6 secs
- Gulp Tasks: 0.84 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.
On a small project like ours, a difference of ~800 milliseconds 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 a “right” or “wrong” answer – it really comes down to personal preference. 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.
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 tooltips? Let us know in the comments.