Setting up a comfortable dev environment for an Angular application may be sometimes a complicated task. Many issues should be considered, mainly:
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!
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/viewsreturn 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.
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 distreturn stream;});
This will create a main.css file inside dist/styles, including a minified version of all your css files between the comments tag.
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 distreturn stream;});
And run
$ gulp bower-files
So, that’s all! Our bower dependencies are successfully included into a single vendor.js file and compressed.
‘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.
‘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
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: