Lately I’ve switched to Gulp from Grunt in my various pet projects. Why you ask? Because it’s simpler, faster and focus on code over configuration. I like code! In this post I will give a brief introduction to Gulp and share a simple workflow which is a great start when beginning to use Gulp.
Quick intro to Gulp
Gulp, same as Grunt, is a task runner for javascript that uses Node.js to run. The reason to use a task runner is to automate time consuming but important tasks in your daily work, making you a more productive developer. In Gulp you write all tasks in… wait for it… javascript! Being written in code it gives you more flexibility than configuration done with javascript objects. It uses Node.js streams which simplified means you:
- have an input (some files, maybe javascript or css)
- which you perform an action on
- and finally send to some output
Think of it as a pipeline where at certain points along the pipe actions are performed, e.g. linting, concatenating, transforming, minifying, testing etc.
To concretize even more, let’s look at the simple api that Gulp exposes:
- gulp.task: the task/action to perform
- gulp.src: the input, which files
- gulp.dest: the output
- gulp.watch: detect changes and run tasks automatically
Could it be more easy? 🙂
Simple workflow in Gulp
Let’s walk through the code that is necessary for setting up a nice, easy workflow that you can use as a starting point in your javascript projects. The plan is to have one ”build” task that does linting, concatenating and minifying of our javascript files, one ”test” task that will run our tests and finally one ”watch” task that will detect file changes and automatically reload the browser.
Install
I assume you are familiar with Node.js and has its package manager npm installed. Let’s create a node project and a package.json:
npm init
Install Gulp:
npm install gulp -g
Install the Gulp plugins needed for our workflow:
npm install gulp-jshint gulp-mocha gulp-concat gulp-uglify gulp-livereload --save-dev
Create gulpfile.js (our Gulp.js configuration file) and require the plugins:
var gulp = require('gulp'),
jshint = require('gulp-jshint'),
mocha = require('gulp-mocha'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
livereload = require('gulp-livereload');
Linting with gulp-jshint
JSHint is ”a tool that helps to detect errors and potential problems in your JavaScript code”. Let’s use it as first step in the build task, add the following code to gulpfile.js:
gulp.task('build', function() {
return gulp.src('src/scripts/**/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
What happened here? A task called ”build” was defined and a function that is the content of the task was provided. The function Gulp src api is used to select the input (our javascript files) before using pipe to stream to the next step which is using JSHint to analyze our code and finally handing over to the next step in the pipe that is reporting the result. The following task can be run from command line:
gulp build
Concatenation with gulp-concat
This plugin will concatenate multiple javascript files into one bundled file, which then is the only file needed to be referenced from our HTML page. Lets add it to our existing task:
gulp.task('build', function() {
return gulp.src('src/scripts/**/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(concat('bundle.min.js'))
.pipe(gulp.dest('public/js'));
});
As you can see we just add this to the chain of pipes, meaning it will take the output of the previous action and continue to work with it, combining the multiple source files into one new file called bundle.min.js. It will place it in a public/js folder using Gulp dest api.
Minifying with gulp-uglify
This plugin will take the javascript file(s) and ”parse, compress and beautify”. We want to make our final single javascript file as small as possible to reduce load time. Let’s add it:
gulp.task('build', function() {
return gulp.src('src/scripts/**/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(concat('bundle.min.js'))
.pipe(gulp.dest('public/js'))
.pipe(uglify())
.pipe(gulp.dest('public/js'));
});
That concludes our build task and we now have a problem free (eeeh, hopefully), minified, single javascript file the can be referenced from our HTML page.
Testing with gulp-mocha
Mocha is ”a javaccript test framework running on Node.js, featuring browser support, asynchronous testing, test coverage reports, and use of any assertion library. It’s pretty awesome so let’s add a task for it:
gulp.task('test', function() {
return gulp .src('test/*.js')
.pipe(mocha());
});
This will take all the test javascript files and hand it over to mocha who will run all tests.
Automatic reload with gulp-livereload
This plugin ”monitors changes in your file system (for example, your CSS, images or javascripts). As soon as a change is detected the browser is refreshed automatically”. (To make it work you need to install the plugin (here’s for Chrome) or include a script snippet.) Lets create the ”watch” task using gulp-livereload and Gulp api watch:
gulp.task('watch', function() {
gulp.watch('src/scripts/**/*.js', ['build']);
livereload.listen();
gulp.watch(['public/**']).on('change', livereload.changed);
});
By using Gulp watch api we detect changes in our javascript files and run the ”build” task. Then livereload server i started and will listen to changes. Finally, when a new bundle.js is created and placed in public folder livereload will detect that change and reload the browser for us. This means that as soon as you save a javascript source file the changes will be reflected in the browser.
Default task to do it all
Finally, lets create a default task that will both build and start watching our source files:
gulp.task('default', ['watch', 'test', 'build']);
This ”default” task have three dependencies that it will run, which happens to be our newly created ”test”, ”watch” and ”build” tasks. So now all you need to do is run this from command line:
gulp
You can of course run these tasks individually:
gulp test
gulp build
gulp watch
Here’s the final code in gulpfile.js
var gulp = require('gulp'),
jshint = require('gulp-jshint'),
mocha = require('gulp-mocha'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
livereload = require('gulp-livereload');
gulp.task('build', function() {
return gulp.src('src/scripts/**/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(concat('bundle.min.js'))
.pipe(gulp.dest('public/js'))
.pipe(uglify())
.pipe(gulp.dest('public/js'));
});
gulp.task('test', function() {
return gulp .src('test/*.js')
.pipe(mocha());
});
gulp.task('watch', function() {
gulp.watch('src/scripts/**/*.js', ['build']);
livereload.listen();
gulp.watch(['public/**']).on('change', livereload.changed);
});
gulp.task('default', ['watch', 'test', 'build']);
Final thoughs
Aren’t there any downsides to this? Well, both Gulp and Grunt is very plugin and community driven… Otherwise I think it’s mostly upsides and it saves A LOT of time.
Perfect, thanks, so many of these builds are overcomplicated!