The Ultimate Guide to Grunt for WordPress Plugin Developers
Grunt has been instrumental in streamlining the development of my WordPress plugins, such as the popular Simple Lightbox. This guide shows you how to use Grunt to slay tedious tasks, work smarter, and bring the fun back into developing WordPress plugins.
Grunt?
Grunt’s tagline, “The JavaScript Task Runner,” is a bit misleading because it implies that it’s only meant for JavaScript-based projects.
In reality, Grunt’s tagline should be “The JavaScript-Based Task Runner,” because it allows you to create tasks using the ubiquitous scripting language for any development project, including WordPress plugins.
There is much more in-depth information on Grunt available on the web (linked to below), so I’ll keep this short: Grunt handles tedious or repetitive tasks so that you don’t have to.
How does Grunt Streamline WordPress Plugin Development?
Peruse Grunt’s home page and you’ll see references to several projects, but not WordPress. In fact, it wasn’t until I started using Grunt in Simple Lightbox’s (SLB) development that I realized how perfect Grunt is for handling the particular mix of technologies commonly used in WordPress plugins.
Validating PHP code on the fly, concatenating CSS files, minifying JavaScript files, and preparing your plugin for release are all just scratching the surface of what Grunt is capable of automating while you happily work on your plugin, liberated from the tedium of those tasks.
Using Simple Lightbox as an example, this article will detail how to get the most out of Grunt when developing a WordPress plugin.
Note: This guide demonstrates how Grunt can be used to streamline WordPress plugin development. Familiarity with Grunt would be ideal, but is not required to benefit from this guide. I would recommend Grunt’s Getting Started tutorial (link at the end of this article) if you would like to familiarize yourself with Grunt.
Plugins
Grunt uses plugins to add functionality. These are the Grunt plugins used by SLB:
- Load Grunt Tasks (
load-grunt-tasks
) - Time Grunt (
time-grunt
) - PHPLint (
grunt-phplint
) - JSHint (
grunt-contrib-jshint
) - Uglify (
grunt-contrib-uglify
) - Sass (
grunt-sass
) - Watch (
grunt-contrib-watch
)
Main Configuration
This is Grunt’s main configuration (Gruntfile.js
):
module.exports = function(grunt) { // Load tasks require('load-grunt-tasks')(grunt); // Display task timing require('time-grunt')(grunt); // Project configuration. grunt.initConfig({ // Metadata pkg : grunt.file.readJSON('package.json'), // Variables paths : { // Base dir assets dir base : 'client', // PHP assets php : { files_std : ['*.php', '**/*.php', '!node_modules/**/*.php'], // Standard file match files : '<%= paths.php.files_std %>' // Dynamic file match }, // JavaScript assets js : { base : 'js', //Base dir src : '<%= paths.js.base %>/dev', // Development code dest : '<%= paths.js.base %>/prod', // Production code files_std : '**/<%= paths.js.src %>/**/*.js', // Standard file match files : '<%= paths.js.files_std %>' // Dynamic file match }, // Sass assets sass : { src : 'sass', // Source files dir dest : 'css', // Compiled files dir ext : '.css', // Compiled extension target : '*.scss', // Only Sass files in CWD exclude : '!_*.scss', // Do not process partials base_src : '<%= paths.base %>/<%= paths.sass.src %>', //Base source dir base_dest : '<%= paths.base %>/<%= paths.sass.dest %>', //Base compile dir } }, }); // Load task configurations grunt.loadTasks('grunt'); // Default Tasks grunt.registerTask('build', ['phplint', 'jshint:all', 'uglify', 'sass']); grunt.registerTask('watch_all', ['watch:js', 'watch:sass']); };
Each Grunt task is managed in a separate file to simplify maintenance, so Grunt’s main configuration mostly initializes the aforementioned plugins, preps some variables, and gets out of the way. The plugins will do the heavy lifting from here on out.
The Load Grunt Tasks plugin loads all tasks automatically, so once a task-based plugin is installed, it’s ready to go.
Time Grunt provides feedback on how long Grunt’s tasks are taking to run. This is an optional but useful plugin that provides insight as to where bottlenecks may be occurring.
A single path
variable is created to define the plugin’s structure. This facilitates accessing assets (PHP files, JavaScript files, etc.) in Grunt’s various tasks and allows structural changes to be quickly reflected in all tasks instantly. This allows for some fancy footwork as we’ll soon see.
PHP
Let’s start with PHP, as all WordPress plugins have PHP.
PHPLint
PHPLint validates PHP code and notifies you about issues that you may have missed. This is a great way to automatically check for issues before you commit a change or release a new update. As we’ll see later, PHPLint is also perfect for automatically validating your plugin’s code on the fly whenever a change is made, informing you of issues immediately and saving you debugging time later on down the road.
module.exports = function(grunt) { grunt.config('phplint', { options : { phpArgs : { '-lf': null } }, all : { src : '<%= paths.php.files %>' } }); };
For additional validation, the -f
command is passed to PHP when linting to also check for fatal errors.
JavaScript
Many WordPress plugins use JavaScript to some extent. As a plugin that provides a client-side lightbox for users, SLB employs a fair amount of JavaScript, so making sure this code works properly is very important.
JSHint
Like PHPLint, JSHint checks your JavaScript code for issues (“lint”). It’s possible to miss these issues when making changes or adding new features (e.g. unused variables, coding style, inefficient code, etc.), so JSHint will alert you to these issues so you can fix them.
Think of JSHint as your Jiminy Cricket for JavaScript. You can count on it to let you know when you’re doing something wrong.
module.exports = function(grunt) { grunt.config('jshint', { options : { reporter: require('jshint-stylish'), curly : true, eqeqeq : true, immed : true, latedef : true, newcap : false, noarg : true, sub : true, undef : true, unused : true, boss : true, eqnull : true, browser : true, jquery : true, globals : {} }, grunt : { options : { node : true }, src : ['Gruntfile.js', 'grunt/*.js'] }, all : { options : { globals : { 'SLB' : true, 'console' : true } }, src : ['<%= paths.js.files %>'] }, }); };
There are currently only two targets for JSHint.
Grunt
First, as Grunt itself is configured with JavaScript, it’s a good idea to make sure your configuration is working properly. A separate target is created to validate Grunt-specific files because you’ll only need to do this when updating Grunt’s configuration, otherwise you’ll want skip such files to conserve resources.
Plugin-Specific JavaScript
The second target is for all of SLB’s own JavaScript files. We use a variable defined in Grunt’s main configuration (paths.js.files
) to specify which files should be inspected. Since these same JavaScript files are processed by multiple tasks, using a variable keeps all tasks in sync. Using a variable will prove to be even more important later in this article.
Uglify
SLB’s JavaScript code is very developer friendly and is thus heavily documented with inline comments. However, visitors to sites using Simple Lightbox will never need to see this documentation, so why should they be forced to download it?
Thanks to Uglify, they no longer have to.
Uglify compacts JavaScript code to provide the smallest possible file size for download by an end-user’s browser.
module.exports = function(grunt) { grunt.config('uglify', { options : { mangle: false, report: 'min' }, all : { files : [{ expand : true, cwd : '', dest : '', src : ['<%= paths.js.files %>'], rename : function(dest, srcPath) { return srcPath.replace('/' + grunt.config.get('paths.js.src') + '/', '/' + grunt.config.get('paths.js.dest') + '/'); } }] }, }); };
As with JSHint, the paths.js.files
variable is used by Uglify to target and process SLB’s JavaScript files. JSHint checks the code, and Uglify compacts it. That’s two steps you never have to do manually again.
While Uglify can really shrink down the size of a JavaScript file, it has been tamed somewhat for SLB as WordPress’ Plugin Repository does not allow code that appears to have been obfuscated, even if it’s just shortening variable names to save space.
By setting Uglify’s mangle
option to false
, Uglify merely compacts the code– essentially removing unnecessary white space and inline comments. This makes it simple to re-expand the compacted code if anyone ever wants to compare it with the fully-commented version.
So while SLB’s JavaScript is not as compact as it possibly could be, we still get substantial savings for users. In many cases, the files are over 90% smaller when compacted and gzipped!
Thanks to Uglify we can have the best of both worlds– full documentation for developers, and fast downloads for end-users.
CSS
Many WordPress plugins add custom CSS to front-end or admin pages. SLB mostly uses CSS for its custom lightbox themes on the front-end, but also adds a dash of CSS on its admin options page.
Use Sass
In lieu of vanilla CSS, SLB uses Sass for all stylesheets. Sass is an excellent CSS preprocessor that makes creating stylesheets faster and easier to maintain. It basically gives you everything you ever wanted in CSS– nesting, variables, and even functions. If you’re not using Sass, you don’t know what you’re missing.
As great as it is, Sass files need to be compiled to CSS before they can be used on a web page.
Thankfully, Grunt has a plugin for that.
Grunt’s Sass plugin is actually what convinced me to start using Grunt in the first place. Employing the impressively fast Libsass engine, the plugin compiles Sass into CSS so much faster than the default Ruby-based compiler that it would be downright silly not to use Grunt.
Here’s SLB’s Sass configuration:
module.exports = function(grunt) { grunt.config('sass', { options : { outputStyle : 'compressed', }, core : { files : [{ expand : true, cwd : '<%= paths.sass.base_src %>/', dest : '<%= paths.sass.base_dest %>/', src : ['<%= paths.sass.target %>', '<%= paths.sass.exclude %>'], ext : '<%= paths.sass.ext %>' }] }, themes : { options : { includePaths : require('node-bourbon').includePaths }, files : [{ expand : true, cwd : 'themes/', src : ['*/**/*.scss', '<%= paths.sass.exclude %>'], dest : '<%= paths.sass.dest %>/', srcd : '<%= paths.sass.src %>/', ext : '<%= paths.sass.ext %>', rename : function(dest, matchedSrcPath, options) { var path = [options.cwd, matchedSrcPath.replace(options.srcd, dest)].join(''); return path; } }] } }); };
There are two targets defined in the Sass task– one for core styles, and another for theme styles, as I’m usually either working specifically on the core, or specifically on themes. With separate targets, I can save time by only compiling the Sass files I’m currently working on.
Core Styles
Once again, we’re using variables to define various paths, but compiling SLB’s core styles is pretty straightforward– find Sass files in the client/sass
directory and compile them into the client/css
directory. Done.
Theme Styles
Managing SLB’s theme styles is a bit more involved because each theme has independent sass
and css
directories where their styles are authored and compiled to respectively.
Grunt should not have to be updated every time a new theme is added to SLB, so all Sass files in the themes
targeted in bulk. How then do you compile a theme’s Sass file into that theme’s css
directory without having to explicitly define the sass
/css
directory pairs for each theme?
Answer: By using the rename
option!
This option takes a callback function that all processed Sass files are passed through. The file’s path is modified by this function so that the compiled file is saved to the appropriate theme’s css
directory.
Automate the Automation
Now that you have Grunt tasks covering all corners of your WordPress plugin, you only need to run a single command in the console before a release:
grunt build
Grunt will handle the rest– validate your code, compile it, compress it, and anything else you tell it to do.
This is awesome, why wait until you’re about to release an update for your plugin for these things to happen? Furthermore, why should you even have to type a command for Grunt to jump into action?
Enter Grunt’s Watch plugin.
Watch
As the name implies, the Watch plugin monitors a set files and runs a collection of tasks when any of those files are modified.
module.exports = function(grunt) { grunt.config('watch', { phplint : { files : '<%= paths.php.files_std %>', tasks : ['phplint'], options : { spawn : false } }, sass_core : { files : ['<%= paths.sass.base_src %>/**/*.scss'], tasks : ['sass:core'] }, sass_themes : { files : ['themes/**/<%= paths.sass.src %>/**/*.scss'], tasks : ['sass:themes'] }, jshint : { files : '<%= paths.js.files_std %>', tasks : ['jshint:all'], options : { spawn : false } }, js : { files : '<%= paths.js.files_std %>', tasks : ['jshint:all', 'uglify:all'], options : { spawn : false } } }); grunt.event.on('watch', function(action, filepath) { // Determine task based on filepath var get_ext = function(path) { var ret = ''; var i = path.lastIndexOf('.'); if ( -1 !== i && i <= path.length ) { ret = path.substr(i + 1); } return ret; }; switch ( get_ext(filepath) ) { // PHP case 'php' : grunt.config('paths.php.files', [filepath]); break; // JavaScript case 'js' : grunt.config('paths.js.files', [filepath]); break; } }); };
With Watch running (via the grunt watch
command), everything is now automated. I can work as usual and Grunt will make sure everything is taken care of.
- PHP files are validated.
- JavaScript files are validated and minified.
- Sass files are automatically compiled to CSS files.
The sky is the limit here– anything you configure Grunt to do can be automated by the Watch plugin.
Run Tasks Efficiently
By default, Watch runs the tasks you tell it to when any matching file is modified. This can result in wasted resources if a task’s default collection of files are processed or compiled when just one file has been changed.
For example: Do all JavaScript files need to be validated and minified when just one is updated? Of course not.
Therefore, to run Grunt tasks as efficiently as possible, the Watch targets can be configured to run tasks only on the file that was modified, instead of all files normally processed by a task.
In SLB, an event handler is used so that when a file is updated, only that file is processed by the task it is associated with, leaving all other files untouched.
For example:
- Modify a PHP file and it (and only it) is validated.
- Modify a JavaScript file and it (and only it) is validated and minified.
This is where variables (e.g. paths.js.files
) really demonstrate their value. By changing the value of one variable to contain only the modified file’s path, the subsequent task (PHPLint, JSHint, etc.) will only process that file, not the collection of files they were originally assigned.
Now, rather than requiring 2-3 seconds to validate and compact all JavaScript files, JSHint and Uglify do their job in 0.1 seconds or less. That’s efficiency.
Let Grunt Handle the Grunt Work
Thanks to Grunt, your WordPress plugin development workflow can be vastly streamlined by automating the “grunt work” involved in maintaining and updating a plugin.
Ultimately, this allows you focus on the real work– adding new features, making your plugin better, and releasing updates to users faster. Everyone wins.
What do you think? Are you ready to let Grunt work for you? If you already use Grunt for WordPress plugin development, what have you done to streamline your workflow?
Resources
- Simple Lightbox — A dead simple lightbox plugin built specifically for WordPress.
- Getting Started with Grunt – Automate the tedium out of your projects.
- Grunt Plugins
- WordPress Plugin Guidelines
- Sass — A robust CSS preprocessor that makes stylesheets faster to develop and easier to maintain.
- Libsass — Lightning fast Sass engine
- PHP Command Line Options — Further customize PHP validation with PHPLint