Gulp is a very powerful tool that can be used to help build and deploy our JavaScript projects (amongst many other things). The benefit of using Gulp, or a similar tool such as Grunt, is that with a press of a button your project can run its tests, minify the JavaScript files, compile the SASS, compress images, and then open up a browser window with your project running for you to play with. And it does all this in a way which will just work on any computer, Mac or PC, whichever IDE you choose to develop with.
I recently decided to have a play around with Gulp and build my portfolio website using it. I had a bit of trouble finding a good list of all the tasks which I felt I needed to build and run my website, and so decided to list them here in one spot in case this helps anyone else.
I’ll go through them step by step in this post, but if you just want to skip to my gulpfile, you can view it on my github page.
GETTING STARTED
If you haven’t already, you will need to get Gulp up and running on your machine (if you have already done this go ahead and skip to the next section). To get Gulp running, you will need to install node, so as to have access to npm (node package manager) in the command line.
Go to the node website and click on the ‘INSTALL’ link, and run the file once it has downloaded. In the Custom Setup screen, make sure to select the ‘Add to PATH’ option so you will be able to use npm in the command line. If you have installed node and forgot to select this, it is probably easier to just uninstall node and start again, it doesn’t take much time. Once it is installed, you may need to restart your computer. You can test node has installed correctly by opening a command window and executing
npm -v
which should print out the current version of npm you are using.
Now you need to create a file called package.json and save it to your project’s root directory. This will hold the information about all the dependencies installed through node. Here is a sample file you can copy and change the values of:
{
"name": "Heather-Roberts-site",
"version": "1.3.0"
}
You can now install Gulp and save it to your package.json by opening a command window in your project’s root directory and running
npm install --global --save-dev gulp
You should see a new folder in your project called node_modules which contains a folder called gulp. Back in the command line, try running
gulp
It will probably give you an error along the lines of ‘Cannot locate gulpfile’ but it should recognise the command.
The global flag we used means you can use the gulp command from any location on your computer not just this directory where you’ve installed it. And save-dev adds Gulp into your package.json file with the version required by your project.
Now you need to create your gulpfile.js – put this with your package.json file in your project root, and for now just populate it with
var gulp = require("gulp");
gulp.task("default", function() {});
You should now be able to execute
gulp
from the command line and see it run without errors. However, nothing will happen as we are not actually telling it to do anything just yet. Now we need to start adding our tasks!
You should be able to find all of this information on the gulp site – if you are have any issues getting started, you should be able to get help there.
TASKS
JSHINT
gulp-jshint
A good thing to do before deploying your code is to check its quality, and make sure there aren’t any syntax errors, etc. JSHint can give our code a once over to make sure of its standard, and we can add a Gulp task for this which will output any issues in the command window.
So let’s write our very first task. First we need to install the JSHint package and save it to our project dependencies. So run
npm install gulp-jshint --save-dev
If you open package.json you should now see an entry for gulp-jshint with its corresponding version number. And now in the gulpfile we need to import the plugin.
var gulp = require("gulp");
var jshint = require("gulp-jshint");
gulp.task('default', function() {});
Node is using the require function here to go and load the gulp-jshint package that you should be able to see has been added into your node_modules directory.
Now we create our new lint task
var gulp = require("gulp");
var jshint = require("gulp-jshint");
gulp.task("lint", function() {
return gulp.src("js/**.js")
.pipe(jshint())
.pipe(jshint.reporter("default"));
});
gulp.task("default", function() {});
The value passed to gulp.src() is the file pattern to run the task on. Gulp lets you specifiy a String or an Array of explicitly names files ie. “js/app.js” or [“js/app.js”, “js/init.js”]. Or you can use what they call a glob which is what I have done here with the double stars in “js/**.js”. Here we want to look at any JS file in the js folder. There are several different options you can use, to see the full range look at the node-glob docs.
The pipe() function is just adding on the functionality you want to run on that selection of source files. So we are running jshint() and then adding a reporter for JSHint to use. The reporter is just the way the results are output, there are a few values you can use, check the JSHint docs if you aren’t satisfied with the default one I have selected.
Now if you run
gulp
in the command line, still nothing happens. That’s because we need to either add lint to the default task or explicitly call the lint task.
gulp lint
Now you should see something running, and maybe you will have some warnings or errors from JSHint, or maybe your code passes through error free.
To add lint to the default task, we just pass an Array into the default task containing it.
var gulp = require("gulp");
var jshint = require("gulp-jshint");
gulp.task("lint", function() {
return gulp.src("js/**.js")
.pipe(jshint())
.pipe(jshint.reporter("default"));
});
gulp.task("default", ["lint"]);
The default task is what is run if you just execute
gulp
in the command line. You can group tasks in Gulp, which is another of its strengths, and add several tasks to the default one. Or you can create a custom task group such as build or deploy, and then add this into the default task or run it explicitly from the command line.
CONCAT
gulp-concat
The next group of tasks will look at what we can do to our JavaScript files. A pretty standard thing to do is to concatenate all the project’s JS files into one file. To do this I will use a node package called gulp-concat.
So, first run the node install
npm install gulp-concat --save-dev
And import it into the gulpfile
var gulp = require("gulp");
var jshint = require("gulp-jshint");
var concat = require("gulp-concat");
And write a new task called scripts, which we’ll add to the default task after lint
...
gulp.task("scripts", function() {
return gulp.src("js/**.js")
.pipe(concat("app.js"));
});
gulp.task("default", ["lint", "scripts"]);
If you run
gulp
now, you should see all the script files are concatenated into a new file called app.js.
MINIFY
gulp-uglify
To minify the JavaScript I will use the gulp-uglify package.
npm install gulp-uglify --save-dev
Update the gulpfile like so
var gulp = require("gulp");
var jshint = require("gulp-jshint");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
...
gulp.task("scripts", function() {
return gulp.src("js/**.js")
.pipe(concat("app.js"))
.pipe(uglify());
});
...
This will now copy all scripts into a new file called app.js and then minify it.
On a side note – if you are using Angular JS and now try to load your project using the new app.js file, you will probably find your application throws some errors. This is because the JS files have been compressed too much for Angular to work. To fix this issue, update the uglify call like so
...
gulp.task("scripts", function() {
return gulp.src("js/**.js")
.pipe(concat("app.js"))
.pipe(uglify({mangle: false}));
});
...
And your application should now run error-free again.
RENAME
gulp-rename
Now our file has been minified, it would be nice to rename it to reflect this by appending the suffix .min
npm install gulp-rename --save-dev
var gulp = require("gulp");
var jshint = require("gulp-jshint");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var rename = require("gulp-rename");
...
gulp.task("scripts", function() {
return gulp.src("js/**.js")
.pipe(concat("app.js"))
.pipe(uglify())
.pipe(rename({suffix: ".min"}));
});
...
If you test running
gulp
you should see you now get a file called app.min.js which contains all your minified JavaScript files.
STRIP DEBUG STATEMENTS
gulp-strip-debug
Another helpful task is one that will remove any console.log() .warn() .error() calls and any alert() popups that you’ve put in to help you debug in development.
npm install gulp-strip-debug --save-dev
var gulp = require("gulp");
var jshint = require("gulp-jshint");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var rename = require("gulp-rename");
var stripDebug = require("gulp-strip-debug");
...
gulp.task("scripts", function() {
return gulp.src("js/**.js")
.pipe(stripDebug())
.pipe(concat("app.js"))
.pipe(uglify())
.pipe(rename({suffix: ".min"}));
});
...
I have called stripDebug() before the other script tasks as it makes sense to remove the debug statements, making the files smaller, before running the other processes. It is good to think about the order you do your tasks in, and what is the most efficient way.
COPY
It is a pretty standard practise to move all the files needed to run the project into their own directory. It is common to call this folder app or www or dist. I will call mine www, but whatever you like is fine.
To copy files over we do not need to install any new packages as this comes part and parcel with Gulp through the dest() function.
...
gulp.task("scripts", function() {
return gulp.src("js/**.js")
.pipe(stripDebug())
.pipe(concat("app.js"))
.pipe(uglify())
.pipe(rename({suffix: ".min"})
.pipe(gulp.dest("www/js"));
});
...
This will move app.min.js into a new directory called www/js. Gulp will handle creating this directory if it doesn’t exist already.
As you can see, we have grouped several different Gulp modules into one very powerful task which we can execute at the click of a button, saving us a huge amount of time.
MINIFY HTML
gulp-htmlmin
Now I’ll add a task to minify the HTML, which needs a different node package than the one we used to minify the JS files. We’ll write a new Gulp task which will handle minifying the HTML files and moving them to the new www directory.
npm install gulp-htmlmin --save-dev
var gulp = require("gulp");
var jshint = require("gulp-jshint");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var rename = require("gulp-rename");
var stripDebug = require("gulp-strip-debug");
var htmlmin = require("gulp-htmlmin");
...
gulp.task("html", function() {
return gulp.src("index.html")
.pipe(htmlmin({collapseWhitespace: true}))
.pipe(gulp.dest("www"));
});
...
And remember to add it to the default task
...
gulp.task("default", ["lint", "scripts", "html"]);
Now execute
gulp
And you should see index.html turn up in the www directory, minified. Make sure to update the file pattern here to target all your HTML files, I have only done index.html for ease of demonstrating.
SASS
gulp-sass
The next thing we will look at is compiling SASS. This will not apply to everyone, some people use LESS, or no preprocessor at all. If SASS isn’t you, you might want to skip to the next section.
So we need a task that will compile the SASS into CSS, compress the CSS, rename the file with the .min suffix and move it into our new www folder. The only new package we need to install is one called gulp-sass which will handle the SASS compilation and compressing the CSS.
npm install gulp-sass --save-dev
var gulp = require("gulp");
var jshint = require("gulp-jshint");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var rename = require("gulp-rename");
var stripDebug = require("gulp-strip-debug");
var htmlmin = require("gulp-htmlmin");
var sass = require("gulp-sass");
...
gulp.task("sass", function() {
return gulp.src("css/app.scss")
.pipe(sass({outputStyle: "compressed"}))
.pipe(rename({suffix: ".min"})
.pipe(gulp.dest("www/css"));
});
...
And again, update the default task
...
gulp.task("default", ["lint", "scripts", "html", "sass"]);
I initially started using a different sass module called gulp-ruby-sass as I read another tutorial recommending it as it has more features than gulp-sass, but I ran into issues with my Angular work again – gulp-ruby-sass was trying to be clever and strip away the CSS that wasn’t being referenced in any HTML, but it wasn’t taking into account directive templates, so any classes in my custom directive templates were not being applied and my site looked broken. I had to switch to the gulp-sass task as I couldn’t see any way to switch off the CSS stripping. Just a heads up in case you come across a similar issue.
NEWER
gulp-newer
Now I’m going to look at copying asset files across to the new www directory. Often you can have a lot of assets and you don’t want to re-copy them across each time, so you can use gulp-newer to only copy those which are newer or aren’t in the destination directory.
npm install gulp-newer --save-dev
var gulp = require("gulp");
var jshint = require("gulp-jshint");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var rename = require("gulp-rename");
var stripDebug = require("gulp-strip-debug");
var htmlmin = require("gulp-htmlmin");
var sass = require("gulp-sass");
var newer = require("gulp-newer");
I want to copy image assets and font assets over, so to start with I’ll write 2 tasks to do so, and group them under one copy-assets task to add into the default task.
...
gulp.task("copy-imgs", function() {
return gulp.src("assets/imgs/**")
.pipe(newer("www/assets/imgs"))
.pipe(gulp.dest("www/assets/imgs"));
});
gulp.task("copy-fonts", function() {
return gulp.src("assets/fonts/**")
.pipe(newer("www/assets/fonts"))
.pipe(gulp.dest("www/assets/fonts"));
});
gulp.task("copy-assets", ["copy-imgs", "copy-fonts"]);
gulp.task("default", ["lint", "scripts", "html", "sass", "copy-assets"]);
This works fine, however it is obviously not great to write a new task that is doing pretty much exactly the same thing just to a different folder.
So I’ll rewrite this into one task that targets both directories.
...
gulp.task("copy-assets", function() {
return gulp.src(["assets/imgs/**", "assets/fonts/**"])
.pipe(newer("www/assets"))
.pipe(gulp.dest("www/assets"));
});
gulp.task("default", ["lint", "scripts", "html", "sass", "copy-assets"]);
Written like this though, it will just put all our files directly into the www/assets folder, we will lose the img and fonts folders. To preserve the original folder structure we can use the base option of gulp.src(). This option defines the base folder, and will mean your files are copied through in a way that preserves their folder structure.
...
gulp.task("copy-assets", function() {
return gulp.src(["assets/imgs/**", "assets/fonts/**"], {base: "assets"})
.pipe(newer("www/assets"))
.pipe(gulp.dest("www/assets"));
});
gulp.task("default", ["lint", "scripts", "html", "sass", "copy-assets"]);
WATCH
A very cool feature of Gulp is that it can watch the files you are working on and re-execute a task whenever something changes. So, for example, you can update one of your SASS files and Gulp will automatically re-compile and minify the CSS and copy it into the www directory without you having to do a thing. Watch is something inbuilt into the gulp module like copying files, so you don’t need to import anything new. Let’s add a new task called watch to run the sass task in the aforementioned circumstance.
...
gulp.task("watch", function() {
gulp.watch("css/**.scss", ["sass"]);
});
...
The first parameter is just like the src() function, where you specify the file pattern to match using a String, a glob or an Array. If you now run
gulp watch
in the command line, and then go and change one of your SASS files, you should see the sass task gets executed again.
Now let’s update the watch task to run all the tasks written so far when their corresponding files change, and put it in the default task.
...
gulp.task("watch", function() {
gulp.watch("css/**.scss", ["sass"]);
gulp.watch("js/**.js", ["lint", "scripts"]);
gulp.watch("index.html", ["html"]);
gulp.watch(["assets/imgs/**", "assets/fonts/**"], ["copy-assets"]);
});
gulp.task("default", ["lint", "scripts", "html", "sass", "copy-assets", "watch"]);
CONNECT
gulp-connect-multi
The final task I will show you will load the project in a browser so you can actually view it after all the wonderful things you have done to it. This also has a live reload option, where you can set it up so that when you make a change to one of your files and its watch task runs and compiles/moves something etc. the browser will detect something has changed and reload the content.
npm install gulp-multi-connect --save-dev
When importing this into your gulpfile, it has a slightly different syntax in that you assign the execution to your variable by putting parentheses at the end
var gulp = require("gulp");
var jshint = require("gulp-jshint");
var concat = require("gulp-concat");
var uglify = require("gulp-uglify");
var rename = require("gulp-rename");
var stripDebug = require("gulp-strip-debug");
var htmlmin = require("gulp-htmlmin");
var newer = require("gulp-newer");
var connect = require("gulp-multi-connect")();
...
gulp.task("connect", connect.server({
root: ["www"],
port: 1337,
livereload: true,
open: {
browser: "chrome" // if not working OS X browser: "Google Chrome"
}
}));
...
Make sure to set the value of root to your destination directory. Now try running
gulp connect
and you should see your project load in a new tab in Google Chrome. If it isn’t loading, maybe have a look at the settings of gulp-connect-multi and specifiy a different browser. Failing that you can just type http://localhost:1337 into your browser.
To get the live reload functionality working you will need to update the previously written tasks by adding .pipe(connect()); at the end. For example here is my sass task updated:
...
gulp.task("sass", function() {
return gulp.src("css/app.scss")
.pipe(sass({outputStyle: "compressed"}))
.pipe(rename({suffix: ".min"}))
.pipe(gulp.dest("www/css"))
.pipe(connect.reload());
});
...
You might not want to always run the connect task in with everything else, so I’ll leave it up to you if you want to put it in the default task.
TO FINISH
So this is pretty much everything I have used on my own site, but every project is different and I have likely missed out something you need.
Some other tasks which you might be interested in are:
- gulp-clean which you can use to empty out your destination directory
- karma which you can use to execute your tests
- gulp-imagemin which will compress any images
There are pretty much Gulp tasks available for everything you can think of, and you can easily extend the gulpfile I’ve built here to suit you and your projects.
When structuring your gulpfile, it is a good idea to think about the automation process a bit. It is more helpful to you for the tasks which are more likely to fail to run first, so you don’t have to sit through all the other tasks running only to have the whole thing fail at the final step. So, for example, it is a good idea to run any tests as one of the first steps.
So, that’s it! Have fun with Gulp 🙂
If you want to look at my whole gulpfile.js and the project it is running for, you can view it on my github page