Skip To Content

WordPress Theme Development With Gulp, Bower, and Sass

February 2015 Update: this post is now way out of date as the starter kit has evolved. I’m working on a new post to explain all the developments; in the meantime please check out the original repo on GitHub.

September 2017 Update: don’t spend any time reading this post; it’s essentially obsolete at this point. I’ll try and find some time to update this as a lot has changed since it was first written.

WordPress theme development is remarkably more efficient with Gulp, a Node-based build system and task runner similar to Grunt. Gulp has been widely hyped in recent times—and no wonder, it’s a major time-saver. I found out about Gulp by way of this excellent introduction by Mark Goodyear, later adapting the build script in this post by Matt Banks for my own use.

Since then I have refined my workflow to the point where I figure it might be helpful for me to share my approach with the WordPress community. With this in mind I have prepared this starter kit (with source code on GitHub) to demonstrate how you can use Gulp with Bower and Sass to optimize your WordPress theme development workflow. This starter kit does not contain any actual WordPress theme files—it is meant to act as the scaffolding that surrounds your theme and supports your workflow.

Working with Gulp requires that you know your way around the command line. You’ll need to have installed Gulp, Bower, and Sass already, of course. (And hopefully I don’t have to tell you that both Bower and Sass are awesome!) Follow the instructions on GitHub or Google around a bit and you should be up and running in no time.

Configuration is a snap; just rename the project variable at the top of gulpfile.js to whatever you want your theme directory to be named. The src directory presently contains nothing more than a sample of code showing how you might want to include the generated scripts and stylesheet in your theme.

Another component you will want to setup is LiveReload, a great tool that will allow Gulp to refresh your browser anytime you make changes to theme files. I use a Chrome browser extension but there are many other options out there if you go looking.

Next you may as well drop some code into your new theme directory so that you have something to play with. _s is not a bad place to start. You can also add a pre-existing theme, though you may want to move some files around, especially if that theme has Sass source files.

Finally, you will want to symlink your new theme to a local installation of WordPress so you can work on it. There are two directories you’ll want to consider symlinking after setup: build (for development and testing) and the project folder under dist (for testing and deployment to production). Once you start mucking around this stuff will become clear.

At any rate, when setup is complete (which shouldn’t take more than a few minutes once you’re familiar with the process) all you need to do is run gulp. Now, anytime you modify a file, Gulp will draw upon an arsenal of plugins to concatenate, autoprefix, lint, minify, rename, and copy your stylesheets, scripts, and images as needed. When you’re read to deploy something to production you can build a clean distribution with gulp dist. This task is more memory-intensive as images are compressed each time. You’ll want to do this before deploying a theme—but while you are still developing it locally simply running gulp is good enough.

Let me walk you through parts of the build script:

// Project configuration
var project     = 'my-theme'
  , build       = './build/'
  , dist        = './dist/'
  , source      = './src/' // 'source' instead of 'src' to avoid confusion with gulp.src
  , bower       = './bower_components/'
;

// Initialization sequence
var gulp        = require('gulp')
  , gutil       = require('gulp-util')
  , plugins     = require('gulp-load-plugins')({ camelize: true })
;

Specify the name of your theme directory by modifying the project variable. The next batch of variables specify various paths that will be used in the remainder of the script. Gulp is then loaded alongside various dependencies. gulp-load-plugins is a handy tool that will read package.json and require whatever plugins are specified there, saving us the need to explicitly require plugins.

gulp.task('styles', function() {
  return gulp.src([source+'scss/*.scss', '!'+source+'scss/_*.scss']) // Ignore partials
  .pipe(plugins.rubySass({
    loadPath: bower // Adds the `bower_components` directory to the load path so you can @import directly
  , precision: 8
  }))
  .pipe(plugins.autoprefixer('last 2 versions', 'ie 9', 'ios 6', 'android 4'))
  .pipe(gulp.dest(build))
  .pipe(plugins.rename({suffix: '.min'}))
  .pipe(plugins.minifyCss({ keepSpecialComments: 1 }))
  .pipe(gulp.dest(build));
});

This is a typical Gulp task for handling Sass files. Here we are telling Gulp to read all .scss files from the src/scss directory that don’t start with an underscore and pipe them through the Sass compiler. Note that I haven’t included Compass, though you may want to. With Autoprefixer in the toolchain I find most Sass libraries somewhat useless. (And if you’re looking for a Sass mixin library for the post-vendor prefixing era try Scut.) At any rate the resulting CSS file ends up in build for debugging purposes in both uncompressed and minified forms. The keepSpecialComments flag ensures that the minified CSS still includes the header required by WordPress.

// Scripts; broken out into different tasks to create specific bundles which are then compressed in place
gulp.task('scripts', ['scripts-lint', 'scripts-core', 'scripts-extras'], function(){
  return gulp.src([build+'js/**/*.js', '!'+build+'js/**/*.min.js']) // Avoid recursive min.min.min.js
  .pipe(plugins.rename({suffix: '.min'}))
  .pipe(plugins.uglify())
  .pipe(gulp.dest(build+'js/'));
});

