Gulp, LiveReload, SASS and WordPress

For a little while now, I’ve been using Gulp in my WordPress themes to automate my front-end workflow and add some handy helpers along the way. For those unaware, Gulp is a slick JavaScript task runner, which can be used to concatenate JS and CSS files, lint files, and generally automate your front-end workflow.

My goal was to write a short introduction to how this works. It ended up kind of long. But by the end you’ll be compiling SASS, minifying Javascript, working with livereload and implementing a SVG icon system. So not bad.

Setting Things Up

First off, gulp runs on top of NodeJS, and is generally operated from the command line. If you’re not set up with Node yet, I’d recommend following the steps on their website (or better yet, installing Homebrew).

With Node installed, you can install gulp globally on your system by opening up your Terminal and running:

npm install -g gulp

This will ensure that you can use the gulp command from the command line. In the root directory of your theme file, you will also need to install and set up a local version of gulp, and add a package.json file to manage your dependencies. This is basically just a config file that Node uses to figure out which packages it needs to bring down and install (things like a SASS concatenate or a JavaScript linter or any number of other tools). To create this file, just navigate to your theme’s directory in the terminal and run:

npm init

This will step you through the process of creating a package.json file. When that’s finished, the last step is to install gulp locally to your WordPress theme folder.

npm install gulp --save-dev

That last flag, --save-dev will automatically save this to a list of dependencies in your package.json file, so you can easily pass the directory around. But more on that later.

More info at CSS-Tricks.

Folder Structure

I just want to quickly note my folder structure. You’ll notice that a lot of my decisions in gulp are dependent on this, but they can easily be changed. My theme folder looks like this:

screen-of-files

 

In the root directory, we’ve already created the package.json file, and we’ll create the gulpfile.js in just a minute. The other important folder is assets which includes all of my front-end files.

  • assets/js/app – Contains all of my custom Javascript files, which gulp will combine
  • assets/js/vendor – Contains any vendor Javascript files, things like Bootstrap and Modernizr
  • assets/sass/style.scss – My main SASS file, which contains imports for all the other sass files
  • assets/sass/ – All the other SASS files
  • assets/svg/individual – Contains any SVG files I would like to use in my SVG icon system

Hopefully this will be a bit more clear as we step through.

From here, it’s just a matter of installing the packages we need, and creating a gulp task for each one.

Getting Started with SASS

Ok, let’s start out with the SASS setup. The first thing we’ll need to do is install gulp-sass, a library which will compile SASS down into plain old CSS.

npm install gulp-sass --save-dev

With that, we can finally start setting up Gulp. Create a new file in the root directory titled gulpfile.js and at the very top add:

Gulpfile.js
var gulp   	= require('gulp'),
    sass   	= require('gulp-sass');

Gulp uses the require syntax in order to import dependencies, so all we are doing here is ensuring that the gulp and gulp-sass library are loaded into variables.

Gulp works in what’s known as tasks. Each task performs a simple operation, passing files through and spitting them out in a different format on the other end. We’ll call our first task sass, which will handle the process of processing the style.scss file located in the assets/sass directory of our theme.

Gulpfile.js
gulp.task('sass', function () {
	return gulp.src('./assets/sass/style.scss')
			.pipe(sass.sync({outputStyle: 'compressed'}).on('error', sass.logError))
			.pipe(gulp.dest('./'));
});

Pretty simple actually. We give the initial task a source, and then use pipe, a built in connector for tasks to process sass. You’ll notice on line 3 that we’re actually calling the sass variable in order to process our file. Now, if we run gulp sass in the terminal, a new CSS file will be generated in the root directory of our theme. So far, so good.

Concatenating and Minifying Javascript

In a typical theme, I have two sets of Javascript files. The first are vendor files, and the other are my custom files. As seen above, I keep these in two separate folders helps to manage them better, and later on, I use gulp to combine them all together.

One thing I’ve run into over and over again is loading order for Javascript. If you’re using modules, you may not have to worry about this, but I’ve found that rather then trying weird naming conventions or separating files, I’ve opted to simply list out my Javascript files in the order I would like them to load. That way, gulp will process them one by one and spit them out into one big file. I know there are other methods, but this has worked best for me.

We’ll need to install a few other packages to get this up and running.

npm install gulp-concat gulp-uglify gulp-rename --save-dev

Gulp-concat will combine all of our Javascript files into one big file, gulp-ugilfy will minify them, compressing the file and saving us precious kilobytes, and gulp-rename will allow us to call this outputted file whatever we would like. Once again, at the top of our gulpfile we need to define the packages we just installed.

Gulpfile.js
var concat 	= require('gulp-concat'),
    uglify 	= require('gulp-uglify'),
   rename 	= require('gulp-rename');

