Armen Vardanyan

Angular developer from Yerevan, Armenia

Building Angular development environment with Gulp

Setting up a comfortable dev environment for an Angular application may be sometimes a complicated task. Many issues should be considered, mainly:

  • Copy HTML files
  • Concatenate, annotate and minimize .js files
  • Minimize and copy CSS files
  • Copy images and fonts
  • Inject dependencies from a package manager (particularly bower)

Fortunately, gulp.js solves all of the above mentioned tasks and more. I won’t be covering all the capabilities a developer can achieve with gulp and it’s powerful plugins, but anyone with no previous gulp background can use this article to set up his first development environment.

So,with no further delay, let’s get started!

First steps

Now, let us create a new project folder and define our project’s architecture. I usually use to go in for folders for scripts(with subfolders for angular directives, services and controllers), stylesheets, views (HTML files, maybe divided into relevant folders for each application feature — depends on the scale of a project), images and fonts, with an index.html file at the document root. Basically, the structure looks like this:

Folder tree structure for a simple angular app

Here we are. From this point we need to start understanding how gulp works. Go on and install it with npm.

$ npm install gulp

Create a gulpfile.js file in the document root of your application. Let’s start with a basic task — copy all html files from ‘views’ folder to our ‘dist’ folder (which is going to be the root folder of our production-ready (well, harshly said) application version), while preserving the file/folder structure.

var gulp = require('gulp'); // import the gulp module itself
gulp.task('copy-html-files', function () {
var stream = gulp.src('./app/views/**/*.html') // stream source
.pipe(gulp.dest('./dist/views/')); // copy to dist/views
return stream;
});

Then you just run

$ gulp copy-html-files

in your terminal.

What gulp does here? It just takes all .html files from the app/views folder and copy-pastes them to the dist directory, while creating folders with similar structure as it was before.

Let’s now dive into some more complicated tasks.

Introducing gulp-useref

Say we want to take all our .css files, minify and concatenate them into a single file. We are going to use gulp-useref for concatenation and sourcing, gulp-minify-css for minification and gulp-if for conditional statements. minify-css is pretty simple, you just call it on a stream with pipe. gulp-if is not hard too: it just takes a string argument as a condition (to filter the files that meet this particular condition) and executes a task only if the condition is true. gulp-useref, on the contrary, has a pretty convenient and (to be honest) awesome usage: it allows to inject dependent files into your html files and tell useref what to take into a stream for concatenation. Let’s take a look at a sample from index.html file at our root directory:

<!-- build:css styles/main.css -->
<link rel="stylesheet" href="styles/custom-css-file.css">
<link rel="stylesheet" href="styles/some-plugin-css-file.css">
<!-- endbuild -->

The comments that begin and end this section just tell gulp-useref that the linked files are going to be taken into one stream, concatenated and copy-pasted to dist/styles/main.css file. Here’s the gulpfile.js code for this action:

var useref = require('gulp-useref');
var minifyCss = require('gulp-minify-css');
var gulpif = require('gulp-if');
gulp.task('css-files', function () {
var stream = gulp.src('./app/index.html')
.pipe(useref()) //take a streem from index.html comment
.pipe(gulpif('*.css', minifyCss())) // if .css file, minify
.pipe(gulpif('*.css', gulp.dest('./dist'))); // copy to dist
return stream;
});

This will create a main.css file inside dist/styles, including a minified version of all your css files between the comments tag.

Not only css: injecting dependencies from a package manager

You can use useref not only for managing custom files, but injecting dependencies from a package manager (npm, bower…). I personally prefer bower and will cover an example on it here.

Let’s assume we are going to need angular and jQuery for our project. Go on and install it with bower:

$ bower install angular
$ bower install jquery

bower will create a bower_components folder inside your document root. Now we are going to use wiredep to inject these dependencies into our index.html file and concatenate/uglify them into a single vendor.js file. We will use ngAnnotate and uglify.

$ npm install gulp-uglify
$ npm install wiredep
$npm install gulp-ng-annotate

Now, in our index.html file:

<!-- build:js scripts/vendor.js --> // concatenate to vendor.js
<!-- bower:js --> // tells wiredep to inject all bower dependencies

