Multi-Site Build Scripts

September 8, 2017

I’ve recently finished a project that needed build scripts for multiple websites within the same repository. The scripts themselves were pretty similar: processing production ready CSS, JS and image files. My goal was to come up with a solution that would work regardless of how many sites got added. I also wanted to create a method for using shared assets that each site could pull from if needed. I use Gulp for build scripts so all the examples below are created in gulpfile.js. Make sure to include the gulp package.

var gulp = require('gulp');

Lets create a paths variable that we can store our common file paths. For example: themes, shared assets, source and destination paths, etc. Anything that will be used in multiple places.

var paths = {
  themes: 'wp-content/themes/',
  shared: 'shared/',
  src: 'src/',
  dest: 'dist/'
};

We’ll also create a themes array that contains theme objects which represent each theme we want build scripts for along with any other theme specific data. For now, all we’re going to need is a unique identifier for each theme which will also be the same as each theme’s directory.

var themes = [{
  id : 'astra'
}, {
  id : 'bistro'
}, {
  id : 'cell'
}, {
  id : 'nova'
}, {
  id : 'quest'
}];

Now, we can simply loop through our themes and build identical tasks for each using the forEach() method.

themes.forEach(function (theme, index) {
  // ...
});

We’ll still need a unique identifier for our tasks so we don’t override them in each loop. For this we’ll use the themes id.

themes.forEach(function (theme, index) {

  gulp.task(theme.id + ':css', function() {
    // Our task code goes here...
  });

  gulp.task(theme.id + ':js', function() {
    // Our task code goes here...
  });

  gulp.task(theme.id + ':img', function() {
    // Our task code goes here...
  });

});

This will create a set of unique tasks for each theme using the theme id as a prefix: astra:css, bistro:css, cell:css, and so on for each :css, :js and :img suffixed task.

We’ll also want to create a watch task for each theme. This will take the same form as our previous tasks with the theme name followed by the :watch suffix. We’ll also build the unique source and destination paths that can be then used within any task.

themes.forEach(function (theme, index) {

  // Construct our unique source and destination paths
  var src  = paths.themes + theme.id + '/' + paths.src;
  var dest = paths.themes + theme.id + '/' + paths.dest;

  // ...

  // Watch task
  gulp.task(theme.id + ':watch', function() {

    gulp.watch(src + 'scss/**/*', [theme.id + ':css']);

    gulp.watch(src + 'js/**/*', [theme.id + ':js']);

    gulp.watch(src + 'img/**/*', [theme.id + ':img']);

  });

});

Finally, we also want to create a :go task for running everything specific to each theme and a default task for running :go as well as initiating the :watch task after. This would look like this.

themes.forEach(function (theme, index) {

  // ...

  // Creates the :go tasks to run everything
  gulp.task(theme.id + ':go', [
    theme.id + ':css',
    theme.id + ':js',
    theme.id + ':img'
  ]);

  // Creates default task
  gulp.task(theme.id, [
    theme.id + ':go',
    theme.id + ':watch'
  ]);

});

Now to put all this together, we’d have:

// Paths variable
var paths = {
  themes: 'wp-content/themes/',
  shared: 'shared/',
  src: 'src/',
  dest: 'dist/'
};

// Themes variable
var themes = [{
  dir : 'astra'
}, {
  dir : 'bistro'
}, {
  dir : 'cell'
}, {
  dir : 'nova'
}, {
  dir : 'quest'
}];

// Loop through theme objects
themes.forEach(function (theme, index) {

  // Construct our unique source and destination paths
  var src  = paths.themes + theme.id + '/' + paths.src;
  var dest = paths.themes + theme.id + '/' + paths.dest;

  // CSS task
  gulp.task(theme.id + ':css', function() {
    // Our task code goes here...
  });

  // JavaScript task
  gulp.task(theme.id + ':js', function() {
    // Our task code goes here...
  });

  // Image processing task
  gulp.task(theme.id + ':img', function() {
    // Our task code goes here...
  });

  // Watch task
  gulp.task(theme.id + ':watch', function() {
    gulp.watch(src + 'scss/**/*', [theme.id + ':css']);
    gulp.watch(src + 'js/**/*', [theme.id + ':js']);
    gulp.watch(src + 'img/**/*', [theme.id + ':img']);
  });

  // Creates the :go tasks to run everything
  gulp.task(theme.id + ':go', [
    theme.id + ':css',
    theme.id + ':js',
    theme.id + ':img'
  ]);

  // Creates default task
  gulp.task(theme.id, [
    theme.id + ':go',
    theme.id + ':watch'
  ]);

});

The final result creates the following tasks for each theme in your themes array, (using the theme named astra for the example below):

Task Description
astra:css Theme specific CSS task
astra:js Theme specific JavaScript task
astra:img Theme specific image processing task
astra:watch Watches our theme directories for changes
astra:go Runs all our theme specific tasks
astra Runs :go and then initiates the theme :watch task

So this is all great and works perfectly for your multi-site needs. But what about when you need to run global tasks? What about a global watch task that includes shared assets? No worries, with a little tweak to our previous example we can setup all of these extra features.

Let’s start with some global tasks. This will allow us to run a single task for similar jobs across multiple sites. So for example, currently if we wanted to run all CSS tasks across our sites we’d have to manually run:

gulp astra:css
gulp bistro:css
gulp cell:css
gulp nova:css
gulp quest:css

We want to be able to simply run gulp css and build the CSS task across all sites. To do this, we’ll need to capture all of our tasks in an array as we create them. To do this, we’ll create a tasks object with keyed arrays where we’ll push all related tasks.

// Tasks object
var tasks = {
  'css' : [],
  'js' : [],
  'img' : []
};

// Loop through theme objects
themes.forEach(function (theme, index) {

  // Save our tasks for later
  tasks.css.push(theme.id + ':css');
  tasks.js.push(theme.id + ':js');
  tasks.img.push(theme.id + ':img');

  // ...

});

// Global CSS task
gulp.task('css', tasks.css);

// Global JavaScript task
gulp.task('js', tasks.js);

// Global image processing task
gulp.task('img', tasks.img);

Now we have available gulp css, gulp js and gulp img which will run all their corresponding tasks for every theme. Next, let’s setup a global watch task which will also include a watch for shared assets.

// Global watch task
gulp.task('watch', function() {

  // Loop through theme objects
  themes.forEach(function (theme, index) {

    // Construct our unique source path
    var src  = paths.themes + theme.id + '/' + paths.src;

    // Theme specific watch tasks
    gulp.watch(src + 'css/**/*', [theme.id + ':css']);
    gulp.watch(src + 'js/**/*', [theme.id + ':js']);
    gulp.watch(src + 'img/**/*', [theme.id + ':img']);

  });

  // Shared watch tasks
  gulp.watch(paths.shared + 'scss/**/*', ['css']);
  gulp.watch(paths.shared + 'js/**/*', ['js']);
  gulp.watch(paths.shared + 'img/**/*', ['img']);

});

Check out the final working example in this GitHub Gist which can be used as a starter file for multi-site build scripts.

Now we have a robust build script that allows for multiple sites given a similar file structure. Also a set of smart global tasks that help us manage all our sites in bulk when needed. These can be used regardless of how many sites get added or removed. Hopefully this is enough to get you started and give you some ideas for what’s possible.