WordPress
Article

Modernizing WordPress Theme Development with Sage

By Wern Ancheta

A few years back, we published an article about Roots, a Theme Framework for WordPress. Now Roots has turned into a company and they’ve turned the Roots theme framework into two sets of tools namely Sage and Bedrock.

In this article, I’m going to walk you through Sage. It utilizes HTML5 Boilerplate, gulp, Bower, and the Bootstrap front-end framework. First I’ll give you a brief overview of each of the tools that I’ve just mentioned and then we’ll go through the installation and customization of the framework. Finally we’ll go through the basic Sage workflow.

Sage

Tools

  • HTML5 Boilerplate – a front-end template.
  • Gulp – a build system used for automating tasks such as minification and concatenation of front-end assets, optimizing images, running tests and many others.
  • Bower – a package manager for front-end assets. It’s used for pulling JavaScript libraries like jQuery or Lodash into your project.
  • Browsersync – synchronises file changes and interactions in multiple devices.
  • asset-builder – used for collecting assets in your theme and putting them together.
  • wiredep – used for injecting Sass and Less dependencies from Bower into the main theme stylesheet.
  • Bootstrap – a front-end framework that allows us to easily create responsive websites.

Installation

To install Sage, navigate to your WordPress theme directory and execute the following command from your terminal. Don’t forget to replace theme-name with the name of your theme:

git clone https://github.com/roots/sage.git theme-name

Note that the command above uses Git. If you don’t have Git installed on your machine, you can just download the zip file from the git repo, create a new folder in your WordPress theme directory and copy the contents of the extracted zip file.

Once that’s done, you need to add the following to your wp-config.php file:

define('WP_ENV', 'development');

This sets WordPress to a development environment.

Directory Structure

The directory structure is pretty much like that of any WordPress theme. You still have the familiar looking files in the root of the theme:

  • index.php
  • functions.php,
  • 404.php
  • search.php
  • single.php
  • page.php

The only difference is that it’s got the lib directory where the theme configuration and utilities are stored. Note that including the front-end assets is done from the assets.php instead of functions.php. Registering widgets, menus and adding theme support is done from init.php. Specifying page titles can be done from titles.php.

There’s also the assets directory which contains .scss files which get compiled later into a single main.css file. This is after you use gulp to concatenate and minify the .scss files. The lang directory contains a sage.pot file which is basically used for translating the text used in Sage. By default, there are no translations in there, just the text to be translated. If you need to support another language in your theme, then you can check out the sage-translations repo, which contains translations of Sage in different languages. Just copy the translations and put it on the sage.pot file. If you’re using other text that needs to be translated, then you can also add it on that file.

Lastly, there’s the templates directory where all the templates that you usually have in a WordPress theme are stored. The only difference is that the templates are based on HTML5 Boilerplate so it has some default ARIA roles to improve the accesibility of your theme.

Customization

Next we’ll look into customizing Sage to fit your needs. Open up the lib/init.php file in your new theme. Here are some of the things that you can customize:

Title Tag

The title-tag allows your theme to enable modifying of the title tag in your web page. This is a feature added to WordPress 4.1 and you can toggle the functionality by adding or removing the following line:

add_theme_support('title-tag')

By default, Sage adds a primary navigation to the navigation menus. This is where you can add more.

register_nav_menus([
  'primary_navigation' => __('Primary Navigation', 'sage')
]);

Post Thumbnails

Post Thumbnails or better known as Featured Images with version 3.0 of WordPress, is an image representing a post, page or any custom post type. You can enable or disable this feature on your theme by adding or removing the following:

add_theme_support('post-thumbnails')

Post Formats

These are the types of posts that are enabled by default. You can add or remove items from the array depending on your needs.

add_theme_support('post-formats', ['aside', 'gallery', 'link', 'image', 'quote', 'video', 'audio']);

HTML5 Markup

This allows you to add support for HTML5 Markup. By default, it’s allowed on captions, comment forms and comment lists.

add_theme_support('html5', ['caption', 'comment-form', 'comment-list']);

Editor Stylesheet

This allows you to specify the path of the stylesheet for customizing the TinyMCE Editor that is used by WordPress.

add_editor_style(Assets\asset_path('styles/editor-style.css'));

Register Sidebars

Lastly, we have the code for registering sidebars on widgets_init. By default, Sage comes with two sidebars: primary and footer.

