Workflow & Tools

Gulp Configuration: A Beginners Guide

Recently, WebDevStudios integrated Gulp into our theme boilerplate, wd_s. Below, I go through the Gulpfile.js, section by section, in hopes that you can benefit from our mistakes experience.

Terminology

Alright, newbie, before we dive in, let’s go over a few definitions:

Gulp – A streaming build system. Sometimes called a “task runner,” which is built on Node. With the right combination of plugins (aka “dependencies”), Gulp will handle otherwise tedious tasks only fit for an intern, like: compiling Sass or Less, minifying JavaScript, optimizing images, etc.

NodeNode.js (aka “Node”), is a JavaScript runtime environment. This is the engine that runs Gulp and its plugins.

NPM – The Node Package Manager (NPM), reads a JSON formatted configuration file (package.json) and downloads any required dependencies into a folder named “node_modules.”

.stream() – Typically, Gulp will pass (but never cross) a “stream” of files through plugins, which transforms the files in some way. Streams are handled by Node.

.pipe() – If you’re familiar with the command line, you may already know about the vertical bar | (aka “pipe”). This dohicky is used to pass the output of one command to the input of another. The same thing is accomplished via the .pipe() method. This occurs in the stream.

.task() – Defines a task which executes either an anonymous or callback function.

.src() – Creates a stream of files.

.dest() – Writes the stream to a file on a hard disk.

One Config File vs Multiple Config Files

While putting our Gulpfile.js together, there was talk about splitting the tasks into multiple files. After careful consideration, we decided to place everything into one file, and then document the heck out of it.

We felt like a single file would help fellow beginners understand the inner workings of Gulp. Gulpfile.js is written pure JavaScript, and we often joke about there being “twenty-six ways to do the same thing.” While humorous, that statement is absolutely true! If you spend any time on Google or Github looking at other’s Gulp configuration, you might scratch your head and wonder, “Why did this person write it that way?” or “Wow! I didn’t know I could write it that way…”

I promise you, there isn’t really a right or wrong way to set up Gulpfile.js. Do what makes you comfortable. If your configuration works and is executing those tedious tasks? Then you’ve done it right. As you feel more comfortable with the config, you can always go back and iterate.

In wd_s, all Gulp related configuration is contained in a single Gulpfile.js.

Adding Dependencies

Dependencies? Plugins? Which one is it?

Items that are going to be downloaded via NPM are “dependencies” and are defined in package.json. A snippet from our package.json file:

{
  "name": "wd_s",
  "version": "1.0.0",
  "description": "A theme boilerplate for WebDevStudios.",
  "main": "Gulpfile.js",
  "dependencies": {
    "gulp": "^3.9.1",
    "gulp-cssnano": "^2.1.0",    
    "gulp-sass": "^2.2.0",
  },
}

You would then type the following command into your terminal:

npm install

This instructs NPM to go fetch Gulp, CSSNano and Sass… NPM will place the files into a folder named, node_modules.

In the context of Gulp, those dependencies are called “plugins”. In the header of Gulpfile.js, we will define the plugins we just installed using NPM:

// Define some plugins!
var gulp = require('gulp');
var sass = require('gulp-sass');
var cssnano = require('gulp-cssnano');

What’s going on here? We’ve set a variable for each dependency, which tells Gulp we want to use each of those as plugins. Gulp already knows to go look inside node_modules, so there isn’t a configuration setting necessary for that.

If you’ve made it this far? Awesome, sparky. We’re almost done!

Our First Task

Now that we’ve defined our plugins, lets run a task (which are sometimes called “recipes”) to compile that Sass. In Gulpfile.js, we’ll write:

/**
 * Compile Sass.
 */
gulp.task('sass', function() {
    return gulp.src('./sass/*.scss') // Create a stream in the directory where our Sass files are located.
    .pipe(sass())                    // Compile Sass into style.css.
    .pipe(gulp.dest('./'));          // Write style.css to the project's root directory.
});

Boom! A very simple task which can be used to compile Sass and create style.css! The code above is simply an anonymous JavaScript function wrapped in gulp.task(). (Check out the gulp.task() API for more)

