WordPress Development with XAMPP, Gulp, and SASS

One of my main programming projects is development of a complex site powered by WordPress, but I’ve never put together a description of my workflow. I build the site on my Windows box using XAMPP and Gulp to manage everything. It’s surprisingly difficult to find a good explainer1 on how to set this up, so this post will explain how I get my WordPress development environment up and running.

XAMPP

WordPress is an open-source content management system that powers a huge amount of the Internet. It’s built with PHP, and it stores data in a MySQL database. That means your development environment needs a webserver that runs PHP, as well as a connected MySQL databse. It’s much easier to install all of this as a pacakge, rather than as individual components. Fortunately, there are several freely available packages that will install and configure all of this for you. The best one I’ve found is XAMPP.

Some tips for installing XAMPP:

  1. Don’t install it to a system directory, as that can cause permission issues. Install it somewhere in your user directory (typically something like C:\Users\your_username).
  2. Run setup_xampp.bat from the installation directory in your terminal; it’s likely no setup will be required, but this will make sure everything is ready to go.
  3. Run xampp-control.exe as an administrator to start your server and database.
  4. Install MySQL as a Windows service. Do this by clicking the red “X” to the left of MySQL in the control panel. XAMPP has a weird habit of corrupting databases, and this is the only reliable method I’ve found to ensure my databases stay intact. You don’t need to install Apache (the web server) as a service.

Now you should have a running webserver and database. You can grab WordPress from the WordPress website. This post won’t explain how to install or configure WordPress, or how to build a WordPress theme (there are plenty of great resources for that). But from here on out we’ll assume you have WordPress installed in a directory like C:\Users\your_username\Apps\xampp\htdocs\your_site\.

Gulp

Gulp is a toolkit for compiling and optimizing web resources and to improve efficiency in your workflow. It’s easiest to install it as a Node module, so that means we need to install Node and initialize a Node project for our WordPress theme. I recommend installing Node on Windows using Node Version Switcher (I install NVS using chocolatey). Then go to your theme directory (something like C:\Users\your_username\Apps\xampp\htdocs\your_site\wp-content\themes\your_theme\) and run the following command in your terminal:

npm install -g gulp-cli

That will install the Gulp command line interface, which we’ll be using throughout this project. Next create a new Node project with this command:

npm init

Then we need to install several Node packages for our project:

npm install --save-dev gulp autoprefixer browser-sync cssnano gulp-concat gulp-deporder gulp-imagemin gulp-newer sass gulp-sass gulp-strip-debug gulp-uglify

Note that we’re making some assumptions about the structure of your theme directory: there’s a /src folder that will contain img (images), js (Javascript), and scss (SASS stylesheets) files and directories that we want optimized. The optimized versions of those files will be piped to the /img, /js, and /css folders; your theme will need to reference those optimized files (production files should never reference anything in the /src folder). We expect the theme folder to look something like this:

/your_theme/
├── /css/
│   └── another_style.css
├── /img/
│   ├── img1.jpg
│   └── img2.png
├── /inc/
├── /js/
│   ├── script1.js
│   └── script2.js
├── /src/
│   ├── /img/
│   │   ├── img1.jpg
│   │   └── img2.png
│   ├── /js/
│   │   ├── script1.js
│   │   └── script2.js
│   └── /scss/
│       ├── another_style.scss
│       └── style.scss
├── /template-parts
├── .gitignore
├── 404.php
├── archive.php
├── footer.php
├── functions.php
├── gulpfile.js
├── header.php
├── index.php
├── page.php
├── search.php
├── single.php
└── style.css

Now create a new file called gulpfile.js in the root directory of your project (the theme folder referenced above). Start with the following contents in the file:

// Gulp.js configuration for WordPress development with XAMPP
'use strict';

const

  // ** Change these variables **
  wordpress_project_name = 'your_site', // This is the name of your website and should be the name of the folder for the website within the XAMPP htdocs folder
  theme_name = 'your_theme', // The name of your WordPress theme, should be the name of the folder in WordPress themes folder
  browserSyncProxy = `http://localhost/${wordpress_project_name}/`, // Change this is you're not using the XAMPP default configuration

  // Source and build folders
  dir = {
    src         : 'src/',
    build       : `../../../../../htdocs/${wordpress_project_name}/wp-content/themes/${theme_name}/`
  };

  // Gulp and plugins
  import gulp from "gulp";
  const { src, dest, series, watch } = gulp;
  import newer from "gulp-newer";
  import imagemin from 'gulp-imagemin';
  import gulpSass from "gulp-sass";
  import deporder from "gulp-deporder";
  import concat from "gulp-concat";
  import stripdebug from "gulp-strip-debug";
  import uglify from "gulp-uglify";
  import browserSync   from 'browser-sync';
  import autoprefixer from "autoprefixer";
  import cssnano from "cssnano";