Next, we will create a config variable that defines all of the Javascript files we want to concat. This variable will be used by gulp to process the files in order. If you simply want to include all the files from a directory, you can use the /**/*.js syntax, as we will do for our app folder.

Gulpfile.js
var config = {
	scripts: [
                // Bootstrap
		'./assets/js/vendor/bootstrap.js',
		// Modernizr
		'./assets/js/vendor/modernizr/modernizr.shiv.js',
		// SVG Fallback
		'./assets/js/vendor/svg/svg-fallback.js',
		// Any Custom Scripts
		'./assets/js/app/**/*.js'
	]
};

With that all set up, we can add a new task called scripts, which will run the files through concat and uglify and output the file:

Gulpfile.js
gulp.task('scripts', function() {
	return gulp.src(config.scripts)
			.pipe(concat('scripts.js'))
			.pipe(gulp.dest('./assets/js/'))
			.pipe(uglify())
			.pipe(rename({ extname: '.min.js' }))
			.pipe(gulp.dest('./assets/js/'));
});

This time for our source, we pass the variable we creaed, which has an array of the files we want to concat. This will output a file called scripts.js in our assets/js folder. Then uglify will compress this file even further, and output it as scripts.min.js. This will be our production-ready file. However, the scripts.js file can come in handy when debugging your theme.

If you want to see this in action, simply run gulp scripts.

SVG Icons

I won’t go too far into why I prefer SVG icons over any other systems, but if you want a lengthy introduction on how they work, CSS Tricks has a great tutorial. If you find yourself convinced, like I did, just make sure to save all of the individual SVG files you want to use in the assets/svg/individual folder. We will then use the gulp-svg-sprite and gulp-svg2png libraries to automatically create an SVG sprite and image fallbacks of our icons to easily use in our theme.

First step, install the two libraries we will need.

npm install gulp-svg-sprite gulp-svg2png --save-dev

Next, we’ll define our npm packages at the top of our gulpfile.

Gulpfile.js
var svgSprite 	= require("gulp-svg-sprite"),
    svg2png 	= require('gulp-svg2png');

Now it’s time to set up our SVG icon tasks. I’ve actually broken this up in my gulpfile into two tasks. The first creates the SVG sprite, which we can embed in our theme, and the second creates PNG fallbacks for our icons. The first we’ll call sprites:

Gulpfile.js
gulp.task('sprites', function () {
	return gulp.src('**/*.svg', {cwd: './assets/svg/individual'})
			.pipe(svgSprite({ shape: { transform: ['svgo'] }, mode: { defs: {dest: '.'} } } ) )
			.pipe(gulp.dest('./assets/svg/'));
});

As our source, we are simply looking in the ‘assets/svg/individual’ folder for anything with an .svg extension, and then processing it using the svg-sprite library. You will notice that I passed an svgo transformation, which will automatically optimize the SVG files. The final sprite will be saved to assets/svg as sprites.defs.svg. From there, you can follow the article linked above for how to include this in your theme.

Next up is the SVG fallbacks. For these fallbacks, I use a script from Luke Whitehouse. I had to modify it a bit to point to the proper URL (MyTheme + /assets/svg/png/”), but it works fairly well. You may also want to consider a library like SVG For Everybody. But in order for either method to work, they will need PNG images to fall back to. So we’ll create a gulp task that simply runs through our SVG files, converts them to PNG, and drops them in our assets/svg/png folder.

Gulpfile.js
gulp.task('svg2png', function () {
	return gulp.src('./assets/svg/individual/**/*.svg')
			.pipe(svg2png())
			.pipe(gulp.dest('./assets/svg/pngs/'));
});

This uses the gulp-svg2png library we installed to convert our SVG files on the fly.

Gulp tasks do not simply have to be one-off scripts. In fact, they can simply be an array of tasks to perform, in order. For our SVG workflow to be complete, we’ll want to combine the two tasks we created into a single task called icons. All we need to do is pass the names of these two tasks to a new one.

Gulpfile.js
gulp.task('icons', ['sprites', 'svg2png']);

Now, if you run gulp icons you will see this workflow in action.

Bonus Round: LiveReload

Livereload is an incredible library that can be used to automatically reload your site whenever changes are made. That way you don’t have to do the annoying Ctrl / CMD + R, check changes, switch to editor dance. And for CSS files, it will even inject styles on the fly, with no refresh needed. So you will see your styles change instantly.

The first step is to install the gulp-livereload library.

npm install gulp-livereload --save-dev

Next, we need to make sure that the livereload script is actually running on our site. H/T to Rob & Lauren for pointing me in the right direction with this one. We can add this to the functions.php file in our theme, automatically enqueueing the script on local environments.

