Published on 2017 - 01 - 05

Development always starts off in a simple way. You come up with a great idea and then plan out how to build it. Quickly, you scaffold your project structure and organize everything to perfection. As you progress, your small idea starts to grow into a much larger application. You soon realize that your project has become heavy and bloated, and to remedy this, you perform a series of little mundane operations each time you modify your code to keep it small and efficient. Suddenly, all of these repetitive tasks seem to pull you down at the height of your coding victory! You tell yourself that there must be a better way.

The good news is that you are absolutely right. The solution to this development obstacle lies in utilizing build systems. Build systems are some of the most valuable tools in a developer's toolbox, and if you've never used one before, you're soon going to wonder how you ever worked without one.

In software development, build systems such as Make were initially used to compile code into executable formats for use in an operating system. However, in web development, we have a completely different set of practices and operations to contend with. Over the past few years, the growth of the Web has led to an increasing interest in using build systems to more capably handle the growing complexities of our applications and project workflows.

As developers, it is important for us to anticipate these growing complexities. We must do all that we can to improve our workflows so that we can build efficient projects that allow us to focus on what we do best: write great code.

What is gulp?

Gulp is a streaming JavaScript build system built with node.js; it leverages the power of streams and code-over-configuration to automate, organize, and run development tasks very quickly and efficiently. By simply creating a small file of instructions, gulp can perform just about any development task you can think of.

Gulp uses small, single-purpose plugins to modify and process your project files. Additionally, you can chain, or pipe, these plugins together into more complex actions with full control of the order in which those actions take place.

Gulp isn't alone though; it is built upon two of the most powerful tools available in the development industry today: node.js and npm. These tools help gulp perform and organize all of the wonderful things that it empowers us to do.

Installing Gulp Globally

To install gulp globally, use the following command:

npm install -g gulp

In this command, We've added a -g flag to the command, which instructs npm to install the package globally.

Anatomy of a gulpfile

Gulp started with four main methods:.task(), .src(), .watch(), and .dest(). The release of version 4.0 introduced additional methods such as: .series() and .parallel(). In addition to the gulp API methods, each task will also make use of the node.js .pipe()method. This small list of methods is all that is needed to understand how to begin writing basic tasks. They each represent a specific purpose and will act as the building blocks of our gulpfile.

The task() method

The task() method is the basic wrapper for which we create our tasks. Its syntax is .task(string, function). It takes two arguments: a string value representing the name of the task and a function that will contain the code you wish to execute upon running that task.

The src() method

The src() method is our input, or how we gain access to the source files that we plan on modifying. It accepts either a single string or an array of strings as an argument. The syntax is .src(string || array).

The watch() method

The watch() method is used to specifically look for changes in our files. This will allow us to keep gulp running as we code so that we don't need to rerun gulp any time we need to process our tasks. The syntax for this method is .watch(string, array).

The dest() method

The dest() method is used to set the output destination of your processed file. Most often, this will be used to output our data into a build or dist folder that will be either shared as a library or accessed by your application. The syntax for this method is .dest(string).

The pipe() method

The pipe() method will allow us to pipe together smaller single-purpose plugins or applications into a pipechain. This is what gives us full control of the order in which we would need to process our files. The syntax for this method is .pipe(function).

The parallel() and series() methods

The parallel() and series() methods were added in version 4.0 as a way to easily control whether your tasks are ran together - all at once, or in a sequence - one after the other. This is important if one of your tasks requires that other tasks complete before it can be ran successfully.

Next, we will need to put these methods together and explain how they all interact with one another to create a gulp task.

Including modules/plugins

When writing a gulpfile, you will always start by including the modules or plugins you are going to use in your tasks. These can be both gulp plugins or node.js modules, based on what your needs are. Gulp plugins are small node.js applications built for use inside of gulp to provide a single-purpose action that can be chained together to create complex operations for your data. Node.js modules serve a broader purpose and can be used with gulp or independently.

Just remember that you must install each plugin or module using npm prior to including them in your gulpfile. The following is an example of what this code will look like:

// Load Node Modules/Plugins
var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');

In this code, we have included gulp and two gulp plugins: gulp-concat and gulp-uglify. As you can now see, including a plugin into your gulpfile is quite easy. After we install each module or plugin using npm, you simply use node.js' require() function and pass it in the name of the module. You then assign it to a new variable so you can use it throughout your gulpfile.

This is node.js' way of handling modularity, and because a gulpfile is essentially a small node.js application, it adopts this practice as well.

Writing a task

All tasks in gulp share a common structure. Having reviewed the five methods at the beginning of this section, you will already be familiar with most of it. Some tasks might end up being larger than others, but they still follow the same pattern. To better illustrate how they work, let's examine a bare skeleton of a task. This skeleton is the basic "bone structure" of each task we will be creating. Studying this structure will make it incredibly simple to understand how parts of gulp work together to create a task. An example of a sample task is as follows:

gulp.task(name, function() {
  return gulp.src(path)

In the first line, we use the new gulp variable that we created a moment ago and access the .task() method. This creates a new task in our gulpfile. As you learned earlier, the task method accepts two arguments: a task name as a string and a callback function that will contain the actions we wish to run when this task is executed.

Inside the callback function, we refer to the gulp variable once more and then use the .src() method to provide the input to our task. As you learned earlier, the source method accepts a path or an array of paths to the files that we wish to process.

Next, we have a series of three .pipe() methods. In each of these pipe methods, we will specify which plugin we would like to use. This grouping of pipes is what we call our pipechain.

The data that we have provided gulp with in our source method will flow through our pipechain to be modified by each piped plugin that it passes through. The order of the pipe methods is entirely up to you. This gives you a great deal of control in how and when your data is modified.

You may have noticed that the final pipe is a bit different. At the end of our pipechain, we have to tell gulp to move our modified file somewhere. This is where the .dest() method comes into play. As we mentioned earlier, the destination method accepts a path that sets the destination of the processed file as it reaches the end of our pipechain. If .src() is our input, then .dest() is our output.