To execute [‘sass’], simply type the following command into your terminal and press enter:

gulp sass

Man, we’re doing great, huh? But what a pain-in-the-butt to have to type that command every time we make a change. Did you know Gulp can automatically compile Sass each time we save a Sass file? Check this recipe out:

/**
 * Watch the Sass directory for changes.
 */
gulp.task('watch', function() {
  gulp.watch('./sass/*.scss', ['sass']);  // If a file changes, re-run 'sass'
});

In your terminal, simply type the following (instead of gulp sass) and press enter:

gulp watch

Now, anytime you make changes and save any file with a .scss extension, it will trigger this [‘watch’] task which executes [‘sass’]. We sure are rock stars, huh?

Daisy Chaining Tasks

I gotta admit, it’s pretty sweet that we can compile Sass, but what if we want to minify it too?

Because JavaScript is single-threaded, we can run tasks concurrently (aka daisy chain) and they will execute one after another. Let’s chain a couple tasks together…we need to create a secondary task to minify our compiled stylesheet, style.css:

/**
 * Minify stylesheet.
 */
gulp.task('minify', ['sass'], function() {
    return gulp.src('style.css');  // Grab style.css and add it to the stream.
    .pipe(cssnano())               // Minify and optimize style.css
    .pipe(gulp.dest('./'));        // Write style.css to the project's root directory.
});

Do you notice anything different about this task? Before the anonymous function starts, we’re inserting an array with the word [‘sass’] wrapped in single quotations. This tells gulp.task() to execute [‘sass’] before running [‘minify’].

This is fantastic! However, we’re still having to type something each time we make a change…and because I’m lazy, that goes against everything I stand for.

How about we refactor our [‘watch’] task. Then, we can be super lazy; we only have to type one command to kick-off a cascading event of mind blowing compilation proportions:

/**
 * Watch the Sass directory for changes.
 */
gulp.task('watch', function() {
  gulp.watch('./sass/*.scss', ['minify']);  // If a file changes, re-run 'sass' and then 'minify'
});

Gulp is now watching our files, and whenever we make a change…it will start a chain reaction which will create compiled a stylesheet along with a minified version too! #MindBlown

Alright champ. I’ve shown you how to do some cool things with Gulp. Hopefully, I’ve helped you feel confident enough to make your own Gulpfile.js. Now, go forth and use Gulp to help you be just as lazy as we are! Thanks and good luck!

..

.

Ok. I can see you’re not an average developer. You seem to be a glutton for punishment and have the stomach for some advanced stuff. Proceed with caution…

Advanced Example

Below is an example Gulpfile.js, which will put our Sass through a gauntlet of plugins:

  • If there are no errors
    • Lint to ensure code standards are followed
    • Autoprefix
    • Combine similar media queries
    • Compile style.css
    • Optimize
    • Minify
    • Create style.min.css
    • Delete pre-existing compiled files
    • Refresh the web browser when finished

What you’re about to see is nothing more than a few concurrent tasks:

// Define some plugins!
var autoprefixer = require('autoprefixer');
var bourbon = require('bourbon').includePaths;
var browsersync = require('browser-sync');
var cssnano = require('gulp-cssnano');
var del = require('del');
var gulp = require('gulp');
var gutil = require('gulp-util');
var mqpacker = require('css-mqpacker');
var neat = require('bourbon-neat').includePaths;
var notify = require('gulp-notify');
var plumber = require('gulp-plumber');
var postcss = require('gulp-postcss');
var rename = require('gulp-rename');
var sass = require('gulp-sass');
var sasslint = require('gulp-sass-lint');
var sourcemaps = require('gulp-sourcemaps');

// Set asset paths.
var paths = {
 css: ['./*.css', '!*.min.css'],
 sass: 'assets/sass/**/*.scss',
};

/**
 * Run Sass through a linter.
 */
