Simple Gulp Configuration for Angular Applications

Gulp is a great build tool for web applications. In this article I am sharing some gulp scripts that I found really useful.

Installing Gulp

Node.js needs to be installed prior to installing gulp. Once Node.js is ready on your machine, run this command to install gulp:

sudo npm install --global gulp-cli

This will install the gulp command line tools globally, such that the gulp command can be invoked throughout the computer.

Create a Gulp Project

Change directory to the project’s root.

npm init

First of all, initialize npm, set the project name, version, author, etc. After the setup, package.json will be created.

npm install gulp --save-dev

Install gulp for the project. --save-dev make sure that gulp is logged in package.json as a development dependency. This is particularly useful when we need to port the project to another computer. With all the dependencies logged in package.json, we just run npm install to get them back, instead of install each of them again, or copy the gaint node_modules folder around.

Project Structure

For an Angular.js application with gulp as the build tool, I usually setup the project directory like this:

|- project/                
  |- bower_components/
  |- dist/
  |- node_modules/    
  |- src/
    |- css/              # vendor css
    |- less/             # my less source code
    |- img/
    |- js/               # my javascript source code
    |- vendor/           # vendor js
    |- views/            # angular templates
    |- index.html
  |- bower.json
  |- gulpfile.js
  |- package.json

A simple gulpfile.js

Here comes the fun part. A basic gulpfile looks like this:

// require gulp dependencies
var gulp = require('gulp');

// declare a gulp task
gulp.task('task_name', function() {
  // task pipelines
});

The tasks can be run in command line like:

gulp task_name

Define Paths

Define a path object to keep track of all the source code in the project, making it easier for reference in various gulp tasks. For one of the angularjs application, I had the following paths:

var path = {
  // template markups
  HTML: [
    'src/*.html',
    'src/views/**/*.html',
    'src/views/*.html'
  ],
  // my js source code
  JS: [
    'src/js/**/*.js'
    'src/js/*.js',
  ],
  // all less files (for changes monitoring purpose)
  LESS_ALL: [
    'src/less/*.less'
  ],
  // main less file (others are imported in style.less)
  LESS: [
    'src/less/style.less'
  ],
  // images
  IMG: [
    'src/img/**'
  ],
  // vendor css
  CSS: [
    'src/css/*.css'
  ],
  // vendor js
  VENDOR: [
    'bower_components/angular/angular.js',
    'bower_components/angular-animate/angular-aria.js',
    // ...and more
  ],
  DIST: [
    './dist'
  ]
};

Installing Gulp Plugins

There are tons of useful gulp plugins to explore. Gulp plugins can be installed by

npm install <plugin-name> --save-dev

For example, to install gulp-connect:

npm install gulp-connect --save-dev

Useful Gulp Tasks

Here I only introduce some gulp tasks that I found helpful in angularjs development.

Spin up a erver for dist.

var connect = require('gulp-connect');

gulp.task('connect', function() {
  connect.server({
    root: 'dist',
    port: 4000
  });
});

Clean up distribution directory.

var connect = require('gulp-clean');

gulp.task('clean', function() {
  return gulp.src('./dist/*', {force: true})
    .pipe(clean());
});

Use JSLint.

var jshint = require('gulp-jshint');

gulp.task('lint', function() {
  return gulp.src(path.JS)
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(jshint.reporter('fail'));
});

Copy over CSS and HTML

gulp.task('css', function () {
  gulp.src(path.CSS)
    .pipe(gulp.dest(path.DIST + '/css'));
});

gulp.task('html', function () {
  gulp.src(path.HTML, {base: 'src'})
    .pipe(gulp.dest(path.DIST));
});

Compile, minify and copy Less.

var less = require('gulp-less');
var lessDependents = require('gulp-less-dependents');
var minifyCSS = require('gulp-minify-css');

gulp.task('less', function () {
  gulp.src(path.LESS)
  	.pipe(lessDependents())
    .pipe(less())
    .pipe(minifyCSS())
    .pipe(gulp.dest(path.DIST + '/css'));
});

Merge, uglify and copy js files.

var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var ngAnnotate = require('gulp-ng-annotate');
var sourcemaps = require('gulp-sourcemaps');

gulp.task('js', function () {
  gulp.src(path.JS)
  	.pipe(sourcemaps.init())
		  .pipe(concat('app.js'))
		  .pipe(ngAnnotate())
		  .pipe(uglify())
		.pipe(sourcemaps.write())
    .pipe(gulp.dest(path.DIST + '/js'));
});

gulp.task('vendor', function () {
	gulp.src(path.VENDOR)
		.pipe(concat('vendor.js'))
		.pipe(ngAnnotate())
		.pipe(uglify())
    .pipe(gulp.dest(path.DIST + '/js'));
});