// For BrowserSync
const browsersync = browserSync.create();
const reload = (cb) => { browserSync.reload(); if(typeof cb === 'function') cb(); };

Image Compression

Gulp has image processing plugins that will compress and optimize images. We’ll tell Gulp to optimize our images by creating a Gulp task that looks in the /src/img folder for images, optimizes those images, and saves the optimized version in the /img folder. The “exclude” variable in the settings object allows you to specify files that shouldn’t be compressed into the output folder (like PSD files, for example).

// Image settings, assumes images are stored in the /img directory in the theme folder
const images = {
  src         : dir.src + 'img/**/*',
  exclude     : [`!${dir.src}img/*.psd`, `!${dir.src}img/*.xcf`],
  build       : dir.build + 'img/'
};
// image processing
gulp.task('images', async () => {
  let rules = [ images.src ];
  rules = rules.concat(exclude);
  return src(rules)
    .pipe(newer(images.build))
    .pipe(imagemin())
    .pipe(dest(images.build));
});

Test this out by opening your terminal and running the following command:

gulp images

SASS/CSS Optimization

Next we turn to SASS compiling and CSS optimization. SASS is a CSS extension language that allows you to organize and optimize your CSS in some really cool ways. You’ll save your .scss files in the src/scss folder. The file style.scss will be exported as the default CSS file for your theme: it will be compiled and sent to /style.css. Any other .scss or .css files will be compiled and exported to the /css folder, retaining their filenames.

// CSS settings
import * as dartSass from 'sass';
const sass = gulpSass(dartSass);

const css = {
  src         : dir.src + 'scss/style.scss',
  watch       : dir.src + 'scss/**/*',
  build       : dir.build,
  sassOpts: {
    outputStyle     : 'compressed',
    precision       : 3,
    errLogToConsole : true
  },
  processors: [
    autoprefixer(),
    cssnano()
  ]
};

// CSS processing
// CSS settings
import * as dartSass from 'sass';
const sass = gulpSass(dartSass);

const css = {
  src         : dir.src + 'scss/style.scss',
  watch       : dir.src + 'scss/**/*',
  build       : dir.build,
  sassOpts: {
    outputStyle     : 'compressed',
    precision       : 3,
    errLogToConsole : true
  },
  devOpts: {
    outputStyle     : 'expanded',
    precision       : 3,
    errLogToConsole : true
  },
  processors: [
    autoprefixer(),
    cssnano()
  ]
};

// CSS processing
function processPrimarySass( dev = false ) {
  return src(css.src)
    .pipe(sass(dev ? css.devOpts : css.sassOpts))
    .pipe(dest(`${css.build}`))
    .pipe(browsersync ? browsersync.reload({ stream: true }) : gutil.noop());
}

function processOtherSass( dev = false ) {
  return src([css.watch, `!${css.src}`])
    .pipe(sass(dev? css.devOpts : css.sassOpts))
    .pipe(dest(`${css.build}css`))
    .pipe(browsersync ? browsersync.reload({ stream: true }) : gutil.noop());
}

async function processSass(dev = false) {
  processPrimarySass(dev);
  processOtherSass(dev);
}

gulp.task('css', async() => {
  processSass(false)
});
gulp.task('cssdev', async () => {
  processSass(true);
});

You can run the CSS processing task this way:

gulp css

That will compile all your SASS and minify it. But note that we’re also creating a separate cssdev task. That command will compile your SASS to CSS so the browser can read it, but it won’t minify the CSS. That one is meant for your development environment, as the unminified CSS is much easier to read and debug.

Javascript Compression

Gulp will also minify and combine Javascript files. To set that up, we use the following code:

// JavaScript settings
const js = {
  src         : dir.src + 'js/**/*',
  build       : dir.build + 'js/',
  filename    : 'scripts.js'
};

function processJs( dev = false ) {
  let jsOutput = gulp.src(js.src)
    .pipe(deporder())
//  .pipe(concat(js.filename))  Uncomment this line if you want to combine Javascript files
    
  if( !dev ) {
    jsOutput = jsOutput.pipe(stripdebug())
    .pipe(uglify());
  }
    
  jsOutput = jsOutput.pipe(gulp.dest(js.build))
    .pipe(browsersync ? browsersync.reload({ stream: true }) : gutil.noop());
  return jsOutput;
}