functions.php
if (in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) {
  wp_register_script('livereload', 'http://localhost:35729/livereload.js?snipver=1', null, false, true);
  wp_enqueue_script('livereload');
}

 

With that added, we can go back our gulpfile.js file. At the top, make sure to define your dependency in a new variable.

Gulpfile.js
var livereload      = require('gulp-livereload');

Next we can add the livereload function to our SASS and JS tasks. This will call livereload at the appropriate times so that your development site is reloaded automatically in the browser whenever one of these tasks is called. So our SASS task will now be:

Gulpfile.js
gulp.task('sass', function () {
	return gulp.src('./assets/sass/style.scss')
			.pipe(sass.sync({outputStyle: 'compressed'}).on('error', sass.logError))
			.pipe(livereload())
			.pipe(gulp.dest('./'));
});

And our scripts task will now be:

Gulpfile.js
gulp.task('scripts', function() {
	return gulp.src(config.scripts)
			.pipe(concat('scripts.js'))
			.pipe(gulp.dest('./assets/js/'))
			.pipe(uglify())
			.pipe(rename({ extname: '.min.js' }))
			.pipe(livereload())
			.pipe(gulp.dest('./assets/js/'));
});

Wrapping It Up With Watch

The last thing we will want to do is use Gulp’s built-in watch feature to automatically watch for any changes to files and call the appropriate task when needed. So, if a SASS file is changed, the SASS task will be called, and if a JS file is changed then the scripts task will be called. We can also set up livereload here so that everything is wired up.

Gulpfile.js
gulp.task('watch', function () {
	livereload.listen(35729);
	gulp.watch('**/*.php').on('change', function(file) {
	      livereload.changed(file.path);
	  });
	gulp.watch('./assets/sass/**/*.scss', ['sass']);
	gulp.watch('./assets/js/**/*.js', ['scripts']);
});

 

Now all we have to do is add a default task which combines everything from before, calling each of our tasks and ending in watch.

Gulpfile.js
gulp.task('default', ['icons', 'sass', 'scripts', 'watch']);

And that’s that! All we have to do is run gulp and our tasks will be called in order and livereloading will begin. Try changing around some SASS or Javascript files and see what happens.

This is by no means an entirely exhaustive workflow, but it’s more then enough to get started. The trick is to understand how it works, and play around with it when you can. If you want to see what the full gulpfile looks like, well, here it is:

Full Gulpfile.js
var     gulp           = require('gulp'),
    concat         = require('gulp-concat'),
    uglify         = require('gulp-uglify'),
    rename         = require('gulp-rename'),
    sass           = require('gulp-sass'),
    livereload      = require('gulp-livereload'),
    svgSprite     = require("gulp-svg-sprite"),
    svg2png     = require('gulp-svg2png');

var config = {
    scripts: [
        './assets/js/vendor/bootstrap/bootstrap.min.js',
        // Modernizr
        './assets/js/vendor/modernizr/modernizr.shiv.js',
        // SVG Fallback
        './assets/js/vendor/svg/svg_fallback.js',
        // Any Custom Scripts
        './assets/js/app/**/*.js'
    ]
};

gulp.task('scripts', function() {
    return gulp.src(config.scripts)
            .pipe(concat('scripts.js'))
            .pipe(gulp.dest('./assets/js/'))
            .pipe(uglify())
            .pipe(rename({ extname: '.min.js' }))
            .pipe(livereload())
            .pipe(gulp.dest('./assets/js/'));
});

gulp.task('sass', function () {
    return gulp.src('./assets/sass/style.scss')
            .pipe(sass.sync({outputStyle: 'compressed'}).on('error', sass.logError))
            .pipe(livereload())
            .pipe(gulp.dest('./'));
});

gulp.task('sprites', function () {
    return gulp.src('**/*.svg', {cwd: './assets/svg/individual'})
            .pipe(svgSprite({ shape: { transform: ['svgo'] }, mode: { defs: {dest: '.'} } } ) )
            .pipe(gulp.dest('./assets/svg/'));
});

gulp.task('svg2png', function () {
    return gulp.src('./assets/svg/individual/**/*.svg')
            .pipe(svg2png())
            .pipe(gulp.dest('./assets/svg/pngs/'));
});

gulp.task('icons', ['sprites', 'svg2png']);

gulp.task('watch', function () {
    livereload.listen(35729);
    gulp.watch('**/*.php').on('change', function(file) {
          livereload.changed(file.path);
      });
    gulp.watch('./assets/sass/**/*.scss', ['sass']);
    gulp.watch('./assets/js/**/*.js', ['scripts']);
});

gulp.task('default', ['icons', 'sass', 'scripts', 'watch']);