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.
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:
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?
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:
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:
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:
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:
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" } }
One thought on “More Gulp in Practice”