// JavaScript processing
gulp.task('js', async () => {
  processJs(false);
});
gulp.task('jsdev', async () => {
  processJs(true);
});

By default, the js task will minify any Javascript files in the /src/js folder and pipe the output to the /js folder, retaining their filenames. If you’d prefer to combine all your Javascripts into a single file, uncomment the line reading “.pipe(concat(js.filename))”. If that line is uncommented, all of the scripts in the /src/js folder will be combined into a single Javascript file with the filename specified in the settings object. As with the CSS task, we have a parallel development task (jsdev), which will output files in the same place, but won’t minify them so debugging will be easier.

Putting it All Together

We can combine the image, SASS, and Javascript processing into a single Gulp task. We’ll call it “build”:

// run all tasks
gulp.task('build', series('images', 'css', 'js'));

Then we can build the site for production with one command:

gulp build

BrowserSync

BroswerSync is tool for browser testing that watches files in your working directory for changes. We’ll start by watching all the PHP (this is by default—you can add or change the files watched by modifying the filesToWacth variable) files in our WordPress theme and refresh the browser whenever one of them changes. Because we’re already running an Apache server, BrowserSync will wrap everything in a proxy for us. That proxy is defined in the browserSyncProxy variable. You shouldn’t need to change it unless you’ve modified the default settings for XAMPP. We’ll create a new Gulp task to do this:

// Browsersync options
const browsersync = browserSync.create();
const reload = (cb) => {
  browserSync.reload();
  if(typeof cb === 'function') cb();
};

const syncOpts = {
  proxy       : browserSyncProxy,
  files       : dir.build + '**/*',
  open        : false,
  notify      : false,
  ghostMode   : false,
  ui: {
    port: 8001
  }
};

const filesToWatch = [
  "*.php"
];

// Basic file changes to trigger browser reload
gulp.task('browsersync', async () => {
  if( !browsersync === false )
    browsersync = browserSync.create();
  browsersync.init(syncOpts);
  browsersync.watch(filesToWatch).on("change", reload);
});

We also want BrowserSync to watch our SASS, Javascript, and image files. If one of them changes, we’ll run the relevant compile function and then refresh the browser automatically. We’ll also create a new Gulp task to combine these tasks with the compile tasks, so we can run a single command that will build everything, then start watching for changes:

// Basic file changes to trigger browser reload
gulp.task('browsersync', async () => {
  if( browsersync === false )
    browsersync = browserSync.create();
  browsersync.init(syncOpts);
  browsersync.watch(filesToWatch).on("change", reload);
});

// Files where changes should trigger a compile + reload
gulp.task('watch', series('browsersync', async () => {

  // image changes
  watch(images.src, series('images'));

  // CSS changes
  watch(css.watch, series('css', reload));

  // JavaScript main changes
  watch(js.src, series('js', reload));
}));

gulp.task('watchdev', series('browsersync', async () => {

  // image changes
  watch(images.src, series('images'));

  // CSS changes
  watch(css.watch, series('cssdev', reload));

  // JavaScript main changes
  watch(js.src, series('jsdev', reload));
}));

// Build site and start watching
gulp.task('test', series('build', 'watch'));
gulp.task('develop', series('images', 'cssdev', 'jsdev', 'watchdev'));

Running the Development Environment

Now you can fire up your development environment. Open the XAMPP console and start Apache and MySQL. Then go to the project directory in your terminal and run the following command:

gulp develop

You’ll be able to access your project at localhost/your_site. But we spent all that time setting up BrowserSync, so if you want to use its features, you should access your site at localhost:3000/your_site, and BrowserSync also puts together a neat UI, which you can access at localhost:8001.

You can also run gulp test, which will minify all of your scrips and CSS, resulting in a more production-like testing environment (though it’s harder to debug). You should run either gulp test or gulp build before committing changes to production so that your resources are minified.

You can download the complete gulpfile.js from my Github repository at this link.


  1. I used this guide from Webkul as a starting point. But despite the claim that the guide was updated in March 2023, it’s woefully broken and out-of-date. It uses packages that have been discontinued, and the instructions are no longer consistent with the most recent version of the Node packages it uses. So if you try to follow their instructions and use their gulpfile.js, you’ll run into numerous issues. ↩︎