Barbarian Meets Coding
barbarianmeetscoding

WebDev, UX & a Pinch of Fantasy

28 minutes read

Gulp Wiki

This article is part of my personal wiki where I write personal notes while I am learning new technologies. You are welcome to use it for your own learning!

Gulp.js is a javascript task automation library that helps you automate your web development workflow such as minification, bundling, adding vendor prefixes, CSS preprocessor compilation, testing, etc and make you more productive and efficient.

How Does it Work?

Gulp works like a pipeline of tasks, it reads source code files as a stream, submits them to different tasks that can either be non-destructive processes (linting, etc) or transformations (minification, bundling, etc) and generates the output of these processes as new files wherever you want.

You define these tasks in the gulp configuration file, usually gulpfile.js and execute them using the gulp CLI (command-line-interface), f.i. gulp test.

Gulp’s API

Gulp provides a minimal API which consists on the following elements:

  • gulp.task lets you define arbitrary tasks for gulp to perform
  • gulp.src lets you define which files a task is going to convert in a stream and operate over
  • gulp.dest lets you define where the output files are going to be placed
  • gulp.watch lets you define files to watch so that when these task change we can execute arbitrary tasks of our choice

Gulp tasks

gulp.task lets you define arbitrary tasks for gulp to perform. It consist of:

  • a name for the task
  • dependencies that are executed in parallel before the task is executed
  • a function that represents the task itself

// gulp.task(name[, dep], fn)
// name -> task name
// dep -> dependencies
// fn -> function that defines the task itself
gulp.task('test', ['jshint'], function(){
   // imperative description of the task itself
})

For instance:


gulp.task('build-templates', function(){
   gulp.src('client/templates/*.jade')
  .pipe(jade())
  .pipe(minify())
  .pipe(gulp.dest('build/minified_templates'));
})

Gulp src

gulp.src lets you define which files a task is going to convert in a stream and operate over. It consists of:

  • a glob to select all files that match it
  • a set of options:
    • see all options on docs
    • options.base defines the path to retain when place the source files in the destination. By default everything before the glob starts but you can redefine it to be what you want.
// Example from docs
gulp.src('client/js/**/*.js') // Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/`
  .pipe(minify())
  .pipe(gulp.dest('build'));  // Writes 'build/somedir/somefile.js'

gulp.src('client/js/**/*.js', { base: 'client' })
  .pipe(minify())
  .pipe(gulp.dest('build'));  // Writes 'build/js/somedir/somefile.js'

Gulp dest

gulp.dest lets you define where the output files are going to be placed. It consists of:


// example from the docs
gulp.src('./client/templates/*.jade')
  .pipe(jade())
  .pipe(gulp.dest('./build/templates'))
  .pipe(minify())
  .pipe(gulp.dest('./build/minified_templates'));

Gulp watch

gulp.watch lets you define files to watch so that when these task change we can execute arbitrary tasks of our choice. It consists of:


// example from the docs
var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
watcher.on('change', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

You can also specify a callback like this:


// from the docs
gulp.watch('js/**/*.js', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

Adding Gulp to Your Project

Before you add gulp to your project you’ll need to install some dependencies: git and node (use homebrew in OSX or Chocolatey in Windows). Also make sure that you have the latest version of npm, the node package manager which should have been installed with node.

Before you start using gulp you’ll need to install some global packages:


// -g stands for --global
// gulp is the javascript task automation library
// bower is the front-end javascript library manager
PS> npm install -g gulp bower

// you can also list all packages installer globally by using
PS> npm list -g --depth=0

This will install the gulp and bower command line interfaces (CLI).

You can add gulp to any specific project by using:


// install gulp and save it as a development dependency
PS> npm install gulp --save-dev
// this will generate a package.json with the installed dependencies
// dev dependencies are used only during development
// normal dependencies are used when the app is in production (they are
// libraries related to the running of the application itself
// like angular, knockout, express, etc)

The next step is to create a gulpfile.js file which will contain all of our gulp task configurations. For instance:


var gulp = require('gulp');

gulp.task('hello-world', function(){
    console.log('hello world!');
});

Having defined a task we can execute it using the gulp CLI:


PS> gulp hello-world
// => using gulfile.js
// => starting hello-world task
// => hello world!
// => finished hello-world task

Performing Tasks with Gulp

Adding Lintings Tasks

To add linting tasks you can use jshint (static analysis) and jscs (code style checking). Install the plugins with npm as usual:

PS> npm install gulp-jshint gulp-jscs jshint-stylish gulp-print --save-dev

And create the appropriate task in your gulpfile.js:

var gulp = require('gulp'),
    gulpprint = require('gulp-print'),
    jshint = require('gulp-jshint'),
    jscs = require('gulp-jscs');

gulp.task('lint', function(){
  return gulp.src([
            /*source files*/ './src/**/*.js',
            /* js config files */'./*.js'])
         .pipe(gulpprint()) // print files to be processed
         .pipe(jscs())
         .pipe(jshint())
         .pipe(jshint.reporter('jshint-stylish', { verbose: true }));
         // jshint command line reporter
})

Now you can lint your code with gulp lint.

Adding Custom Utility Functions

You can add custom utility functions by using the gulp-util package:

PS> npm install gulp-util --save-dev

var gulp = require('gulp'),
    gulpprint = require('gulp-print'),
    jshint = require('gulp-jshint'),
    jscs = require('gulp-jscs'),
    util = require('gulp-util');

gulp.task('lint', function(){
  // log message in blue color
  util.log(util.colors.blue(msg)) ;
  // perform sub-tasks
  return gulp.src([
            /*source files*/ './src/**/*.js',
            /* js config files */'./*.js'])
         .pipe(gulpprint()) // print files to be processed
         .pipe(jscs())
         .pipe(jshint())
         .pipe(jshint.reporter('jshint-stylish', { verbose: true })) // jshint command line reporter
         .pipe(jshint.reporter('fail'));  // handle failure of linting with jshint
});

Adding Conditional Functions

You can also add the gulp-if package to handle flags to enable/disable certain subtasks within a given task. For instance, if we only want to show the files being processed when we use the verbose flag we can install the package via npm:

PS> npm install gulp-if yargs --save-dev

and write:


var gulp = require('gulp'),
    gulpprint = require('gulp-print'),
    gulpif = require('gulp-if')
    jshint = require('gulp-jshint'),
    jscs = require('gulp-jscs'),
    util = require('gulp-util')
    args = require('yargs').argv;

gulp.task('lint', function(){
  // log message in blue color
  util.log(util.colors.blue(msg)) ;
  // perform sub-tasks
  return gulp.src([
            /*source files*/ './src/**/*.js',
            /* js config files */'./*.js'])
         .pipe(gulpif(args.verbose, gulpprint())) // print files to be processed when the verbose flag is given
         .pipe(jscs())
         .pipe(jshint())
         .pipe(jshint.reporter('jshint-stylish', { verbose: true })) // jshint command line reporter
         .pipe(jshint.reporter('fail'));  // handle failure of linting with jshint
});

to run the task like this:

PS> gulp lint --verbose

Improving the Handling of Many Gulp Plugins with the Gulp-Load-Plugins Plugin

As you can see in the previous tasks, as soon as your gulp tasks start getting more complitated the number of plugins that you need to import in your gulpfile.js increases. In order to improve the management of your gulp plugins you can use the gulp-load-plugins package to load them automatically and lazily:

PS> npm install gulp-load-plugins --save-dev

var gulp = require('gulp'),
    args = require('yargs').argv;

/*
We can substitute these series of requires for a single call to the load plugins plugin
var gulpprint = require('gulp-print'),
    gulpif = require('gulp-if')
    jshint = require('gulp-jshint'),
    jscs = require('gulp-jscs'),
    util = require('gulp-util');
*/

var $ = require('gulp-load-plugins')({lazy: true});
// now we can call all plugins using the $.name-of-plugin-without-gulp (f.i. $.if or $.jshint)

gulp.task('lint', function(){
  // log message in blue color
  $.util.log(util.colors.blue(msg)) ;
  // perform sub-tasks
  return gulp.src([
            /*source files*/ './src/**/*.js',
            /* js config files */'./*.js'])
         .pipe($.if(args.verbose, $.print())) // print files to be processed when the verbose flag is given
         .pipe($.jscs())
         .pipe($.jshint())
         .pipe($.jshint.reporter('jshint-stylish', { verbose: true })) // jshint command line reporter
         .pipe($.jshint.reporter('fail'));  // handle failure of linting with jshint
});

Making Your Gulp Configuration More Maintainable By Extracting Configurations into Gulp.config.js

A way to make your gulp configuration more maintainable is to extract all configuration parameters to a separate configuration file gulp.config.js:


module.exports = function (){
  var config = {
    jsFiles: [/*source files*/ './src/**/*.js', /* js config files */'./*.js']
  };

  return config;
};

We can access this config file as any file in node:


var gulp = require('gulp'),
    args = require('yargs').argv,
    config = require('.\gulp.config')(),
    $ = require('gulp-load-plugins')({lazy: true});

gulp.task('lint', function(){
  // log message in blue color
  $.util.log(util.colors.blue(msg)) ;
  // perform sub-tasks
  return gulp.src(config.jsFiles)
         .pipe($.if(args.verbose, $.print())) // print files to be processed when the verbose flag is given
         .pipe($.jscs())
         .pipe($.jshint())
         .pipe($.jshint.reporter('jshint-stylish', { verbose: true })) // jshint command line reporter
         .pipe($.jshint.reporter('fail'));  // handle failure of linting with jshint
}

By separating the configuration setting from the configuration of the tasks themselves, you could reuse all tasks from project to project and then just change the configuration.

Using Gulp to Compile CSS from LESS/SASS/Stylus

CSS preprocesors like LESS, SASS or Stylus provide awesome features and functionality on top of CSS such as variables, mixins, utility functions, etc. Gulp provides a great workflow to compile your LESS (and SASS, Stylus, etc) files into vanilla CSS via the gulp-less package.

PS> npm install gulp-less --save-dev

var gulp = require('gulp')
    config = require('./gulp.config')(),
    $ = require('gulp-load-plugins')({lazy: true});

gulp.task('css', function(){
  return  gulp.src(config.cssFiles)
              .pipe($.less())
              .pipe(gulp.dest(config.temp));
});

where gulp.config.js should look like this:


module.exports = function(){
  var client = './src/client',
      config = {
    temp: './.tmp',
    jsFiles: [/*source files*/ './src/**/*.js', /* js config files */'./*.js']
    lessFiles: [client + '/styles/styles.less']
  };

  return config;
};

when you run gulp css the less files will be compiled into css and placed in the temporary folder.

Using Gulp to Add CSS Vendor Prefixes

You can automatically add vendor prefixes to your css by using the gulp-autoprefixer package:

PS> npm install gulp-autoprefixer --save-dev

var gulp = require('gulp')
    config = require('./gulp.config')(),
    $ = require('gulp-load-plugins')({lazy: true});

gulp.task('css', function(){
  return  gulp.src(config.lessFiles)
              .pipe($.less())
              .pipe($.autoprefix())
              .pipe(gulp.dest(config.temp));
});

Using Dependency Tasks to Delete Previously Generated Files

As you saw when we took a look at the gulp.task API method you can define tasks to be executed before a given task is run, these tasks are call dependency tasks. For instance, we can define a cleanup tasks that clean css files before re-generating them from LESS files:


var gulp = require('gulp')
    config = require('./gulp.config')(),
    $ = require('gulp-load-plugins')({lazy: true});

// clean css before running less-to-css compilation
gulp.task('css', ['clean-css'], function(){
  return  gulp.src(config.lessFiles)
              .pipe($.less())
              .pipe($.autoprefix())
              .pipe(gulp.dest(config.temp));
});

gulp.task('clean-css', function(done){
  var files = config.temp + '**/*.css';
  del(files, done);
  // done callback is called when the files are deleted
  // and then the 'css' task is notified that the 'clean-css' task has been completed
});

Now whenever we run gulp css the task clean-css will be run automatically. (Note that you need to install the del npm package with npm install del --save-dev).

Using Gulp to Compile LESS files into CSS whenever a LESS file is Modified

We can use the gulp.watch API method that we saw previously to monitor LESS files for changes and trigger the CSS handling pipeline:


gulp.task('watch-less', function(){
  // when less files change run the css pipeline
  return gulp.watch(config.lessFiles, ['css']);
});

Now you can start the watch-less task to monitor changes in less files and generate css:

PS> gulp watch-less

You can also include these gulp-watch calls inside other tasks and ensure that additional tasks are executed on file changes.

Using Gulp To Automatically Inject References to Js and CSS into Your HTML files

A front-end devops pipeline installs and generates javascript and css files. We can use gulp to automatically inject references to both the javascript third party libraries, javascript and css from our application into our HTML files. The wiredep library (to wire dependencies) and gulp-inject plugin are particularly good in performing this task.

PS> npm install wiredep gulp-inject --save-dev

In order to tell the plugin where files should be placed we used html comments like <!-- bower:js --> for instance. In a real world app we would use something like this:

<html>
<head>
<!-- bower:css -->
<!-- endbower -->
<!-- inject:css -->
<!-- endbower -->
</head>
<body>
  <section><h1>My WebApp</h1></section>
<!-- bower:js -->
<!-- endbower -->
<!-- inject:js -->
<!-- endbower -->
</body>
</html>

We can then create a task to wire bower dependencies:


gulp.task('wiredep', function(){
  /* wire bower css/js dependencies and our app js */
  var wiredep = require('wiredep').stream;

  return gulp.src(config.index)
             .pipe(wiredep(config.wiredepOptions))
             .pipe($.inject(gulp.src(config.js)))
             .pipe(gulp.dest(config.client));
});

gulp.task('inject', ['wiredep', 'css'], function(){
  /* wire bower css/js dependencies and our app js AND CSS */
  /* these two tasks are separated because inject is slower as it needs to compile the LESS into CSS */
  var wiredep = require('wiredep').stream;

  return gulp.src(config.index)
             .pipe($.inject(gulp.src(config.css)))
             .pipe(gulp.dest(config.client));
});

with an config file like:


module.exports = function(){
  var temp = './.tmp',
      client = './src/client',
      clientApp = client + 'app/',
      bower = {
          json: './bower.json',
          directory: './bower_components/',
          ignorePath: '../..'
      },
      config = {

        /*** paths ***/
        client: client
        temp: temp,
        jsFiles: [/*source files*/ './src/**/*.js', /* js config files */'./*.js'],
        js: [
          clientApp + '**/*.module.js' // add angular modules
          clientApp + '**/*.js' // add rest of the js files,
          '!' + clientApp + '**/*.spec.js' // ignore spec files
          ],
        lessFiles: [client + '/styles/styles.less'],
        css: temp + 'styles.css'
        index: './index.html',

        /*** bower ***/
        bower: bower

        /*** wiredep ***/
        // wire dep needs to know about bower directories so that
        // it can find the dependencies and inject them in our index.html file
        wiredepOptions: {
          bowerJson: bower.json,
          directory: bower.directory,
          ignorePath: bower.ignorePath
        },
  };

  return config;
};

Configuring Bower to Wire Dependencies After Installing New Libraries

In order to achieve this you need to add a post install script in your bower configuration file .bowerrc:


{
  "directory": "bower_components",
  "scripts": {
    "postinstall": "gulp wiredep"
  }
}

Using Gulp to Create a Server to Run Your Dev Build

You can create a gulp task that makes use of the nodemon npm package to restart a node server when you make changes on the source code of your web application. There is a gulp-nodemon plugin that lets you run tasks when events are fired.

PS> npm install gulp-nodemon --save-dev

gulp.task('serve', [ /* build all front-end files and wire dependencies */ 'inject'], function(){
  var nodeOptions = {
    script: config.nodeServer,
    delayTime: 1,
    env: {
      'PORT' : process.env.PORT || config.defaultPort,
      'NODE_ENV': 'dev'
    },
    watch: [config.server] // when any of the server files change the server will restart
  };

  return $.nodemon(nodeOptions)
          .on('restart', ['lint'], function(){}) // restart after files in the server have changed (lint task is only executed on restart!)
          .on('start', function(){}) // start
          .on('crash', function(){}) // crash
          .on('exit', function(){}); // clean exit
});

with the configuration file:


//...
  defaultPort: 8087,
  nodeServer: './src/server/app.js',
  server = './src/server/'
//...

Using Gulp to Auto-magically Update Your Browser When You Change Files

You can use gulp and BrowserSync to keep not one but multiple browsers synched when you update your files (and even when you perform actions in each given browser like clicking, scrolling or filling a form). You can add BrowserSync to your project like any npm package:

PS> npm install browser-sync --save-dev

And you update your gulp-file.js:

var browserSync = require('browser-sync');

// update server start to start browser sync

gulp.task('serve', [ /* build all front-end files and wire dependencies */ 'inject'], function(){
  var nodeOptions = {
    script: config.nodeServer,
    delayTime: 1,
    env: {
      'PORT' : process.env.PORT || config.defaultPort,
      'NODE_ENV': 'dev'
    },
    watch: [config.server] // when any of the server files change the server will restart
  };

  return $.nodemon(nodeOptions)
          .on('restart', ['lint'], function(){
              // server restarted after files in the server have changed (lint task is only executed on restart!)
              setTimeout(function(){
                browserSync.notify('Restarted server. Reloading...');
                browserSync.reload({stream: false});
              }, /* reload delay */ 1000);
            })
          .on('start', function(){
            // server started
            startBrowserSync();
          })
          .on('crash', function(){}) // crash
          .on('exit', function(){}); // clean exit
});

function startBrowserSync(){
  if(args.nosync || browserSync.active) return;

  // monitor changes on less and generate css
  gulp.watch([config.less], ['css']);

  var options = {
    proxy: 'localhost:' + port,
    port: 3000,
    files: [
      config.client + '**/*.*', // watch all client files (js, css, html)
      '!' + config.less, // don't watch less file (we want css to trigger changes)
      config.temp + '**/*.css'
      ],
    ghostMode: { // sync browsers
      clicks: true,
      location: true,
      forms: true,
      scroll: true
    },
    injectChanges: true, // inject just files that have changed and avoid full reload if possible
    logFileChanges: true,
    logLevel: 'debug',
    logPrefix: 'gulp-patterns',
    notify: true,
    reloadDelay: 1000
  };
  // run browserSync
  browserSync(options);
}

Now you can run gulp serve and browser sync will be automatically activated. From the on it will monitor any changes in your front-end files and reload the page as needed. If you don’t want to run browser sync when serving your application you can use the --nosync switch.

Managing an Ever-increasing Number of Gulp Tasks With the Gulp-Task-Listing Plugin

You can use the gulp-task-listing plugin to create lists to easily visualize your existing gulp tasks.

PS> npm install gulp-task-listing --save-dev
gulp.task('help', $.taskListing);

Now when you run gulp help you’ll get a list of the available tasks.

Using Gulp to Copy Yourn Font-Styles to Your Output Build/Distribution Folder

You can easily copy files between folders by piping gulp.src with gulp.dest:


gulp.task('fonts', ['clean-fonts'], function(){
  // copying fonts
  return gulp.src(config.fonts) // client + 'fonts/**/*.*'
             .pipe(gulp.dest(config.build) + 'fonts');
});

gulp.task('clean-fonts', function(done){
  del(config.build + 'fonts/**/*.*', done);
})

Using Gulp to Compress your Images and Serve them Web-Optimized to Your Users

You can use the gulp-imagemin plugint to compress images and thus opmitize how they are served to your users.

PS> npm install gulp-imagemin --save-dev

gulp.task('images', ['clean-images'], function(){
  // copying and compressing images
  return gulp.src(config.images) // client + 'images/**/*.*'
             .pipe($.imagemin())
             .pipe(gulp.dest(config.build) + 'images'); // './build/'
});

gulp.task('clean-images', function(done){
  del(config.build + 'images/**/*.*', done);
})

Using Gulp to Complete the Build Pipeline and Serve a Production Version of Your App

So far we’ve been working with the development version of our application. When we want to take our app into production we want to minimize the number of assets that we are going to distribute over the wire to minimize the number of HTTP calls and bandwidth consumed. In order to do that we need to gather all our assets and submit them to bundling and minification processes. Gulp can helps us achieve that by combining all previous tasks with the gulp-useref plugin.

The gulp-useref plugin gathers annotated sections of html in comments and turns them into a single file. We can combine the previously used sections for wiredep and gulp-inject with an additional filename for gulp-useref to use:


<html>
<head>
  <!-- build:css styles/app.css -->
  <!-- inject:css -->
  <!-- endinject -->
  <!-- endbuild -->
</head>
<body>
  <!-- build:js js/app.js -->
  <!-- inject:js -->
  <!-- endinject -->
  <!-- other js files -->
  <!-- endbuild -->
</body>
</html>
PS> npm install gulp-useref --save-dev

gulp.task('build',
  ['inject'],  // inject wires all dependencies via wiredep (js,css), injects our app's js/css and also prepares the templateCache
  function(){
  var assets = $.useref.assets({searchPath: './'}); // gather all assets between comments and find them starting in the root

  return gulp
    .src(config.index) // grab the config.index file
    .pipe($.plumber()) // error handler
    .pipe($.inject(
      gulp.src(templaceCache, {read: false}),
      {
        starttag: '<!-- inject:templates:js --> ' // => inject inside this comment element
      }))
    .pipe(assets) // grab all assets and bundle them into single js/css files (everything between build comment tags inside a single file)
    .pipe(assets.restore()) // restore to get index.html back
    .pipe($.useref()) // update index.html with tags (link for css, script for js) pointing to bundled files
    .pipe(gulp.dest(config.build)); // write everything to the build folder
})

// in summary
// useref.assets() gathers all assets from the HTML comments
// useref.assets.restore() restores the files to the stream (index.html for instance) and concatenate all assets within single files
// useref() update files (index.html for instance) with links to the concatenated/bundled files

And you can serve the production build with a new task:


gulp.task('serve-build', ['build'],  function(){

  var nodeOptions = {
    script: config.nodeServer,
    delayTime: 1,
    env: {
      'PORT' : process.env.PORT || config.defaultPort,
      'NODE_ENV': 'build'
    },
    watch: [config.server] // when any of the server files change the server will restart
  };

  return $.nodemon(nodeOptions)
          .on('restart', ['lint'], function(){
              // server restarted after files in the server have changed (lint task is only executed on restart!)
              setTimeout(function(){
                browserSync.notify('Restarted server. Reloading...');
                browserSync.reload({stream: false});
              }, /* reload delay */ 1000);
            })
          .on('start', function(){
            // server started
            // start browsers sync
            if(args.nosync || browserSync.active) return;

            // monitor changes on all files and trigger build, then reload browseSync
            gulp.watch([config.less, config.js, config.html], ['build', browserSync.reload]);

            var options = {
              proxy: 'localhost:' + port,
              port: 3000,
              files: [], // don't reload on source files
              ghostMode: { // sync browsers
                clicks: true,
                location: true,
                forms: true,
                scroll: true
              },
              injectChanges: true, // inject just files that have changed and avoid full reload if possible
              logFileChanges: true,
              logLevel: 'debug',
              logPrefix: 'gulp-patterns',
              notify: true,
              reloadDelay: 1000
            };
            // run browserSync
            browserSync(options);
          })
          .on('crash', function(){}) // crash
          .on('exit', function(){}); // clean exit

});

Using Gulp to Minify Your Front-End Assets

The gulp-csso plugin (CSS optimizer) let’s you optimize your CSS files (minification, mangling and other optimizations). The gulp-uglify plugin let’s you perform optimizations in your JS files (minification and mangling). And you can use the gulp-filter plugin to filter files, perform specific transformations on them and then continue handling all files within the build pipeline.

PS> npm install gulp-csso gulp-uglify gulp-filter --save-dev

gulp.task('build',
  // inject wires all dependencies via wiredep (js,css), injects our app's js/css and also prepares the templateCache
  // copy fonts
  // copy and optimize images
  ['inject', 'fonts', 'images'],
  function(){
  var assets = $.useref.assets({searchPath: './'}), // gather all assets between comments and find them starting in the root
      cssFilter = $.filter('**/*.css'),
      jsFilter = $.filter('**/*.js');

  return gulp
    .src(config.index) // grab the config.index file
    .pipe($.plumber()) // error handler
    .pipe($.inject(
      gulp.src(templaceCache, {read: false}),
      {
        starttag: '<!-- inject:templates:js --> ' // => inject inside this comment element
      }))
    .pipe(assets) // grab all assets and bundle them into single js/css files (everything between build comment tags inside a single file)
      .pipe(cssFilter)
      .pipe($.csso()) // optimize css
      .pipe(cssFilter.restore())
      .pipe(jsFilter)
      .pipe($.uglify()) // optimize js
      .pipe(jsFilter.restore())
    .pipe(assets.restore()) // restore to get index.html back
    .pipe($.useref()) // update index.html with tags (link for css, script for js) pointing to bundled files
    .pipe(gulp.dest(config.build)); // write everything to the build folder
})

Using Gulp to Manage Assets Revisions (And Avoid Caching When New Versions of a Web App Are Released)

You can use the gulp-rev plugin to handle revisions of the files in your build pipeline. It will rename your files with revision hashes. And the gulp-rev-replace plugin to update your application references to point to the versioned assets (and not to the files with the original names).

PS> npm install gulp-rev gulp-rev-replace --save-dev

gulp.task('build',
  // inject wires all dependencies via wiredep (js,css), injects our app's js/css and also prepares the templateCache
  // copy fonts
  // copy and optimize images
  ['inject', 'fonts', 'images'],
  function(){
  var assets = $.useref.assets({searchPath: './'}), // gather all assets between comments and find them starting in the root
      cssFilter = $.filter('**/*.css'),
      jsLibFilter = $.filter('**/lib.js'),
      jsAppFilter = $.filter('**/app.js');

  return gulp
    .src(config.index) // grab the config.index file
    .pipe($.plumber()) // error handler
    .pipe($.inject(
      gulp.src(templaceCache, {read: false}),
      {
        starttag: '<!-- inject:templates:js --> ' // => inject inside this comment element
      }))
    .pipe(assets) // grab all assets and bundle them into single js/css files (everything between build comment tags inside a single file)
    .pipe(cssFilter)
      .pipe($.csso()) // optimize css
    .pipe(cssFilter.restore())
    .pipe(jsAppFilter)
      .pipe($.ngAnnotate()) // =================> annotate angular DI <=========================
      .pipe($.uglify()) // optimize js
    .pipe(jsAppFilter.restore())
    .pipe(jsLibFilter)
      .pipe($.uglify()) // optimize js
    .pipe(jsLibFilter.restore())
    .pipe(assets.restore()) // restore to get index.html back

    .pipe($.rev()) // Add revisions to generated files : app.js to app-3414141ASDF.js

    .pipe($.useref()) // update index.html with tags (link for css, script for js) pointing to bundled files

    .pipe($.revReplace()) // rename references places by useref for ones with revisions
    .pipe($.rev.manifest()) // write revision manifest -> outputs rev.manifest.json with information about the old and new revision

    .pipe(gulp.dest(config.build)); // write everything to the build folder
})

When you publish your application you will want to increment the version of the application in your package.json file. The gulp-bump plugin can help you with that task:

PS> npm install gulp-bump --save-dev
gulp.task('bump', function(){
  Object.assign(options, {args.type, args.version});
  return gulp
    .src(config.packages) // ['./package.json', './bower.json']
    .pipe($.bump(options))
    .pire($.print())
    .pipe(gulp.dest(config.root)) // './'
});

Now you can use gulp bump --type=minor or gulp bump --version=0.10.0 to increase versions.

Running Your Unit Tests With Gulp

Gulp lets you easily hook your application with automated test runners like karma to run your Jasmine/Mocha/QUnit tests.

# using karma(runner), mocha(test framework), chai(assertion library), sinon(mocking framework), phantomjs (headless browser to run UT)
PS> npm install karma karma-chai karma-chai-sinon karma-chrome-launcher karma-coverage karma-growl-reporter karma-mocha karma-phantomjs-launcher karma-sinon mocha mocha-clean sinon-chai sinon phantomjs --save-dev

Having installed karma in your project (and thus having a karma.conf.js file) you can easily create gulp testing tasks as detailed below:


gulp.task('test', ['lint', 'templatecache'], function(done){
  startTest({singleRun: true, done: done})
});

function startTest(options){
  var singleRun = options.singleRun || true,
      done = options.done,
      karma = require('karma').server,
      karmaOptions = {
        configFile: __dirname + '/karma.conf.js',
        exclude: [config.serverIntegrationSpecs],
        singleRun: singleRun
      };

  karma.start({karmaOptions}, karmaCompleted);

  function karmaCompleted(result){
    if (karmaResult === 1){
      done('karma: tests failed with code ' + result);
    } else {
      done();
    }
  }
}

You’ll need to configure your karma.conf.js properly to point to the code to be tested and the test specs themselves. After that you can run gulp test to initiate karma and run your unit tests. You can also use the node-notifier npm package to show you a toast when the process of running tests is complete.

You can also run your tests continuously by making use the startTest function above with options.singleRun = false:


gulp.task('test-continuous', ['lint', 'templatecache'], function(done){
  startTest({singleRun: false, done: done});
});

Using Gulp to Run Your Integration Tests

In order to run integration tests with gulp you’ll need to run child processes from gulp (your server, db, etc). In order to do that you’ll need to use the child_process module.

// before starting tests, start the servers
var child, fork = require('child_process').fork;
if (args.startServers){
  child = fork(config.nodeServer);
}
// later when karma is complete kill the server
child.kill();

Using Gulp to run your Tests in an Html Runner Instead of in the Terminal

In order to run your tests within an HTML runner instead of in the console you’ll need to create an spec.html file and automate the wiring of dependencies and injection of application source code.

Setting Up a Default Task in Gulp

You can setup your default task by naming any given task default. The default task will be executed when you type gulp without an argument.

gulp.task('default', ['help']);

In the previous example the help will be shown when we type gulp.

Copying Files in Gulp

You can easily copy files in gulp by piping gulp.src to gulp.dest directly:


gulp.task('copyyyy', function(){
  return gulp
          .src(config.source)
          .pipe(gulp.dest(config.destination));
});

Handling Errors in Gulp With Gulp-Plumber

Gulp lets you handle events within a task pipeline. A way to handle errors that may occur within the pipeline is to subscribe to the error event:


gulp.task('css', ['clean-css'], function(){
  return  gulp.src(config.lessFiles)
              .pipe($.less())
              .on('error', logError)
              .pipe($.autoprefix())
              .pipe(gulp.dest(config.temp));
});

function logError(error){
  // log error
  $.util.log($.util.colors.red(error));
  // stop pipeline by emitting `end` event
  this.emit('end');
}

A better way to handle errors is by using the gulp-plumber plugin that you can install via npm install --save-dev gulp-plumber:


gulp.task('css', ['clean-css'], function(){
  return  gulp.src(config.lessFiles)
              .pipe($.plumber())
              .pipe($.less())
              .pipe($.autoprefix())
              .pipe(gulp.dest(config.temp));
});

Using Gulp with Other Client-Side Frameworks

Using Gulp and Angular to Cache Angular HTML Templates in the $templateCache

By default, angular will make an HTTP request to get any HTML template referenced by a directive or a route. The Angular $templateCache service allows us to define key value pairs of urls matching to templates and thus provide a cache and avoid the necessity of doing unnecessary HTTP requests.

You can use the gulp-angular-templatecache plugin to get all your Angular HTML templates and put them inside the $templateCache, and the gulp-minify-html plugin to minify them:

PS> npm install gulp-angular-templatecache gulp-minify-html --save-dev
gulp.task('template-cache', ['clean-code'], function(){
  var templatesConfig = {
    file: 'templates.js',
    options: {
      module: 'app.core',
      standAlone: false, // add them to the existing app.core module (don't create a new module),
      root: 'app/' // prefix for template urls
    }
  };

  return gulp.src(config.htmltemplates) // clientApp + '**/*.html'
             .pipe($.minifyHtml({empty: true})) // include empty HTML tags
             .pipe($.angularTemplatecache(templatesConfig))
             .pipe(gulp.dest(config.temp));
})

gulp.task('clean-code', function(done){
  var allFiles = [
    config.temp + '**/*.js',
    config.build + 'js/**/*.js',
    config.build + '**/*.html'
  ];
  del(allFiles, done)
})

You can find more information about the gulp-angular-templatecache plugin on GitHub.

Beware of Mangling With Angular

Whenever you optimize your javascript code with mangling, the Angular DI infrastructure can be affected. Because mangling will try to use single letters as names for arguments, angular won’t be able to use convention over configuration to find your services, etc by name. In those ocassions you’ll get cryptic warnings in the browser. A way to get better DI information is to decorate your ng-app element with ng-strict-di. This will warn you whenever any angular component lacks the necessary annotations (see $inject property annotation).

Using Gulp to Annotate Your Angular Components with Dependency Injection Annotations

The gulp-ng-annotate plugin will search through your angular code, find where DI annotations are required and apply them before the javascript code gets optimized.

PS> npm install gulp-ng-annotate --save-dev

gulp.task('build',
  // inject wires all dependencies via wiredep (js,css), injects our app's js/css and also prepares the templateCache
  // copy fonts
  // copy and optimize images
  ['inject', 'fonts', 'images'],
  function(){
  var assets = $.useref.assets({searchPath: './'}), // gather all assets between comments and find them starting in the root
      cssFilter = $.filter('**/*.css'),
      jsLibFilter = $.filter('**/lib.js'),
      jsAppFilter = $.filter('**/app.js');

  return gulp
    .src(config.index) // grab the config.index file
    .pipe($.plumber()) // error handler
    .pipe($.inject(
      gulp.src(templaceCache, {read: false}),
      {
        starttag: '<!-- inject:templates:js --> ' // => inject inside this comment element
      }))
    .pipe(assets) // grab all assets and bundle them into single js/css files (everything between build comment tags inside a single file)
    .pipe(cssFilter)
      .pipe($.csso()) // optimize css
    .pipe(cssFilter.restore())
    .pipe(jsAppFilter)
      .pipe($.ngAnnotate()) // =================> annotate angular DI <=========================
      .pipe($.uglify()) // optimize js
    .pipe(jsAppFilter.restore())
    .pipe(jsLibFilter)
      .pipe($.uglify()) // optimize js
    .pipe(jsLibFilter.restore())
    .pipe(assets.restore()) // restore to get index.html back
    .pipe($.useref()) // update index.html with tags (link for css, script for js) pointing to bundled files
    .pipe(gulp.dest(config.build)); // write everything to the build folder
})

Note that in some cases you’ll need to add hints to ngAnnotate like html comments: /* @ngInject */. This will happend particularly with anonymous functions.

Gulp 4

Take a look at Gulp 4 changelog. In summary the task engine has changed and now you can:

  • Run tasks in a explicit serial order: gulp.task('styles', gulp.series('clean-styles', styles))
  • Run tasks in parallel as before: gulp.task('assets', gulp.parallel('fonts', 'images', 'styles'))
  • Mix tasks in series and parallel: gulp.task('build', gulp.series(gulp.parallel(...), gulp.parallel(...)))
  • Built-in listing of tasks with: gulp --tasks or gulp --tasks-simple

Migrate From Gulp 3 to Gulp 4

Just substitute the task signatures from gulp.task(name[,dep],fn1) to gulp.task(name,fn2) where fn2 are sets of tasks that can run either in parallel or in serial form (either task names or functions). You’ll also need to notify serial tasks when they are done (by calling done()).

You can install the pre release version of gulp by running: npm install --save-dev git://github.com/gulpjs/gulp.git#4.0 and npm install -g git://github.com/gulpjs/gulp-cli.git#4.0.

References


Jaime González García

Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.Jaime González García