gulp 4
By Mark Volkmann, OCI Partner & Principal Software Engineer
June 2015
Introduction
gulp is a JavaScript-based build tool for automating the execution of web development tasks.
Actually, gulp can automate anything that may be done from Node.js, and since Node.js can run shell commands, gulp may actually be used to automate any task. However, in practice, gulp is primarily used for tasks associated with web development.
gulp competes with tools like Grunt, which (like gulp) is also free and open source. Other contenders (far less popular) include: Broccoli, Brunch, Cake, and Jake.
One major difference between gulp and Grunt is the way each is configured. Both use a JavaScript source file: one named gulpfile.js
and the other Gruntfile.js
. Configuration for gulp is accomplished with calls to various functions. Grunt configuration is primarily done by passing a rather large object literal to one function.
As a rule, gulp runs faster than Grunt due to its use of streams. The alternative, used by Grunt, is creating many temporary files that are used to make data available to subsequent steps. Using streams allows subsequent steps to begin earlier, as soon as data is available in a stream, instead of waiting for a file to be completely written.
To run gulp, Node.js must be installed. It can also run on io.js, which is a fork of Node.js that uses a newer version of the Chrome V8 JavaScript engine and releases new versions more frequently. Like Node.js itself, gulp may be used in Windows, Mac OS X, and *nix operating systems.
With both Node.js and io.js, some ES6 (a.k.a ES 2015) features may be used if the --harmony
flag is set. To get gulp to use this in a *nix environment, create an alias like the following: alias gulp6='node --harmony $(which gulp)'
. This may be placed in .bashrc
so it is always available. After this, run gulp commands with gulp6
instead of gulp
. In a Windows environment, consider using the doskey
command to define something similar to a *nix alias.
One ES6 feature, used in some examples in this article, is the arrow function expression. This feature provides a more concise way to write anonymous functions, and it has other benefits ("lexical this"). For purposes of this article, all you need to know is that function () { return expression; }
in ES5 is roughly equivalent to () => expression
in ES6. For a thorough discussion of ES6 features, please visit these slides.
Typically, plugins are used in the implemention of gulp tasks. A large number of plugins are available: 1,711 as of 5/25/15. To get an updated count in a *nix environment, run
npm search gulpplugin | wc-l
Common tasks that may be run using gulp include:
- validating/linting HTML, CSS, JavaScript, and JSON files
- compiling ES6 JavaScript code to ES5 (using Babel, Traceur, or TypeScript)
- running unit tests and end-to-end tests
- concatenating and minimizing CSS and JavaScript files
- serving static files via HTTP
- executing shell commands
- watching specific files or file types for changes and running specific tasks when they do
- reloading a web browser after modified files have been processed (livereload)
At the time of writing, the current stable version of gulp was 3. New development should use version 4, which is the version this article targets. Version 4 is not backward-compatible with version 3. Changes to gulpfile.js files are required. However, most plugins written for version 3 should work with version 4.
Throughout this article, references to "terminal" may be replaced by "Command Prompt" when using Windows.
Installing gulp
To make the gulp
command available from a terminal, enter npm install -g gulp
. This installs the current, stable version.
Until version 4 becomes the stable version, follow these steps to install it:
- Open a terminal.
- Verify that git is installed.
- If a previous version of gulp is installed, uninstall it with
npm uninstall -g gulp
- Install version 4 with
npm install -g gulpjs/gulp-cli#4.0
To configure a project to use gulp 4:
- Open a terminal.
cd
to the top project directory.- If no
package.json
file exists, create one by runningnpm init
and answering the questions it asks. - Install gulp locally and add it as a dependency in
package.json
by runningnpm install gulp --save-dev
- If a previous version of gulp is installed, uninstall it with
npm uninstall gulp
npm install gulpjs/gulp.git#4.0 --save-dev
- Create
gulpfile.js
(described later).
The --save-dev
option of npm install
adds a development dependency to package.json
. This allows other developers on the team to install all of the project dependencies by running npm install
.
Running gulp
Before attempting to run a gulp task that is defined in the project gulpfile.js
, cd
to the top project directory or a subdirectory.
To get basic help on gulp options, enter gulp --help
or gulp -h
.
To see the version of gulp that is installed, enter gulp --version
or gulp -v
. This shows the versions of the globally-installed command-line interpreter (CLI) and the project-specific, locally installed version.
To see a list of the tasks defined in gulpfile.js
for the current project, enter gulp --tasks
or gulp -T
. This outputs a task dependency tree. To see a flat list of tasks in the order they are defined, enter gulp --tasks-simple
.
To check for usage of blacklisted plugins, enter gulp --verify
. This examines the dependencies listed in the project's package.json
file.
To run tasks defined in gulpfile.js
, enter gulp [options] task1 task2...
While there are options that may be specified when running tasks, typically none is used. When multiple tasks are specified, they run in parallel. If they need to be run sequentially, define a task that does that, and run that one task. If no task is specified, the default task is run. We'll see how to define a default task later. If no task is specified and no default task is defined, an error message is displayed.
Most tasks run to completion and gulp exits. Some tasks such as connect
(for serving files using HTTP) and watch
(for watching for file changes) run until cancelled, and gulp does not exit on its own. To cancel a task and exit gulp, press ctrl-c.
gulp Plugins
The large number of available plugins can make finding good ones challenging. To get you started, here is a list of recommended plugins:
gulp-babel
- compiles ES6 to ES5gulp-changed
- only processes (pipes through) src files that are newer than dest filesgulp-concat
- concatenates CSS and JavaScript filesgulp-csslint
- validates CSS filesgulp-eslint
- validates JavaScript files using ESLintgulp-jasmine
- runs Jasmine testsgulp-jshint
- validates JavaScript files using JSHintgulp-jscs
- checks JavaScript code style using JSCSgulp-less
- compiles LESS files to CSSgulp-livereload
- reloads a web browser listening for livereload requests when thelivereload
method is calledgulp-plumber
- allows gulp to continue running after errorsgulp-sourcemaps
- generates sourcemap files that allow debugging in files (ES6, CoffeeScript, TypeScript, ...) that are compiled to the ES5 JavaScript running in the browsergulp-uglify
- minimizes JavaScript filesgulp-usemin
- replaces HTMLlink
/script
tags that refer to individual.css
and.js
files with single tags that refer to concatenated/minimized versionsgulp-watch
- watches files for changes and runs specified tasks when they do
In addition, the npm module del
is commonly used. This deletes specified directories and files.
One way to search for gulp plugins is to browse http://gulpjs.com/plugins. Plugins that are published to npm with the "gulpplugin" keyword are automatically cataloged at this site. Using this site has the advantage of being able to click the name of a plugin to browse its documentation page. Another way to search for a plugin is the npm search
command. For example, to get a list of all gulp plugins that perform some kind of linting, enter npm search gulpplugin lint
.
To install a plugin, enter npm install plugin-name --save-dev
. This installs the plugin in the project node_modules
directory. Once the plugin is installed, modify gulpfile.js
to "require" the plugin and use it in one or more tasks. For example, var foo = require('gulp-foo');
A better way to "require" plugins is to use the gulp-load-plugins
plugin. This makes it unnecessary to add a require for each plugin to gulpfile.js
. The function provided by this plugin returns an object with properties that are the base names of the plugins. It loads dependencies in package.json
whose names start with "gulp-
". Plugin loading is done lazily, which means that plugins are not loaded until their first use. Unused plugins are never loaded.
var pi = require('gulp-load-plugins')();
Reference plugins from within task definitions using pi.name
gulp Methods
The JavaScript methods provided by gulp are defined in two JavaScript classes, Gulp
and Undertaker
.
The Gulp
class provides the src
, dest
, and watch
methods. It is defined in the index.js
file at the top of the gulp GitHub repo (currently https://github.com/gulpjs/gulp/tree/4.0). This class inherits from the Undertaker
class.
The Undertaker
class provides the task
, series
, parallel
, get
, set
, tree
, and registry
methods. It is defined in the index.js
file at the top of the Undertaker GitHub repo (https://github.com/phated/undertaker). The Undertaker
class inherits from the Node core EventEmitter
class (https://nodejs.org/api/events.html).
It is not necessary to know about this inheritance hierarchy in order to use gulp, but it is important to understand how to use some of these methods.
Another important npm module used by gulp is vinyl-fs
. This uses Vinyl
objects to hold metadata that describes a file. Vinyl adapters provide access to the content of Vinyl
objects through streams. "The src stream produces file objects, and the dest stream consumes file objects."
For more detail, see https://github.com/wearefractal/vinyl-fs, https://github.com/wearefractal/vinyl, and https://medium.com/@contrahacks/gulp-3828e8126466.
A Gulp object is obtained by the following line:
var gulp = require('gulp');
This object supports all the methods in the Gulp
, Undertaker
, and EventEmitter
classes.
Several of the methods take a glob pattern argument. This may be a string or an array of strings. The strings may contain wildcard characters. The underlying implementation is provided by the npm module node-glob
. For details on the syntax, see "Glob Primer" at https://github.com/isaacs/node-glob. The basics are:
?
means one of any character*
means zero or more of any character**
in a path portion, means any number of directories
The src
method produces a stream of Vinyl
objects that may be piped to plugins. It takes a glob pattern and an options object. The glob pattern specifies the input files to be processed. Options are passed to the glob
module.
For details on options, see https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulpsrcglobs-options and https://github.com/isaacs/node-glob.
Typically no options are passed and that argument may be omitted.
The dest
method accepts piped data and outputs it to files. All data piped to it is re-emitted, which allows dest
to be used more than once in order to write to multiple files. It takes an output path and an options object. The output path specifies an output file or directory.
For details on options, see https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulpdestpath-options.
Typically no options are passed and that argument may be omitted.
The watch
method watches files and calls a function when they are modified. It takes a glob pattern, an options object, and a function. The glob pattern specifies the files to be watched. This is actually implemented by the npm module gaze
.
For details on options, see the options for the gaze.Gaze
constructor at https://github.com/shama/gaze.
Typically no options are passed and that argument may be omitted.
The task
method defines a task. It takes a string name and a function. When the task is run, the function is run. The function may be anonymous, or the name of a function defined elsewhere. If the function is omitted, it acts as a getter for the function previously associated with the task name.
The series
method returns a function that, when called, executes specified tasks in series. It takes any number of arguments that may be any combination of string task names and functions. Since this method returns a function, calls may be nested.
The parallel
method returns a function that, when called, executes specified tasks in parallel. It takes any number of arguments that may be any combination of string task names and functions. Since this method returns a function, calls may be nested.
The remaining methods in the Undertaker
class described below are not typically used directly in gulpfile.js
files.
The get
method returns the function associated with a given task name. It takes a string task name.
The set
method sets or changes the function associated with a given task name. It takes a string task name and a function. If the task is already defined, its function is replaced.
The tree
method returns an array of defined task name strings. It takes an options object. If the deep
option is set to true, it returns an array of objects that provide task names and their dependencies. This method is used by the gulp command-line options --tasks
and --tasks-simple
.
The registry
method gets or sets a map of task names to functions.
Defining gulp Tasks
Since gulp runs on top of Node.js, gulpfile.js
may contain any code that Node.js can process. This means all Node.js core modules and all npm modules may be used.
Here is the simplest example of a gulp task definition:
- var gulp = require('gulp');
-
- gulp.task('hello', function () {
- console.log('Hello, World!');
- });
Here is the same gulp task implemented using ES6.
- let gulp = require('gulp');
-
- gulp.task('hello', () => console.log('Hello, World!'));
To run this enter
gulp hello
Typically gulp task definitions take one of three forms:
gulp.task(name, function () { ... });
gulp.task(name, gulp.series(...));
gulp.task(name, gulp.parallel(...));
A task often reads specific files, performs one or more actions or transformations on the contents, and produces one or more output files. The general form of such a task is shown below.
gulp.task(name, function () {
return gulp.src(srcPath).
pipe(somePluginFn()).
pipe(anotherPluginFn()).
pipe(gulp.dest(destPath));
});
In ES6 this could be written as follows:
gulp.task(name, () =>
gulp.src(srcPath).
pipe(somePluginFn()).
pipe(anotherPluginFn()).
pipe(gulp.dest(destPath)));
Serving Files From gulp
There are several npm modules that serve static files using HTTP. One popular option is connect
at https://github.com/senchalabs/connect. The commands to install the required modules follow:
npm install connect --save
npm install serve-static -save
Below is an example of a gulp task that serves static files from the top project directory.
- var connect = require('connect');
- var http = require('http'); // a Node.js core module
- var serveStatic = require('serve-static');
-
- gulp.task('connect', function () {
- var app = connect();
- app.use(serveStatic(__dirname));
- var port = 8080;
- http.createServer(app).listen(port);
- });
__dirname
is a Node.js variable that holds the path to the current directory. If there is an index.html
file in that directory, it can be viewed by browsing http://localhost:8080.
To run this enter gulp connect
Watching For File Changes
gulp can be configured to run specified tasks when new files are created or existing files are modified. If gulpfile.js
is modified, gulp must be restarted for the changes to take effect.
Below is an example of a gulp task that watches for changes to LESS files. When changes are detected, it runs the less
and csslint
tasks which we'll see later.
- gulp.task('watch', function () {
- gulp.watch('styles/*.less', gulp.series('less', 'csslint'));
- })
Live Reload
gulp can trigger a web browser to reload, which is useful after changes to HTML, CSS, JavaScript, and other files loaded by the browser. There are several gulp plugins that support this. A popular choice is gulp-livereload
at https://github.com/vohof/gulp-livereload. This works best with Chrome and requires installing the livereload Chrome extension.
To install this Chrome extension, browse https://chrome.google.com/webstore/category/apps and enter "livereload" in the search field.
To use this plugin, follow these steps:
- Install the
gulp-livereload
plugin. - Add the following script tag to the main HTML file:
<script type="text/javascript" src="http://localhost:35729/liverload.js">// <![CDATA[// ]]></script>
- Call
livereload.listen()
in thewatch
task. - Call
livereload()
after every file change that should trigger a reload.
The example gulpfile.js
below demonstrates the last two of these steps. It defines a series of gulp tasks that are useful for typical web applications.
- var connect = require('connect');
- var del = require('del');
- var gulp = require('gulp');
- var http = require('http');
- var pi = require('gulp-load-plugins')();
- var serveStatic = require('serve-static');
-
- var paths = {
- build: 'build',
- css: 'build/**/*.css',
- html: ['index.html', 'src/**/*.html'],
- js: ['src/**/*.js'],
- jsPlusTests: ['src/**/*.js', 'test/**/*.js'],
- less: 'src/**/*.less',
- test: 'build/**/*-test.js'
- };
-
- // This just demonstrates the simplest possible task.
- gulp.task('hello', function () {
- console.log('Hello, World!'));
- });
-
- // This deletes all generated files.
- // In tasks that do something asynchronously, the function
- // passed to task should take a callback function and
- // invoke it when the asynchronous action completes.
- // This is how gulp knows when the task has completed.
- gulp.task('clean', function (cb) {
- del(paths.build, cb);
- });
-
- // This starts a simple HTTP file server.
- gulp.task('connect', function () {
- var app = connect();
- app.use(serveStatic(__dirname));
- http.createServer(app).listen(1919);
- });
-
- // This validates all CSS files.
- // In this example, the CSS files are generated from LESS files.
- gulp.task('csslint', function () {
- return gulp.src(paths.css).
- pipe(pi.csslint({ids: false})).
- pipe(pi.csslint.reporter());
- });
-
- // This validates JavaScript files using ESLint.
- gulp.task('eslint', function () {
- return gulp.src(paths.jsPlusTests).
- pipe(pi.changed(paths.build)).
- pipe(pi.eslint({
- envs: ['browser', 'es6', 'node'],
- rules: {
- curly: [2, 'multi-line'],
- indent: [2, 2]
- }
- })).
- pipe(pi.eslint.format());
- });
-
- // This is used by the "watch" task to
- // reload the browser when an HTML file is modified.
- gulp.task('html', function () {
- return gulp.src(paths.html).
- pipe(pi.livereload());
- });
-
- // This validates JavaScript files using JSHint.
- gulp.task('jshint', function () {
- return gulp.src(paths.jsPlusTests).
- pipe(pi.changed(paths.build)).
- pipe(pi.jshint()).
- pipe(pi.jshint.reporter('default'));
- });
-
- // This compiles LESS files to CSS files.
- gulp.task('less', function () {
- return gulp.src(paths.less).
- pipe(pi.changed(paths.build)).
- pipe(pi.less()).
- pipe(gulp.dest(paths.build)).
- pipe(pi.livereload());
- });
-
- // This compiles ES6 JavaScript files to ES5 JavaScript files.
- // "transpile" is a term used to describe compiling
- // one syntax to a different version of itself.
- // Compiling ES6 code to ES5 fits this description.
- gulp.task('transpile-dev', function () {
- return gulp.src(paths.jsPlusTests).
- pipe(pi.changed(paths.build)).
- pipe(pi.sourcemaps.init()).
- pipe(pi.babel()).
- pipe(pi.sourcemaps.write('.')).
- pipe(gulp.dest(paths.build)).
- pipe(pi.livereload());
- });
-
- // This does the same as the previous task, but also
- // concatenates and minimizes the resulting JavaScript files.
- gulp.task('transpile-prod', function () {
- return gulp.src(paths.js).
- pipe(pi.sourcemaps.init()).
- pipe(pi.babel()).
- pipe(pi.concat('all.js')).
- pipe(pi.uglify()).
- pipe(pi.sourcemaps.write('.')).
- pipe(gulp.dest(paths.build));
- });
-
- // This is not meant to be used directly.
- // Use the "test" task instead.
- gulp.task('jasmine', function () {
- return gulp.src(paths.test).
- pipe(pi.plumber()).
- pipe(pi.jasmine());
- });
-
- gulp.task('test', gulp.series('transpile-dev', 'jasmine'));
-
- // This watches HTML, LESS, and JavaScript files for changes
- // and processes them when they do.
- // It also reloads the web browser.
- gulp.task('watch', function () {
- pi.livereload.listen();
- gulp.watch(paths.html, gulp.series('html'));
- gulp.watch(paths.less, gulp.series('less', 'csslint'));
- gulp.watch(paths.jsPlusTests,
- gulp.series('eslint', 'jshint', 'transpile-dev'));
- });
-
- // This compiles LESS and ES5 JavaScript files in parallel.
- gulp.task('build-dev', gulp.parallel('less', 'transpile-dev'));
-
- // This does the same as the previous tasks, but also
- // concatenates and minimizes the resulting JavaScript files.
- gulp.task('build-prod', gulp.parallel('less', 'transpile-prod'));
-
- // This is used when gulp is run without specifying a task.
- // It runs the "build-dev" task and then
- // starts the "connect" and "watch" tasks in parallel.
- // This is the most commonly used task during development.
- gulp.task('default',
- gulp.series('build-dev', gulp.parallel('connect', 'watch')));
Typical Usage
The most common usage of the gulpfile.js
above includes the following steps:
- Open a terminal, cd to the project directory, and run
gulp
in it.
This starts the local HTTP server and begins watching for file changes. - Keep this terminal visible so new output can be observed.
- Browse the app in a web browser that works with livereload.
- Use any editor or IDE to make changes to the client-side files of the app.
- Watch the terminal for errors reported by gulp tasks that are triggered by watches.
- Watch the browser for updates that result from liveload.
- Repeat all day long.
Caveat
One concern is how long it is taking for gulp 4 to replace gulp 3 as the stable version. On January 29, 2015, the main contributor tweeted "#gulpjs 4 was supposed to come out on the 31st but i got the flu and am down for a few days, sorry folks". On April 28, 2015, I replied, "Any update on when gulp 4 might be released? I know we can use it now, but I'm looking for an official release." The reply was a simple "nope". On May 19, 2015, the main contributor tweeted, "I'm pleased to announce our 10m in funding for @gulpjs as part of a Series B." Hopefully that is an indication that progress on a new release will soon resume.
Summary
gulp is a popular and powerful tool for automating web development tasks. It seems to be overtaking Grunt in popularity. If you are not currently using gulp, give it a shot!
References
- [1] Chrome web store apps
https://chrome.google.com/webstore/category/apps - [2] Connect module
https://github.com/senchalabs/connect - [3] gaze module
https://github.com/shama/gaze - [4] gulp 4 GitHub repo
https://github.com/gulpjs/gulp/tree/4.0 - [5] gulp dest options
https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulpdestpath-options - [6] gulp main website
http://gulpjs.com - [7] gulp on Twitter - @gulpjs
- [8] gulp plugins
http://gulpjs.com/plugins - [9] gulp src options
https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulpsrcglobs-options - [10] gulp-livereload module
https://github.com/vohof/gulp-livereload - [11] Jasmine
http://jasmine.github.io/ - [12] JSCS
http://jscs.info/ - [13] my ES6 slides
https://objectcomputing.com/download_file/219 - [14] Node.js core Events API
https://nodejs.org/api/events.html - [15] node-glob module
https://github.com/isaacs/node-glob - [16] undertaker GitHub repo
https://github.com/phated/undertaker - [17] vinyl module
https://github.com/wearefractal/vinyl - [18] vinyl overview
https://medium.com/@contrahacks/gulp-3828e8126466 - [19] vinyl-fs module
https://github.com/wearefractal/vinyl-fs
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.