gulp.task('sass:lint', ['cssnano'], function() {
 gulp.src([
  'assets/sass/**/*.scss',
  '!assets/sass/base/_normalize.scss',
  '!assets/sass/utilities/animate/**/*.*',
  '!assets/sass/base/_sprites.scss'
 ])
 .pipe(sasslint())
 .pipe(sasslint.format())
 .pipe(sasslint.failOnError());
});

/**
 * Minify and optimize style.css.
 */
gulp.task('cssnano', ['postcss'], function() {
 return gulp.src('style.css')
 .pipe(plumber({ errorHandler: handleErrors }))
 .pipe(cssnano({ safe: true }))
 .pipe(rename('style.min.css'))
 .pipe(gulp.dest('./')));
});

/**
 * Compile Sass and run stylesheet through PostCSS.
 */
gulp.task('postcss', ['clean:styles'], function() {
 return gulp.src('assets/sass/*.scss', paths.css)
 .pipe(plumber({ errorHandler: handleErrors }))
 .pipe(sourcemaps.init())
 .pipe(sass({
   includePaths: [].concat(bourbon, neat),
   errLogToConsole: true,
   outputStyle: 'expanded'
 }))
 .pipe(postcss([
   autoprefixer({ browsers: ['last 2 version'] }),
   mqpacker({ sort: true }),
 ]))
 .pipe(sourcemaps.write())
 .pipe(gulp.dest('./'))
 .pipe(browserSync.stream());
});

/**
 * Clean compiled files.
 */
gulp.task('clean:styles', function() {
 return del(['style.css', 'style.min.css'])
});

/**
 * Process tasks and reload browsers.
 */
gulp.task('watch', function() {
 var files = [
   paths.sass
 ];
 browserSync.init( files, {
   open: false,
   proxy: "foo-client.dev",
   watchOptions: { debounceDelay: 1000 }
 });
 gulp.watch(paths.sass, ['styles']);
});

/**
 * Handle errors.
 */
function handleErrors() {
 var args = Array.prototype.slice.call(arguments);
 notify.onError({
   title: 'Task Failed [<%= error.message %>',
   message: 'See console.',
   sound: 'Sosumi'
 }).apply(this, args);
 gutil.beep();
 this.emit('end');
}

In the terminal, we type:

gulp watch

And we’re off creating a theme for our client. If you feel like there’s a lot to take in, don’t worry. There are really only four tasks daisy-chained together, a watch task, and a helper function which deals with errors.

Let me break it down:

In the first task, we’re running all of our Sass against both WordPress and WDS coding standards with a Sass Linter. This ensures that everyone on the project is writing excellent Sass.

/**
 * Run Sass through a linter.
 */
gulp.task('sass:lint', ['cssnano'], function() {
 gulp.src([
  'assets/sass/**/*.scss',                  // Include all files that end with .scss
  '!assets/sass/base/_normalize.scss',      // Ignore _normalize.scss
  '!assets/sass/utilities/animate/**/*.*',  // Ignore any animations
  '!assets/sass/base/_sprites.scss'         // Ignore _sprites.scss
 ])
 .pipe(sasslint())
 .pipe(sasslint.format()) 
 .pipe(sasslint.failOnError());
});

[‘sass:lint’] is daisy chained with [‘cssnano’] which optimizes, minifies, and creates a production ready, style.min.css

/**
 * Minify and optimize style.css.
 */
gulp.task('cssnano', ['postcss'], function() {
 return gulp.src('style.css')
 
  // Deal with errors.
 .pipe(plumber({ errorHandler: handleErrors }))
 
 // Optimize and minify style.css.
 .pipe(cssnano({
    safe: true // Use safe optimizations.
 }))

 // Rename style.css to style.min.css.
 .pipe(rename('style.min.css')) 

 // Write style.min.css.
 .pipe(gulp.dest('./'))
 
 // Stream to BrowserSync.
 .pipe(browserSync.stream());
});

[‘cssnano’] is daisy chained with [‘postcss’], which compiles, auto prefixes, and combines all media queries into style.css

/**
 * Compile Sass and run stylesheet through PostCSS.
 */