function widgets_init() {
  register_sidebar([
    'name'          => __('Primary', 'sage'),
    'id'            => 'sidebar-primary',
    'before_widget' => '<section class="widget %1$s %2$s">',
    'after_widget'  => '</section>',
    'before_title'  => '<h3>',
    'after_title'   => '</h3>'
  ]);

  register_sidebar([
    'name'          => __('Footer', 'sage'),
    'id'            => 'sidebar-footer',
    'before_widget' => '<section class="widget %1$s %2$s">',
    'after_widget'  => '</section>',
    'before_title'  => '<h3>',
    'after_title'   => '</h3>'
  ]);
}
add_action('widgets_init', __NAMESPACE__ . '\\widgets_init');

Workflow

Now let’s move on to the workflow. In this section, you’ll learn how to use the tools which I’ve talked about earlier when developing WordPress themes with Sage.

First, let’s install the tools into your development machine. The main dependency of most of the tools that were going to use is Node.js. Luckily it can be easily installed using an installer. Go ahead and download the installer that is applicable to your platform, open it and go through the instructions provided. Normally you’d only have to click on ‘Next’ until the installer finishes installing Node.js. Once that’s done, you can install the rest of the tools by using the npm install command from your terminal:

npm install gulp bower browser-sync asset-builder wiredep --save

The command above installs gulp, Bower, Browsersync, Asset Builder and wiredep into your project.

Using Bower

When using Bower, you only need to keep in mind the search, install, list and uninstall commands. First is the search command. You can use it if you’re not sure of the name of the package that you want to install. This command takes up the name of the package. It doesn’t have to be exact though. For example if you want to search for ‘jQuery’, you can just use ‘query’ and it will list all the packages which have that string in their name. In this case, jQuery would also be listed.

bower search query

Once you already know the name of the package, you can install it with the install command:

bower install jquery

This installs jQuery into the bower_components directory. If you do not want packages to be installed in there, you can edit the .bowerrc file in the root of your WordPress theme and change the directory to the path where you want to install.

Next is the list command. This shows you the list of packages you’ve installed using Bower in a tree form. This means that if a specific package is a dependency of another package then you will also see it.

Lastly, we have the uninstall command. This allows you to remove Bower packages from your project. For example, if you no longer want jQuery:

bower uninstall jquery

Using gulp

In order to use gulp, we first need to install the gulp plugins used by Sage:

npm install gulp-autoprefixer gulp-changed gulp-imagemin gulp-less --save

Once that’s done, we can now use the gulp command to compile and optimize your project assets. Executing the gulp command shows the following output:

[08:50:10] Using gulpfile ~/www/wordpress/wp-content/themes/my-theme/gulpfile.js
[08:50:10] Starting 'clean'...
[08:50:10] Finished 'clean' after 45 ms
[08:50:10] Starting 'default'...
[08:50:10] Starting 'build'...
[08:50:10] Starting 'wiredep'...
[08:50:11] Finished 'default' after 1.39 s
[08:50:12] Finished 'wiredep' after 1.57 s
[08:50:12] Starting 'styles'...
[08:50:22] Finished 'styles' after 10 s
[08:50:22] Starting 'jshint'...
[08:50:23] Finished 'jshint' after 638 ms
[08:50:23] Starting 'scripts'...
[08:50:29] Finished 'scripts' after 6.51 s
[08:50:29] Starting 'fonts'...
[08:50:29] Starting 'images'...
[08:50:29] Finished 'fonts' after 162 ms
[08:50:37] gulp-imagemin: Minified 2 images (saved 405.06 kB - 35.1%)
[08:50:37] Finished 'images' after 7.79 s
[08:50:37] Finished 'build' after 27 s

On the first line, you can see that it’s using the gulpfile.js file. This file contains all the instructions to be carried out by Gulp. The first task is ‘clean’, this removes all the files in the dist directory. This is where all the compiled and optimized files are stored. Next, the ‘default’ task is called. This basically just calls the ‘build’ task. Then the ‘build’ task calls the styles, scripts, fonts and images tasks.

At this point you can open up the gulpfile.js so I can walk you through each task. First is the ‘styles’. This depends on ‘wiredep’ which allows us to inject Less and Sass Bower dependencies into the main.css file found at the dist/styles directory. Once that’s done, it then compiles the Sass and Less files in your assets/styles directory.

gulp.task('styles', ['wiredep'], function() {
  var merged = merge();
  manifest.forEachDependency('css', function(dep) {
    var cssTasksInstance = cssTasks(dep.name);
    if (!enabled.failStyleTask) {
      cssTasksInstance.on('error', function(err) {
        console.error(err.message);
        this.emit('end');
      });
    }
    merged.add(gulp.src(dep.globs, {base: 'styles'})
      .pipe(cssTasksInstance));
  });
  return merged
    .pipe(writeToManifest('styles'));
});

