Fun with Gulp

I’ve been a happy Grunt user for a while now. I use it for dev watch/preprocessing/livereload type stuff, and it works great. It is, however, a bit cantankerous to set up. Sometimes you can’t seem to get around making intermediary files when stringing tasks together either, which means crud in your folders. I hate folder crud.

Gulp is the new hot stuff for task running. I hadn’t heard of it until a few weeks ago, but after having a play with it it might replace Grunt in my stack. The configuration is easier to set up and read, and because it uses node streams it can net you a decent performance boost.

Here’s a config file I just made for one of my projects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Load plugins
var gulp = require('gulp'),
less = require('gulp-less'),
autoprefixer = require('gulp-autoprefixer'),
minifycss = require('gulp-minify-css'),
uglify = require('gulp-uglify'),
imagemin = require('gulp-imagemin'),
concat = require('gulp-concat'),
replace = require('gulp-replace'),
refresh = require('gulp-livereload'),
lr = require('tiny-lr'),
server = lr();

var jsFiles = [
'assets/scripts/vendor/jquery-2.0.3.min.js',
'assets/scripts/vendor/bootstrap/transition.js',
'assets/scripts/vendor/bootstrap/button.js',
'assets/scripts/vendor/bootstrap/collapse.js',
'assets/scripts/vendor/bootstrap/dropdown.js',
'assets/scripts/vendor/leaflet/leaflet.js',
'assets/scripts/vendor/leaflet/leaflet.d3.js',
'assets/scripts/vendor/jquery-ui-1.10.3.custom.min.js',
'assets/scripts/vendor/chosen.jquery.js',
'assets/scripts/vendor/d3.v3.js',
'assets/scripts/vendor/pagedown.js',
'assets/scripts/vendor/pubsub.js',
'assets/scripts/vendor/queue.v1.min.js',
'assets/scripts/vendor/topojson.v0.js',
'assets/scripts/vendor/d3.tip.v0.6.3.js',
'assets/scripts/vendor/typeahead.js',
'assets/scripts/vendor/underscore-min.js',
'assets/scripts/vis/*.js',
'assets/scripts/datameta.js',
'assets/scripts/page.js'
];

// Less preprocessing with autoprefixer and minify
gulp.task('styles', function() {
return gulp.src('assets/less/main.less')
.pipe(less())
.pipe(autoprefixer('last 2 version', 'safari 5', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
.pipe(minifycss())
.pipe(gulp.dest('public/css'))
.pipe(refresh(server));
});

// Script concatenation
gulp.task('scripts', function() {
return gulp.src(jsFiles)
.pipe(concat('main.js'))
.pipe(gulp.dest('public/js'))
.pipe(refresh(server));
});

// Script uglify
gulp.task('uglify', function() {
return gulp.src('public/js/main.js')
.pipe(uglify())
.pipe(gulp.dest('public/js'));
});

// Image Minification
gulp.task('imagemin', function() {
return gulp.src('assets/images/build/*')
.pipe(imagemin({
optimizationLevel: 3,
progressive: true,
interlaced: true
}))
.pipe(gulp.dest('public/images'));
});

// HTML refresh
gulp.task('html', function() {
return gulp.src(['public/**/*.html'])
.pipe(refresh(server));
});

// Image refresh
gulp.task('images', function() {
return gulp.src(['public/**/*.png', 'public/**/*.gif', 'public/**/*.jpg', 'public/**/*.svg'])
.pipe(refresh(server));
});

// Cache busting
gulp.task('replace', function() {
return gulp.src('public/index.html')
.pipe(replace(/foo=[0-9]*/g, 'foo=' + Math.floor((Math.random() * 100000) + 1)))
.pipe(gulp.dest('public/'));
});

// live reload
gulp.task('livereload', function() {
server.listen(35729, function(err) {
if (err) {
return console.log(err);
}
});
});

// dev task
gulp.task('dev', function() {
gulp.run('livereload');
gulp.watch('assets/less/**/*.less', ['styles']);
gulp.watch('assets/scripts/**/*.js', ['scripts']);
gulp.watch(['public/**/*.html'], ['html']);
gulp.watch(['public/**/*.png', 'public/**/*.gif', 'public/**/*.jpg', 'public/**/*.svg'], ['images']);
});

// build task
gulp.task('build', function() {
gulp.run('replace', 'imagemin', 'uglify');
});

For my web projects, I generally have two workflows. During dev, I watch JavaScript source files and concatenate them, watch less files and preprocess and minify them, and watch HTML and image files for changes, using livereload to refresh the browser as necessary. Then for build I minify images, uglify the JavaScript, and do some cache busting. To keep my HTML, JS, and CSS in sync, I load JS and CSS with a foo=XXXXXX argument. When I push a new build I replace the XXXXXX number with a random number, invalidating the browser’s cache. I was able to replicate all of this with Gulp in under an hour of Googling.

It’s a little faster and easier to configure than Grunt, but not so much so that you should drop whatever you’re doing and replace your Grunt workflow immediately. Still, for new projects or if you’re just getting into task runners, make Gulp your first stop.