More Gulp in Practice

In my introductory Gulp article, I explained how to set up Gulp, and how to use it to concatenate and minify JavaScript files. In this article, we’re going to see a bunch of techniques that you’ll find useful when working with Gulp in practice.

We’re going to start with the following package.json:

{
  "name": "gulptest",
  "version": "1.0.0",
  "description": "Learning to use Gulp.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Daniel D'Agostino",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^3.9.0",
    "gulp-concat": "^2.6.0",
    "gulp-uglify": "^1.5.1"
  }
}

…and the following Gulpfile.js:

var gulp = require('gulp'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat');
     
gulp.task('default', function() {
  return gulp.src('js/*.js')
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(gulp.dest('dist/'));
});

This is where we left off in the previous Gulp article.

Restoring Packages

And as we have seen in the previous article, it takes a bit of work to set things up the first time. You have to install a bunch of packages, set up your package.json, and then write your Gulpfile. Fortunately, however, this needs to be done only once. We’ve been installing Gulp and the packages it requires using the –save-dev flag for a reason. Once they get added to the package.json, a person getting this file from source control needs only to run the following command:

npm install

We didn’t specify a package to install, so npm will look in your package.json file and install any packages you’re missing. No need to install all the required Gulp packages every time.

gulp-npm-install

It is still necessary, however, to install Gulp globally (npm install gulp -g) to be able to run the gulp command.

Watch

Some people think it’s a pain in the ass to have to run Gulp every time you change a script. Well, it turns out that there’s a Gulp plugin that will run tasks automatically when changes are detected.

All we need to do is add a watch task that will execute a particular task when changes to a specified set of files are detected:

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

Try this by making a change to one of your JavaScript files (e.g. adding a space) and saving the file. watch detects this and runs the default task:

gulp-watch

JSHint

JavaScript is known to be a language with a lot of pitfalls, and there are many reasons why it takes extra effort to do things right. I’m not going to get into the details here; read JavaScript: The Good Parts (book) if you’re curious.

Fortunately, however, there’s a set of tools we can use to help us. These tools carry out a process called linting, which means syntactic analysis of the code. Just like with compiling just about any programming language, syntactic analysis reports any errors in your programming syntax. That is very useful in catching bugs early.

A linter called JSHint can be used by Gulp. In order to use it, we’ll need to install both jshint and gulp-jshint:

npm install jshint gulp-jshint --save-dev

With that done, add JSHint to your task:

var gulp = require('gulp'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat');
     
gulp.task('default', function() {
  return gulp.src('js/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(gulp.dest('dist/'));
});

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

The order of placement of your jshint() is important. If you place it after your concat() call, you’ll see all errors coming from all.js, rather than the actual source filenames.

Now, you’d think jQuery library code would pass linting with flying colours, right? Right?

gulp-jshint-jquery

Development and Production Tasks

If we’re minifying our JavaScript, then how do we debug it? We can’t possibly read minified code.

In fact, we don’t have to. We can set up different tasks for Gulp to run in development and production. For instance:

var gulp = require('gulp'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat');
     
gulp.task('dev', function() {
  return gulp.src('js/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(concat('all.js'))
    //.pipe(uglify())
    .pipe(gulp.dest('dist/'));
});

gulp.task('prod', function() {
  return gulp.src('js/*.js')
    //.pipe(jshint())
    //.pipe(jshint.reporter('default'))
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(gulp.dest('dist/'));
});

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

Notice I’ve changed the name of the tasks, and commented out different parts of the pipeline in dev and prod. We don’t want to minify our JavaScript during development because we want to be able to debug it, and we don’t want to waste time linting it when preparing a release, because hopefully it turned out impeccable as a result of our development. 🙂

Then, just run:

gulp <taskname>

See:

gulp-named-tasks

Task Dependencies

Right, but now, we lost the ability to just run gulp without specifying the task name, because we no longer have a default task. We can fix this as follows:

gulp.task('default', ['dev']);

Instead of specifying a function for the task to execute, we’re giving it an array of task names it is dependent on. In this case, we’re saying that the default task is dependent on the dev task. So Gulp will run dev first, then default:

gulp-dependencies

This is particularly useful when you want to chain tasks. For instance, your dev task will depend on separate tasks for processing JavaScript and CSS. Speaking of which…

CSS

We’ve mostly seen examples dealing with JavaScript files, but Gulp can do much more than that.

Dealing with CSS is not very different from what we’ve seen so far, but we need a separate plugin for minification:

npm install gulp-minify-css --save-dev

Remember to add a require() for this at the top:

    minifycss = require('gulp-minify-css'),

With that available, we can now create a separate task to concatenate and minify CSS:

gulp.task('dev-css', function() {
  return gulp.src('css/*.css')
    .pipe(concat('all.css'))
    .pipe(minifycss())
    .pipe(gulp.dest('dist/'));
});

This is a good time to rename our former dev task to dev-js for clarity:

gulp.task('dev-js', function() {
  return gulp.src('js/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(concat('all.js'))
    //.pipe(uglify())
    .pipe(gulp.dest('dist/'));
});

Then, using the task dependency mechanism we have seen in the previous section, we can set things up so that both these tasks are run when invoking gulp dev or just gulp:

gulp.task('dev', ['dev-js', 'dev-css']);

gulp.task('default', ['dev']);

And here you go:

gulp-css-with-dependencies

Notify

Rather than explaining this one, let’s install it and see what it does:

npm install gulp-notify --save-dev

Let’s update our Gulpfile to notify us when one of the core tasks is ready:

var gulp = require('gulp'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    minifycss = require('gulp-minify-css'),
    notify = require('gulp-notify'),
    concat = require('gulp-concat');
     
gulp.task('dev-js', function() {
  return gulp.src('js/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(concat('all.js'))
    //.pipe(uglify())
    .pipe(gulp.dest('dist/'))
    .pipe(notify({ message: 'dev-js task complete' }));
});

gulp.task('dev-css', function() {
  return gulp.src('css/*.css')
    .pipe(concat('all.css'))
    .pipe(minifycss())
    .pipe(gulp.dest('dist/'))
    .pipe(notify({ message: 'dev-css task complete' }));
});

gulp.task('prod', function() {
  return gulp.src('js/*.js')
    //.pipe(jshint())
    //.pipe(jshint.reporter('default'))
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(gulp.dest('dist/'))
    .pipe(notify({ message: 'prod task complete' }));
});

gulp.task('dev', ['dev-js', 'dev-css']);

gulp.task('default', ['dev']);

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

This, by the way, is the final Gulpfile for this article (in case you want to copy it for faster setting up).

Here’s the result of running Gulp with this:

gulp-notify

Not only does it write to the console, but you get a notification from your system tray. Nice.

Semicolon insertion

If you just bump gulp.src onto the next line like this:

gulp.task('dev-js', function() {
  return
    gulp.src('js/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(concat('all.js'))
    //.pipe(uglify())
    .pipe(gulp.dest('dist/'));
});

…your task will mysteriously not run. That’s because of a JavaScript feature called semicolon insertion. Basically, JavaScript sees fit to put a semicolon after your return statement, making your task ineffective. Just make sure your return statement isn’t isolated like this, and use the style in the previous sections.

Getting more out of Gulp

There are many other useful plugins for Gulp that you can use. Check out Mark Goodyear’s article for examples involving SASS and image manipulation among other things.

The final package.json

Here you go. This should save you some time setting things up. Remember: npm install.

{
  "name": "gulptest",
  "version": "1.0.0",
  "description": "Learning to use Gulp.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Daniel D'Agostino",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^3.9.0",
    "gulp-concat": "^2.6.0",
    "gulp-jshint": "^2.0.0",
    "gulp-minify-css": "^1.2.3",
    "gulp-notify": "^2.2.0",
    "gulp-uglify": "^1.5.1",
    "jshint": "^2.8.0"
  }
}