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 performgulp.src
lets you define which files a task is going to convert in a stream and operate overgulp.dest
lets you define where the output files are going to be placedgulp.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:
- a destination folder
- a set of options
// 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:
- a glob to select the files to watch
- a set of options
- the tasks to execute when the selected files are changed
// 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
orgulp --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
- Gulp.js website
- Gulp.js API docs
- This awesome John Papa’s course: Javascript Build Automation With Gulp.js at Pluralsight
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.