If you want to add page specific CSS, you can do that by through the manifest.json file which you can find at the root of the assets directory.

Here’s an example where I’ve added other-page.css. You can then specify the files that will be used as a source by specifying an array of paths in the files property. In this case, I’m only using the other-page.less file in the styles directory.

{
  "dependencies": {
    "main.js": {
      "files": [
        "scripts/main.js"
      ],
      "main": true
    },
    "main.css": {
      "files": [
        "styles/main.scss"
      ],
      "main": true
    },
    "editor-style.css": {
      "files": [
        "styles/editor-style.scss"
      ]
    },
    "other-page.css": {
      "files": [
        "styles/other-page.less"
      ]
    },
    "modernizr.js": {
      "bower": ["modernizr"]
    }
  },
  "config": {
    "devUrl": "http://example.dev"
  }
}

Next is the ‘scripts’ task. This depends on the ‘jshint’ task which checks for the quality of your JavaScript code. For example, always putting curly braces, or prohibiting the use of == or != in your code because they can cause bugs related to equality of the variables that you’re trying to compare.

gulp.task('scripts', ['jshint'], function() {
  var merged = merge();
  manifest.forEachDependency('js', function(dep) {
    merged.add(
      gulp.src(dep.globs, {base: 'scripts'})
        .pipe(jsTasks(dep.name))
    );
  });
  return merged
    .pipe(writeToManifest('scripts'));
});

You can customize JSHint by editing the .jshintrc file found at the root of your theme. Sage has already added some options by default, so be sure to check out the list of all the available options and customize JSHint according to your needs.

{
  "bitwise": true,
  "browser": true,
  "curly": true,
  "eqeqeq": true,
  "eqnull": true,
  "esnext": true,
  "immed": true,
  "jquery": true,
  "latedef": true,
  "newcap": true,
  "noarg": true,
  "node": true,
  "strict": false
}

After checking the quality of your code, the ‘scripts’ task concatenates all the JavaScript files in your Bower dependencies together with your scripts/main.js file. Finally, it will minify the resulting script with uglify.js. Note that you can customize the default behavior through your manifest.json file. For example, if you don’t want all your Bower dependencies to get combined together with your main script. Then you can remove the main property in your main.js. Then add a bower property, this takes up the array of the Bower components that your script depends on. In the example below, I’ve set jQuery as the dependency.

"main.js": {
  "files": [
    "scripts/main.js"
  ],
  "main": true, //remove this line
  "bower": ["jquery"]
},

Next is the ‘fonts’ task. What this does is it gets all the fonts in the assets/fonts directory as well as any fonts used by your Bower dependencies. It puts them all in the dist/fonts directory. This task uses gulp-flatten which flattens the directory structure. This means you will only find fonts in the dist/fonts directory. This is good since you no longer have to specify lots of directories when linking your fonts.

gulp.task('fonts', function() {
  return gulp.src(globs.fonts)
    .pipe(flatten())
    .pipe(gulp.dest(path.dist + 'fonts'))
    .pipe(browserSync.stream());
});

Lastly, we have the ‘images’ task. This compresses the images in the assets/images directory using lossless compression. This effectively reduces the size of the image without any perceivable reduction in the image quality. Optimized images are then stored in the dist/images directory.

gulp.task('images', function() {
  return gulp.src(globs.images)
    .pipe(imagemin({
      progressive: true,
      interlaced: true,
      svgoPlugins: [{removeUnknownsAndDefaults: false}, {cleanupIDs: false}]
    }))
    .pipe(gulp.dest(path.dist + 'images'))
    .pipe(browserSync.stream());
});

Aside from the gulp command, there’s also the gulp watch command. You can use this to speed up your development process. It does the same thing as the gulp command, only it does it every time you make changes to an asset. But before you can use it, you have to update the assets/manifest.json file so that the devUrl is the same as the URL of your WordPress install.

"config": {
  "devUrl": "http://localhost/wordpress/"
}

Once that’s done, you can now execute the gulp watch command, make some changes to your assets and watch the browser inject the changes you’ve just made. This works by creating a proxy URL for the devUrl you specified in your config. This contains the script which injects the changes to the page.

http://localhost:3000/wordpress/

If you want to simultaneously test with another device that’s connected to your home network, you can determine the local IP address of your computer and access that from a browser on your device. The URL should look something like the following:

http://192.168.xxx.xxx:3000/wordpress/

Speeding Things Up