Compress images.

var imagemin = require('gulp-imagemin');

gulp.task('img', function(){
  gulp.src(path.IMG)
    .pipe(imagemin())
    .pipe(gulp.dest(path.DIST + '/img'));
});

Watch changes and automate tasks.

var watch = require('gulp-watch');

gulp.task('watch', function () {
  gulp.watch(path.LESS_ALL, ['less']);
  gulp.watch(path.VENDOR, ['vendor']);
  gulp.watch(path.JS, ['lint', 'js']);
  gulp.watch(path.HTML, ['html']);
  gulp.watch(path.IMG, ['img']);
});

Default task.

var all_tasks = ['lint', 'css', 'less', 'vendor', 'js', 'html', 'img'];

gulp.task('default', all_tasks);

A More Complete gulpfile.js

/* gulp dependencies */
var gulp = require('gulp');
var less = require('gulp-less');
var watch = require('gulp-watch');
var imagemin = require('gulp-imagemin');
var connect = require('gulp-connect');
var concat = require('gulp-concat');
var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify');
var ngAnnotate = require('gulp-ng-annotate');
var minifyCSS = require('gulp-minify-css');
var lessDependents = require('gulp-less-dependents');
var clean = require('gulp-clean');
var bower = require('gulp-bower');
var concat_vendor = require('gulp-concat-vendor');
var jshint = require('gulp-jshint');

/* path def */
var path = {
  HTML: [
  	'src/.htaccess', 
  	'src/*.html', 
  	'src/views/**/*.html', 
  	'src/views/*.html', 
  	'src/favicon.png'
  ],
  JS: [
  	'src/js/*.js', 
  	'src/js/**/*.js'
  ],
  CSS: [
	  'src/css/*.css'
  ],
  LESS: [
  	'src/less/style.less'
  ],
  LESS_ALL: [
  	'src/less/*.less'
  ], 
  IMG: [
  	'src/img/**'
  ],
  VENDOR: [
  	'bower_components/angular/angular.js', 
		'bower_components/angular-animate/angular-animate.js', 
		'bower_components/angular-aria/angular-aria.js', 
		'bower_components/angular-messages/angular-messages.js',
		'bower_components/angular-sanitize/angular-sanitize.js',  
		'bower_components/angular-ui-router/release/angular-ui-router.js'
    // ...and more
	],
  DIST: './dist'
};

/* spin up distribution server */
gulp.task('connect', function() {
	connect.server({
		root: 'dist',
		port: 4005
	});
});

/* clean up dist dir */
gulp.task('clean', function() {
	return gulp.src('./dist/*', {force: true})
		.pipe(clean());
});

/* jslint for debugging */
gulp.task('lint', function() {
  return gulp.src(path.JS)
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(jshint.reporter('fail'));
});

/* move css */
gulp.task('css', function () {
  gulp.src(path.CSS)
    .pipe(gulp.dest(path.DIST + '/css'));
});

/* compile less */
gulp.task('less', function () {
  gulp.src(path.LESS)
  	.pipe(lessDependents())
    .pipe(less())
    .pipe(minifyCSS())
    .pipe(gulp.dest(path.DIST + '/css'));
});

/* concat and compress app scripts */
gulp.task('js', function () {
  gulp.src(path.JS)
  	.pipe(sourcemaps.init())
		  .pipe(concat('app.js'))
		  .pipe(ngAnnotate())
		  .pipe(uglify())
		.pipe(sourcemaps.write())
    .pipe(gulp.dest(path.DIST + '/js'));
});

/* concat vendor dependencies */
gulp.task('vendor', function () {
	gulp.src(path.VENDOR)
		.pipe(concat('vendor.js'))
		.pipe(ngAnnotate())
		.pipe(uglify())
    .pipe(gulp.dest(path.DIST + '/js'));
});

/* copy over markups */
gulp.task('html', function(){
  gulp.src(path.HTML, {base: 'src'})
    .pipe(gulp.dest(path.DIST));
});

/* compress images */
gulp.task('img', function(){
  gulp.src(path.IMG)
    .pipe(imagemin())
    .pipe(gulp.dest(path.DIST + '/img'));
});

/* watch all changes */
gulp.task('watch', function () {
  gulp.watch(path.LESS_ALL, ['less']);
  gulp.watch(path.VENDOR, ['vendor']);
  gulp.watch(path.JS, ['lint', 'js']);
  gulp.watch(path.HTML, ['html']);
  gulp.watch(path.IMG, ['img']);
});

/* defualt */
gulp.task('default', all_tasks);