Introduction to Gulp

Note: This article is last updated on February 09, 2020 with Gulp 4.0.2

Gulp is a cross-paltform task runner for web development. When working on a javascript projects, quite often we have to do some repetitive tasks such as minifying the css or javascript, concatenating them into one file to reduce the HTTP round trip between server to the client browser, optimizing images, running the tests etc. Depending on the size of the projects, managing those tasks can be very time consuming and tedious. But with the help of a task runner you can really automate this whole process and just focus on the application.

Installing Gulp

Gulp is based on Node.js. Which means you have to download and install node.js. Go ahead and do that if you haven't already. Now before installing gulp in your project you need to install the gulp command line utility. This article explains why you need to install gulp-cli.

$ npm install gulp-cli --global

Now we are ready to install gulp. The following commands will create a package.json file with default configs, install gulp as your development dependency, and display the versions:

$ npm init --yes
$ npm install gulp --save-dev
$ gulp --version

Gulp api's

Gulp exposes several api's to work. The most commons are:

src(globs, [options]): Accepts a globs which is a string array and returns a stream that produces Vinyl objects. Vinyl is a metadata object that describes a file.

dest(directory, [options]): The path of the output directory where files will be written.

series(...tasks): Combines task functions and/or composed operations into larger operations that will be executed one after another, in sequential order.

parallel(...tasks): Combines task functions and/or composed operations into larger operations that will be executed simultaneously.

watch(globs, [options], [task]): Allows watching globs and running a task when a change occurs.

Gulpfile

Create a gulpfile.js or Gulpfile.js file in root directory that automatically loads when you run the gulp command. The syntax for gulp is gulp <task> <othertask>. Any exported functions from gulpfile.js will be registered into gulp's task system. These functions are asynchronous that can return streams, promises, event emitters, child processes, or observables.

function defaultTask(cb) {
console.log("This is the default task.");
cb();
}
function otherTask(cb) {
console.log("Tell me what to do!");
cb();
}
exports.default = defaultTask;
exports.otherTask = otherTask;

Now if you run the following commands one by one; the first one will execute the defaultTask function or task beacuse it is exported as default and the second command will execute the otherTask task:

$ gulp
$ gulp otherTask

You can combines task functions and/or composed operations into larger operations using series() and parallel().

const { series, parallel } = require("gulp");
function css(done) {
// body omitted
done();
}
function javascript(done) {
// body omitted
done();
}
exports.sequentialBuild = series(javascript, css);
exports.simultaneousBuild = parallel(javascript, css);

Using Plugins

Gulp plugins are small utility library that encapsulate common functionality to transform files in a pipeline. You can use multiple plugins in a single task. Plugins are a great way to reuse functionality from different places in Gulpfile. Currently there are 3807 plugins available and the list is still growing. You can use plugins to do things like css minification, concatenating javascripts, running server etc. Lets look at some examples of such things using gulp:

Example #1: Sass compilation and css minification

$ npm install node-sass gulp-sass gulp-clean-css --save-dev
const { src, dest } = require("gulp");
const sass = require("gulp-sass");
const cleanCSS = require("gulp-clean-css");
sass.compiler = require("node-sass");
function css(done) {
src("./style.scss")
.pipe(sass())
.pipe(cleanCSS())
.pipe(dest("./output"));
done();
}
exports.css = css;

First we brought in the required libraries. Then, we created a task called style. Inside that task we told gulp what is the file path for our scss file using src api. Than we compile it down to css using the sass() method that gulp-sass plugin provides. After that we minified the compiled css file. Finally we specified the destination for our output file. You can see that we can chain methods together to describe our tasks.

Example #2: TypeScript to JavaScript compilation

$ npm install typescript gulp-typescript --save-dev
const { src, dest } = require("gulp");
const ts = require("gulp-typescript");
function typescript(done) {
src("./script.ts")
.pipe(ts())
.pipe(dest("./output"));
done();
}
exports.typescript = typescript;

Example #3: JavaScript minification

$ npm install gulp-uglify-es --save-dev
const { src, dest } = require("gulp");
const uglify = require("gulp-uglify-es").default;
function compress(done) {
src("./scripts.js")
.pipe(uglify())
.pipe(dest("./output"));
done();
}
exports.compress = compress;

Watching source files

You can also watch for changes so that you don’t have to manually run gulp each time your source files change. lets create a task for that:

const { watch } = require("gulp");
function css(done) {
// body omitted
done();
}
function javascript(done) {
// body omitted
done();
}
function watcher() {
watch("./style.css", css);
watch("./script.js", javascript);
}
exports.watcher = watcher;

Now simply running gulp watcher will watch for changes in the specified files and automatically trigger the corresponding task.

All together

Lets put all of the above examples together to create a final example.

First, download the necessary packages for this particular scenario:

$ npm install gulp node-sass typescript gulp-sass gulp-typescript gulp-rename gulp-clean-css gulp-uglify-es --save-dev

Lets edit the gulpfile.js:

const { src, dest, watch, parallel } = require("gulp");
const sass = require("gulp-sass");
const ts = require("gulp-typescript");
const rename = require("gulp-rename");
const cleanCSS = require("gulp-clean-css");
const uglify = require("gulp-uglify-es").default;
sass.compiler = require("node-sass");
function css(done) {
src("./style.scss")
.pipe(sass())
.pipe(rename("bundle.css"))
.pipe(dest("./output"))
.pipe(cleanCSS())
.pipe(rename("bundle.min.css"))
.pipe(dest("./output"));
done();
}
function typescript(done) {
src("./script.ts")
.pipe(ts())
.pipe(rename("bundle.js"))
.pipe(dest("./output"))
.pipe(uglify())
.pipe(rename("bundle.min.js"))
.pipe(dest("./output"));
done();
}
function watcher() {
watch("./style.css", css);
watch("./script.ts", typescript);
}
exports.watcher = watcher;
exports.build = parallel(css, typescript);
$ gulp watcher // listen for changes
$ gulp build // execute css and javascript tasks simultaneously

Wrap up

Easy, right? Although we've gone through only the basics of Gulp. Gulp can do much more than this and can be very complex or very simple depending on the project. Here are some important ponits to remember:

  • Each gulp task is an asynchronous JavaScript function. Synchronous tasks aren't supported, though there is a alternative.
  • Tasks can be considered public or private. Public tasks are exported from your gulpfile. Private tasks are made to be used internally.
  • If your gulpfile become too big, you can split each task into its own file. More on here.
  • You can write a gulpfile using TypeScript or Babel. For TypeScript, rename to gulpfile.ts and for Babel, rename to gulpfile.babel.js. More on here.