// Lint scripts for errors and sub-optimal formatting
gulp.task('scripts-lint', function() {
  return gulp.src(source+'js/**/*.js')
  .pipe(plugins.jshint('.jshintrc'))
  .pipe(plugins.jshint.reporter('default'));
});

// These are the core custom scripts loaded on every page; pass an array to bundle several scripts in order
gulp.task('scripts-core', function() {
  return gulp.src([
    source+'js/core.js'
  , source+'js/navigation.js'
  ])
  .pipe(plugins.concat('core.js'))
  .pipe(gulp.dest(build+'js/'));
});

The current implementation of the scripts task differs remarkably from the original outlined by Matt Banks. Here there is a master scripts task that waits on a bunch of other tasks to generate source files before minifying whatever scripts can be found in the build directory. First, all scripts under src are linted (checked for errors against the settings in the .jshintrc file), and then two bundles are concatenated and deposited in the build directory.

As you can see you’ll need to manually specify files for each bundle1. Bundling gives you, the theme developer, more control over what gets loaded and where. Say, for instance, you have a script that you only run on the front page; in that case you can generate a secondary bundle and call that conditionally from within WordPress rather than load it up on every page. In the example listed core.js is concatenated with navigation.js before being shipped off to the build directory for minification. You can, of course, also include dependencies from the Bower components folder; just add a path such as bower+'dependency/dependency.js' to the source array.

Image and PHP file handling is simple and straight-forward: files that match the relevant patterns are copied to the build directory with paths intact. If you store PHP files in a subdirectory they’ll land in the same spot under build.

Next is a series of tasks to handle packaging. I won’t include the source code in this write-up but I will explain what’s going on. First, junk files are cleared out and the dist folder is wiped clean. CSS files are minified, images are optimized, and everything is deposited in a subdirectory of dist matching the name of your project variable. This way you can upload only this folder to your production environment without having to rename anything.

Next is a hack to copy and rename normalize.css so that it can be properly imported in Sass. This task is called by Bower when front-end components are added or updated (see .bowerrc for more). You may find other uses for the Bower post-install trigger depending on what front-end components you integrate into your project.

// Start the livereload server; not asynchronous
gulp.task('server', ['build'], function() {
  plugins.livereload.listen(35729, function (err) {
    if (err) {
      return console.log(err);
    };
  });
});

// Watch task: build stuff when files are modified, livereload when anything in the `build` or `dist` folders change
gulp.task('watch', ['server'], function() {
  gulp.watch(source+'scss/**/*.scss', ['styles']);
  gulp.watch([source+'js/**/*.js', bower+'**/*.js'], ['scripts']);
  gulp.watch(source+'**/*(*.png|*.jpg|*.jpeg|*.gif)', ['images']);
  gulp.watch(source+'**/*.php', ['php']);
  gulp.watch([build+'**/*', dist+'**/*']).on('change', function(file) {
    plugins.livereload.changed(file.path);
  });
});

The watch task watches for file changes and calls the appropriate tasks as needed, only triggering a browser refresh via Live Reload when files in the build or dist directories have changed. This task is chained to server which is then chained to build. This means that when you run watch a series of tasks will fire ensuring that you’re always working with a freshly minted copy of your theme. Consequently, the default task simply fires watch when you run gulp from the command line.

Apart from the main build script this project also includes a few handy files:

  • .bowerrc to run the aforementioned Bower post-install script (in this case gulp bower_components).
  • .gitignore and .gitattributes files adapted for use with WordPress theme development.
  • .jshintrc settings for JSHint.
  • bower.json pre-loaded with Normalize.css and Eric Meyer’s reset. Edit this file to give your project a name and take credit for your work!
  • packages.json pre-loaded with all the Gulp development dependencies you need. Edit this file to give your project a name and take credit for your work!

You will notice that this project is setup to generate a clean theme directory for deployment and distribution. For some reason I continually run across WordPress themes hosted on GitHub littered with all manner of development files (.gitignore and so on). There’s no need for this—just build into a clean directory. The only downside is that you probably won’t be able to update your WordPress theme from GitHub, but that’s not a show-stopper.

If you dive into src/scss you’ll find a handful of files demonstrating how to import Bower components. In the case of Normalize.css I have a custom task (above) to copy the file into the assets directory but the Eric Meyer’s reset is imported directly. Bower components are in the Sass load path so you don’t have to mess around with ../../.. nonsense.

With all that said, Gulp is not a panacea and Grunt is certainly not obsolete. I happen to find Gulp fast and easy to work with but it isn’t nearly as feature-rich as Grunt—not yet anyway. I agree with what Matt Banks wrote in his original article: Gulp lacks a robust suite of deployment plugins. But apart from that Gulp is an incredibly useful tool for WordPress theme development.

I hope you have found my WordPress-Gulp-Bower-Sass starter kit helpful in some way!


  1. Automated script bundling is a feature I’d like to add in the future. I tried a few approaches recently but wasn’t satisfied with the results. For now, unless you’re managing dozens of dependencies, a human touch will probably offer more control and accuracy than some kind of automated kludge.