gulp.task('postcss', ['clean:styles'], function() {
 return gulp.src('assets/sass/*.scss', paths.css)

 // Deal with errors.
 .pipe(plumber({ errorHandler: handleErrors }))

 // Wrap tasks in a sourcemap.
 .pipe(sourcemaps.init())

 // Compile Sass using LibSass.
 .pipe(sass({
   includePaths: [].concat(bourbon, neat), // Include Bourbon & Neat
   errLogToConsole: true, // Log errors.
   outputStyle: 'expanded' // Options: nested, expanded, compact, compressed
 }))

 // Parse with PostCSS plugins.
 .pipe(postcss([

  // Only prefix the last two browsers.
  autoprefixer({
     browsers: ['last 2 version'] 
   }),

  // Combine media queries.
  mqpacker({
     sort: true 
   }),
 ]))

 // Create sourcemap.
 .pipe(sourcemaps.write())

 // Create style.css.
 .pipe(gulp.dest('./'))

 // Stream to BrowserSync.
 .pipe(browserSync.stream());
});

But first we’re going to “clean” our compiled files by deleting them with [‘clean:styles’]

/**
 * Clean compiled files.
 */
gulp.task('clean:styles', function() {
 return del(['style.css', 'style.min.css']) // Delete style.css and style.min.css from the hard disk.
});

And finally, with [‘watch’] we’re using BrowserSync to view our changes instantly in the web browser after everything has successfully run:

/**
 * Process tasks and reload browsers on file changes.
 */
gulp.task('watch', function() {

 // Files to watch.
 var files = [
   paths.sass
 ];

 // Kick off BrowserSync.
 browserSync.init( files, {
   open: false,             // Don't open a new tab on init.
   proxy: "foo-client.dev", // The URL of our local project.
   watchOptions: {
     debounceDelay: 1000    // Wait one second before refreshing.
   }
 });

 // Run tasks when files change.
 gulp.watch(paths.sass, ['styles']);
});

If your head is spinning, I can assure you so was ours!

But if you look at the inline documentation and comments; you’ll see that it’s actually a beautiful symphony of plugins working together in harmony, which ultimately make our workflow fast and efficient. (All the code above, executes in a few hundred milliseconds! ERMAHGERD!)

This enables the developers at WebDevStudios to type one command, and then spend all day focused on theming–with zero time lost doing any of those “tedious tasks” by hand.

Conclusion

As you can see, Gulp can be a very powerful tool, but it’s only as powerful as you make it! Because it’s written in JavaScript, there are many ways to accomplish the same thing. So remember–use Gulp the way that is most comfortable for you.

If large configuration files spread across multiple folders is a bit intimidating…then I would encourage you to check out the full Gulpfile.js in wd_s and then experiment with our setup. Maybe one day soon, you’ll be as excited as we are about using Gulp as a build tool. Thanks for reading!

Official Gulp Links:

Comments

1 thought on “Gulp Configuration: A Beginners Guide

  1. Really helpful tutorial– one question in regards to your example Gulp file though. If I understand correctly, the second arg you pass to the `Gulp.watch()` call (within the `watch` task) is an array of tasks to run on change. You have that set as `[‘styles’]`, yet a close reading of the rest of the file reveals no task named `styles`. (I see a `clean:styles` tasks, but otherwise nada!)

    So I guess I’m wonder how the F that works! Maybe it’s magic? or maybe I’m just missing something?

    Good intro nonetheless!

Have a comment?

Your email address will not be published. Required fields are marked *

accessibilityadminaggregationanchorarrow-rightattach-iconbackupsblogbookmarksbuddypresscachingcalendarcaret-downcartunifiedcouponcrediblecredit-cardcustommigrationdesigndevecomfriendsgallerygoodgroupsgrowthhostingideasinternationalizationiphoneloyaltymailmaphealthmessagingArtboard 1migrationsmultiple-sourcesmultisitenewsnotificationsperformancephonepluginprofilesresearcharrowscalablescrapingsecuresecureseosharearrowarrowsourcestreamsupporttwitchunifiedupdatesvaultwebsitewordpress