While I was testing Sage, I noticed that gulp watch takes around 10 seconds to finish doing all the tasks. This is no good, since you can’t see the changes immediately. For that reason, we have to make a few changes on the gulpfile.js file. First is the enabled variable. This is used to enable or disable specific tasks by specifying an option when using the gulp watch command.

var enabled = {
  // Enable static asset revisioning when `--production`
  rev: argv.production,
  // Disable source maps when `--production`
  maps: argv.maps,
  // Fail styles task on error when `--production`
  failStyleTask: argv.production,
  // minify only when `--minify` is specified
  minify: argv.minify
};

In the code above, I’ve set to disable source maps and minifying files by default. So if you want to use those tasks then you would need to specify them as options:

gulp watch --minify --maps

Now we have to change the cssTasks and jsTasks so it utilizes the changes we’ve made to the enabled variable. First is the source maps, we use gulpif to check if source maps is enabled and only call the function that generates sourcemaps when it is enabled.

.pipe(function() {
  return gulpif(enabled.maps, sourcemaps.init());
})

Next is the ‘minify’ task.

.pipe(function(){
  return gulpif(enabled.minify, minifyCss({
    advanced: false,
    rebase: false
  }));
})

Your cssTasks should now look like the following:

var cssTasks = function(filename) {
  return lazypipe()
    .pipe(function() {
      return gulpif(!enabled.failStyleTask, plumber());
    })
    .pipe(function() {
      return gulpif(enabled.maps, sourcemaps.init());
    })
    .pipe(function() {
      return gulpif('*.less', less());
    })
    .pipe(function() {
      return gulpif('*.scss', sass({
        outputStyle: 'nested', // libsass doesn't support expanded yet
        precision: 10,
        includePaths: ['.'],
        errLogToConsole: !enabled.failStyleTask
      }));
    })
    .pipe(concat, filename)
    .pipe(autoprefixer, {
      browsers: [
        'last 2 versions',
        'ie 8',
        'ie 9',
        'android 2.3',
        'android 4',
        'opera 12'
      ]
    })
    .pipe(function(){
      return gulpif(enabled.minify, minifyCss({
        advanced: false,
        rebase: false
      }));
    })
    .pipe(function() {
      return gulpif(enabled.rev, rev());
    })
    .pipe(function() {
      return gulpif(enabled.maps, sourcemaps.write('.'));
    })();
};

Next is the jsTask, modify the ‘uglify’ task so that it checks first if minify is enabled before it executes the function that minifies the scripts:

.pipe(function(){
  return gulpif(enabled.minify, uglify())
})

Your jsTasks should now look like the following:

var jsTasks = function(filename) {
  return lazypipe()
    .pipe(function() {
      return gulpif(enabled.maps, sourcemaps.init());
    })
    .pipe(concat, filename)
    .pipe(function(){
      return gulpif(enabled.minify, uglify())
    })
    .pipe(function() {
      return gulpif(enabled.rev, rev());
    })
    .pipe(function() {
      return gulpif(enabled.maps, sourcemaps.write('.'));
    })();
};

Conclusion

That’s it! In this tutorial you’ve learned how to work with Sage to modernize your process in developing a WordPress theme. You’ve also learned how to use tools like Bower, gulp and Browsersync to speed up your development.

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

  • http://colinjohnston.com Colin Johnston

    This is a nice clear intro to getting the Sage starter theme installed. One of the main benefits of developing themes with Sage is the theme wrapper, a very efficient approach to templating that removes often repeated markup from individual templates and puts it into a single file. It can be tricky even for long-time WordPress developers to get comfortable with this (myself included), so it would be great to see a walk-through of how to add custom base templates, etc.

    Overall I’m really enjoying exploring Sage and the processes the team at Roots use—everything they do works really well (I’m currently creating my own fork of it that uses Foundation for Sites 6.2, though, as I’ve always preferred it over Bootstrap.)

  • https://awontis.com/ Dan Awontis

    I’d agree with @colinleejohnston:disqus. It’s a nice walk-through article about the Sage. Especially for someone who still didn’t use it.

  • michal lev

    Thanks for this articles. I have been using Sage for quite a while, and although its quite complicated to figure out at the development stage it pays off when it comes to performance. I have noticed a couple of differences between what’s in this article and last versions of Sage:
    1. init.php file does no (longer?) exist in the lib folder. I usually use the setup.php file instead.
    2. According to Sage docs, for the theme build it’s quite enough to use only bower install and npm install. Those two commands install all the dependencies and make the build as detailed in the configuration files.

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in WordPress, once a week, for free.