<!-- endbower -->
<!-- endbuild -->

And create a task for bower files:

var wiredep = require('wiredep').stream;
var uglify = require('gulp-uglify');
var ngAnnotate = require('gulp-ng-annotate');
gulp.task('bower-files', function () {
var stream = gulp.src('./app/index.html')
.pipe(wiredep({
directory: 'bower_components' //inject dependencies
}))
.pipe(useref())
.pipe(gulpif('*.js', ngAnnotate())) // ng-annotate if .js
.pipe(gulpif('*.js', uglify())) // uglify if .js
.pipe(gulpif('*.js', gulp.dest('./dist'))); // paste to dist
return stream;
});

And run

$ gulp bower-files

So, that’s all! Our bower dependencies are successfully included into a single vendor.js file and compressed.

Too many tasks

‘So, when I am going to deploy my project, I have to run all this tasks one by one?’ — may be a beginner’s obvious question. Of course you don’t. You will just use gulp-run-sequence. It allows you to run several tasks on a single command from terminal.

$ npm install gulp-run-sequence

Implement a sequence in your gulpfile.js:

var runSequence = require('run-sequence');
/* some other plugins go here */
/* define our tasks here */
gulp.task('build', function (callback) {
runSequence(
'css-files',
'bower-files',
'copy-html-files',
/* other tasks maybe */
callback);
});

Now run

$ gulp build

This will execute the tasks passed to runSequence as strings.

Note an important thing: run-sequence executes tasks asynchronously, meaning that they will start (almost) at the same time and be executed in parallel. Thus, if you have tasks that may depend on other tasks to be completed, they may cause unexpected behavior when being run in a sequence. To avoid this, you should return the stream from the task function. Just like this:

gulp.task('some-task', function(){
var stream = gulp.src('some-source') // save the stream
/* do some stuff here */
return stream; // return the stream so task can be run
}); // in queue

This will help you avoid any unpleasant issues with run-sequence.

Remove them all!

Sometimes you will probably need to remove the contents of your dist folder. In your normal development lifecycle you’ll have to do that any time you make a change — just before you start copying your files again! (see next section)

gulp-clean is what you’re after.

$ npm install gulp-clean

Now define a simple task:

var clean = require('gulp-clean');
gulp.task('clean', function () {
var stream = gulp.src('./dist', {read: false})
.pipe(clean());
return stream
});

This will safely remove everything from your dist folder.

From the official documentation:

Option read:false prevents gulp from reading the contents of the file and makes this task a lot faster. If you need the file and its contents after cleaning in the same stream, do not set the read option to false.

Automation?

‘So I have to run gulp tasks every time I make a change in my project files?’, may be another obvious beginner question. And no, you have not to. Actually, you SHOULD NOT. gulp-watch solves the problem. Go on and install it:

$ npm install gulp-watch

Setting up the task is as easy as anything else in gulp:

gulp.task('watch', function() {
gulp.watch('./app/**/**/*.*', function () {
runSequence('task1', 'task2', ...);
});
});

This will re-execute any commands you define in the run-sequence every time you make changes in your files.

This is an example of how dist folder may look after calling gulp. As you can see, the folder structure is preserved, but the .css and .js files have been concatenated.

A look on the document-folder tree after gulp

Some tips

You probably would be tempted to create tasks for every deployment feature, run them in sequence and put a watcher. But beware! Concatenating and minifying a lot of files every time a change is made is time-consuming. On a large-scale project this may sometimes take from 10 to 15 seconds, and will really slow down your development. For this I prefer to create some kind of ‘mini’ version of my tasks (just basic copy pasting and/or may be concatenation and dependency injection) with a gulp-watch, and a production version, which is slow, minifies just everything and does not turn on a watcher. This will allow you to both avoid slowing down your work and to have capability of creating a cool, tiny, production-ready version of your development environment.

Summary

Never do any kind of work with your bare hands, if it can be automated. gulp.js is a great tool for that, extremely widely used, easy and convenient. Feel interested? Here is a link to my own version of gulpfile, which I use every day, on github:

Topics of interest

More Related Stories