Developers spend precious little time coding. Even if we ignore irritating meetings, much of the job involves basic tasks which can sap your working day:
console and debugger statements from scriptsTasks must be repeated every time you make a change. You may start with good intentions, but the most infallible developer will forget to compress an image or two. Over time, pre-production tasks become increasingly arduous and time-consuming; you'll dread the inevitable content and template changes. It's mind-numbing, repetitive work. Wouldn’t it be better to spend your time on more profitable jobs?
If so, you need a task runner or build process.
Creating a build process will take time. It's more complex than performing each task manually, but over the long term, you’ll save hours of effort, reduce human error and save your sanity. Adopt a pragmatic approach:
Some of the tools and concepts may be new to you, but take a deep breath and concentrate on one thing at a time.
Build tools such as GNU Make have been available for decades, but web-specific task runners are a relatively new phenomenon. The first to achieve critical mass was Grunt — a Node.js task runner which used plugins controlled (originally) by a JSON configuration file. Grunt was hugely successful, but there were a number of issues:
Many issues were addressed in later editions, but Gulp had already arrived and offered a number of improvements:
Of course, Gulp itself isn't perfect, and new task runners such as Broccoli.js, Brunch and webpack have also been competing for developer attention. More recently, npm itself has been touted as a simpler option. All have their pros and cons, but Gulp remains the favorite and is currently used by more than 40% of web developers.
Gulp requires Node.js, but while some JavaScript knowledge is beneficial, developers from all web programming faiths will find it useful.
This tutorial describes how to use Gulp 3 — the most recent release version at the time of writing. Gulp 4 has been in development for some time but remains a beta product. It's possible to use or switch to Gulp 4, but I recommend sticking with version 3 until the final release.
Node.js can be downloaded for Windows, macOS and Linux from nodejs.org/download/. There are various options for installing from binaries, package managers and docker images, and full instructions are available.
Node.js and Gulp run on Windows, but some plugins may not install or run if they depend on native Linux binaries such as image compression libraries. One option for Windows 10 users is the new bash command-line, which solves many issues.
Once installed, open a command prompt and enter:
node -v
This reveals the version number. You're about to make heavy use of npm — the Node.js package manager which is used to install modules. Examine its version number:
npm -v
Node.js modules can be installed globally so they’re available throughout your system. However, most users will not have permission to write to the global directories unless npm commands are prefixed with sudo. There are a number of options to fix npm permissions and tools such as nvm can help, but I often change the default directory. For example, on Ubuntu/Debian-based platforms:
cd ~
mkdir .node_modules_global
npm config set prefix=$HOME/.node_modules_global
npm install npm -g
Then add the following line to the end of ~/.bashrc:
export PATH="$HOME/.node_modules_global/bin:$PATH"
Finally, update with this:
source ~/.bashrc
Install Gulp command-line interface globally so the gulp command can be run from any project folder:
npm install gulp-cli -g
Verify Gulp has installed with this:
gulp -v
You can skip this step if you already have a package.json configuration file.
Note for Node.js projects: you can skip this step if you already have a package.json configuration file.
Presume you have a new or pre-existing project in the folder project1. Navigate to this folder and initialize it with npm:
cd project1
npm init
You’ll be asked a series of questions. Enter a value or hit Return to accept defaults. A package.json file will be created on completion which stores your npm configuration settings.
Node.js installs modules to a node_modules folder. You should add this to your .gitignore file to ensure they’re not committed to your repository. When deploying the project to another PC, you can run npm install to restore them.
For the remainder of this article, we'll presume your project folder contains the following sub-folders:
src folder: preprocessed source files
This contains further sub-folders:
html - HTML source files and templatesimages — the original uncompressed imagesjs — multiple preprocessed script filesscss — multiple preprocessed Sass .scss filesbuild folder: compiled/processed files
Gulp will create files and create sub-folders as necessary:
html — compiled static HTML filesimages — compressed imagesjs — a single concatenated and minified JavaScript filecss — a single compiled and minified CSS fileYour project will almost certainly be different but this structure is used for the examples below.
Tip: If you're on a Unix-based system and you just want to follow along with the tutorial, you can recreate the folder structure with the following command:
mkdir -p src/{html,images,js,scss} build/{html,images,js,css}
Tip: If you're on a Unix-based system and you just want to follow along with the tutorial, you can recreate the folder structure with the following command:
mkdir -p src/{html,images,js,scss} build/{html,images,js,css}
You can now install Gulp in your project folder using the command:
npm install gulp --save-dev
This installs Gulp as a development dependency and the "devDependencies" section of package.json is updated accordingly. We’ll presume Gulp and all plugins are development dependencies for the remainder of this tutorial.
Development dependencies are not installed when the NODE_ENV environment variable is set to production on your operating system. You would normally do this on your live server with the Mac/Linux command:
export NODE_ENV=production
Or on Windows:
set NODE_ENV=production
This tutorial presumes your assets will be compiled to the build folder and committed to your Git repository or uploaded directly to the server. However, it may be preferable to build assets on the live server if you want to change the way they are created. For example, HTML, CSS and JavaScript files are minified on production but not development environments. In that case, use the --save option for Gulp and all plugins, i.e.
npm install gulp --save
This sets Gulp as an application dependency in the "dependencies" section of package.json. It will be installed when you enter npm install and can be run wherever the project is deployed. You can remove the build folder from your repository since the files can be created on any platform when required.
Create a new gulpfile.js configuration file in the root of your project folder. Add some basic code to get started:
// Gulp.js configuration
var
// modules
gulp = require('gulp'),
// development mode?
devBuild = (process.env.NODE_ENV !== 'production'),
// folders
folder = {
src: 'src/',
build: 'build/'
}
;
This references the Gulp module, sets a devBuild variable to true when running in development (or non-production mode) and defines the source and build folder locations.
ES5-compatible JavaScript code is provided in this tutorial. This will work for all versions of Gulp and Node.js with or without the --harmony flag. Most ES6 features are supported in Node 6 and above so feel free to use arrow functions, let, const, etc. if you're using a recent version.
gulpfile.js won't do anything yet because you need to …
On its own, Gulp does nothing. You must:
It's possible to write your own plugins but, since almost 3,000 are available, it's unlikely you'll ever need to. You can search using Gulp's own directory at gulpjs.com/plugins/, on npmjs.com, or search "gulp something" to harness the mighty power of Google.
Gulp provides three primary task methods:
gulp.task — defines a new task with a name, optional array of dependencies and a function.gulp.src — sets the folder where source files are located.gulp.dest — sets the destination folder where build files will be placed.Any number of plugin calls are set with pipe between the .src and .dest.
This is best demonstrated with an example, so let's create a basic task which compresses images and copies them to the appropriate build folder. Since this process could take time, we'll only compress new and modified files. Two plugins can help us: gulp-newer and gulp-imagemin. Install them from the command-line:
npm install gulp-newer gulp-imagemin --save-dev
We can now reference both modules the top of gulpfile.js:
// Gulp.js configuration
var
// modules
gulp = require('gulp'),
newer = require('gulp-newer'),
imagemin = require('gulp-imagemin'),
We can now define the image processing task itself as a function at the end of gulpfile.js:
// image processing
gulp.task('images', function() {
var out = folder.build + 'images/';
return gulp.src(folder.src + 'images/**/*')
.pipe(newer(out))
.pipe(imagemin({ optimizationLevel: 5 }))
.pipe(gulp.dest(out));
});
All tasks are syntactically similar. This code:
images.out folder where build files will be located.src source folder. The /**/* ensures that images in sub-folders are also processed.gulp-newer module. Source files that are newer than corresponding destination files are passed through. Everything else is removed.gulp-imagemin which sets an optional optimizationLevel argument.dest folder set by out.Save gulpfile.js and place a few images in your project's src/images folder before running the task from the command line:
gulp images
All images are compressed accordingly and you’ll see output such as:
Using file gulpfile.js
Running 'imagemin'...
Finished 'imagemin' in 5.71 ms
gulp-imagemin: image1.png (saved 48.7 kB)
gulp-imagemin: image2.jpg (saved 36.2 kB)
gulp-imagemin: image3.svg (saved 12.8 kB)
Try running gulp images again and nothing should happen because no newer images exist.
We can now create a similar task which copies files from the source HTML folder. We can safely minify our HTML code to remove unnecessary whitespace and attributes using the gulp-htmlclean plugin:
npm install gulp-htmlclean --save-dev
This is then referenced at the top of gulpfile.js:
var
// modules
gulp = require('gulp'),
newer = require('gulp-newer'),
imagemin = require('gulp-imagemin'),
htmlclean = require('gulp-htmlclean'),
We can now create an html task at the end of gulpfile.js:
// HTML processing
gulp.task('html', ['images'], function() {
var
out = folder.build + 'html/',
page = gulp.src(folder.src + 'html/**/*')
.pipe(newer(out));
// minify production code
if (!devBuild) {
page = page.pipe(htmlclean());
}
return page.pipe(gulp.dest(out));
});
This reuses gulp-newer and introduces a couple of concepts:
[images] argument states that our images task must be run before processing the HTML (the HTML is likely to reference images). Any number of dependent tasks can be listed in this array and all will complete before the task function runs.gulp-htmlclean if NODE_ENV is set to production. Therefore, the HTML remains uncompressed during development which may be useful for debugging.Save gulpfile.js and run gulp html from the command line. Both the html and images tasks will run.
Too easy for you? Let's process all our JavaScript files by building a basic module bundler. It will:
// requires: defaults.js lib.js.main.js file using gulp-concat.console and debugging statements with gulp-strip-debug and minimize code with gulp-uglify. This step will only occur when running in production mode.Install the plugins:
npm install gulp-deporder gulp-concat gulp-strip-debug gulp-uglify --save-dev
Reference them at the top of gulpfile.js:
var
...
concat = require('gulp-concat'),
deporder = require('gulp-deporder'),
stripdebug = require('gulp-strip-debug'),
uglify = require('gulp-uglify'),
Then add a new js task:
// JavaScript processing
gulp.task('js', function() {
var jsbuild = gulp.src(folder.src + 'js/**/*')
.pipe(deporder())
.pipe(concat('main.js'));
if (!devBuild) {
jsbuild = jsbuild
.pipe(stripdebug())
.pipe(uglify());
}
return jsbuild.pipe(gulp.dest(folder.build + 'js/'));
});
Save then run gulp js to watch the magic happen!
Finally, let’s create a CSS task which compiles Sass .scss files to a single .css file using gulp-sass. This is a Gulp plugin for node-sass which binds to the superfast LibSass C/C++ port of the Sass engine (you won't need to install Ruby). We'll presume your primary Sass file scss/main.scss is responsible for loading all partials.
Our task will also utilize the fabulous PostCSS via the gulp-postcss plugin. PostCSS requires its own set of plugins and we'll install these:
background: resolve('image.png'); to resolve file paths or background: inline('image.png'); to inline data-encoded images.First, install all the modules:
npm install gulp-sass gulp-postcss postcss-assets autoprefixer css-mqpacker cssnano --save-dev
Then reference them at the top of gulpfile.js:
var
...
sass = require('gulp-sass'),
postcss = require('gulp-postcss'),
assets = require('postcss-assets'),
autoprefixer = require('autoprefixer'),
mqpacker = require('css-mqpacker'),
cssnano = require('cssnano'),
We can now create a new css task at the end of gulpfile.js. Note the images task is set as a dependency because the postcss-assets plugin can reference images during the build process. In addition, most plugins can be passed arguments (refer to their documentation for more information):
// CSS processing
gulp.task('css', ['images'], function() {
var postCssOpts = [
assets({ loadPaths: ['images/'] }),
autoprefixer({ browsers: ['last 2 versions', '> 2%'] }),
mqpacker
];
if (!devBuild) {
postCssOpts.push(cssnano);
}
return gulp.src(folder.src + 'scss/main.scss')
.pipe(sass({
outputStyle: 'nested',
imagePath: 'images/',
precision: 3,
errLogToConsole: true
}))
.pipe(postcss(postCssOpts))
.pipe(gulp.dest(folder.build + 'css/'));
});
Save the file and run the task from the command line:
gulp css
We've been running one task at a time. We can run them all in one command by adding a new run task to gulpfile.js:
// run all tasks
gulp.task('run', ['html', 'css', 'js']);
Save and enter gulp run at the command line to execute all tasks. Note that I omitted the images task because it's already set as a dependency for the html and css tasks.
Is this still too much hard work? Gulp offers another method — gulp.watch — which can monitor your source files and run an appropriate task whenever a file is changed. The method is passed a folder and a list of tasks to execute when a change occurs. Let's create a new watch task at the end of gulpfile.js:
// watch for changes
gulp.task('watch', function() {
// image changes
gulp.watch(folder.src + 'images/**/*', ['images']);
// html changes
gulp.watch(folder.src + 'html/**/*', ['html']);
// javascript changes
gulp.watch(folder.src + 'js/**/*', ['js']);
// css changes
gulp.watch(folder.src + 'scss/**/*', ['css']);
});
Rather than running gulp watch immediately, let's add a default task:
// default task
gulp.task('default', ['run', 'watch']);
Save gulpfile.js and enter gulp at the command line. Your images, HTML, CSS and JavaScript will all be processed, then Gulp will remain active watching for updates and re-running tasks as necessary. Hit Ctrl/Cmd + C to abort monitoring and return to the command line.
Other plugins you may find useful:
require declarationsOne useful method in gulp-util is .noop() which passes data straight through without performing any action. This could be used for cleaner development/production processing code. For example:
var gutil = require('gulp-util');
// HTML processing
gulp.task('html', ['images'], function() {
var out = folder.src + 'html/**/*';
return gulp.src(folder.src + 'html/**/*')
.pipe(newer(out))
.pipe(devBuild ? gutil.noop() : htmlclean())
.pipe(gulp.dest(out));
});
Gulp can also call other Node.js modules, and they don't necessarily need to be plugins. For example:
build folder at the start of every run).Invest a little time and Gulp could save many hours of development frustration. The advantages:
gulpfile.js can be adapted and reused in other projectsUseful links:
Applying the processes above to a simple website reduced the total weight by more than 50%. You can test your own results using page weight analysis tools or a service such as New Relic, which provides a range of sophisticated application performance monitoring tools.
Gulp can revolutionize your workflow. I hope you found this tutorial useful and consider Gulp for your production process.