JavaScript GZIP Compression in WordPress: What’s Possible and what Hurts
WordPress 2.8 introduced some constants and bits and bots that nominally make certain kinds of compression of JavaScript and CSS possible. This post looks at JavaScript compression and explains what is possible, and what is very, very hard.
In a nutshell, with a bit of tweaking, you can GZIP and concatenate the scripts that come packaged with WordPress. GZipping and concatenating anything else–plugin scripts or scripts in your own theme–is not currently supported in any obviously sane way (using WP’s WP_Scripts management class anyway).
I’m going to give away the ending to save you some time:
add_action('wp_enqueue_scripts', 'lyza_force_compress');
function lyza_force_compress()
{
global $compress_scripts, $concatenate_scripts;
$compress_scripts = 1;
$concatenate_scripts = 1;
define('ENFORCE_GZIP', true);
/* enqueue your scripts here */
}
Code language: PHP (php)
Putting the above in your functions.php file may result in insanely good compression on your subsequently-enqueued JavaScript files. I say may because GZIP is one of those fidgety server-side things that varies from hosting provider to hosting provider. I tested this approach on two vastly different environments and had good success, but as always with these things, YMMV.
While this may look rather silly and simple, figuring out exactly which constants and globals to set and when to set them took me just about forever.
The most important thing to note is that this only works on scripts that come pre-packaged with WordPress and are listed in the wp_default_scripts() function in wp-includes/script-loader.php. This is slightly irritating.
When it Does Work
The above will concatenate and GZIP all of the applicable enqueued JavaScript into a single request, served out through the wp-admin/load-scripts.php script.
- By “applicable”, I mean scripts listed in wp-includes/script-loader.php in the wp_default_scripts() function.
- If you have applicable JavaScript in both the head and the foot, you will end up with two script src tags. If everything is in the foot (or the head), you’ll get one tag. You not only get GZipping, you get fewer HTTP requests! Yay!
- JavaScript in plugins and your own theme won’t get concatenated and GZipped in this approach, but it certainly doesn’t break it.
In testing, the following:
wp_enqueue_script('jquery');
wp_enqueue_script('thickbox');
wp_enqueue_script('scriptaculous');
wp_enqueue_script('editor');
Code language: PHP (php)
Went from:
10 requests, 310kB
to
2 requests, 90kB
with the activation of the above function. Nice! I got two requests because the above enqueueing puts stuff in both the head and the footer.
That’s a 340% improvement on filesize alone.
Why Won’t It Work for All JavaScript?
Because there are no, nada, zilch, none hooks for making it do so without doing some deep surgery and writing a plugin. It boils down to a couple of places in code:
From wp-includes/class.wp-scripts.php in the do_item() function:
if ( $this->in_default_dir($srce) ) {
$this->print_code .= $this->print_scripts_l10n( $handle, false );
$this->concat .= "$handle,";
$this->concat_version .= "$handle$ver";
return true;
} else {
$this->ext_handles .= "$handle,";
$this->ext_version .= "$handle$ver";
}
Code language: PHP (php)
To get the built-in concatenation and GZIPped delivery, it is necessary that the if statement here evaluate true. I won’t explain the nitty gritty unless you really want me to.
So, my first approach was to add paths to the $wp_scripts->default_dirs Array to add entries for my theme’s scripts and my plugin dir’s scripts. Big fat fail.
The reason is that the actual file — wp-admin/load-scripts.php — that serves up the concatenated, zipped JavaScript is a standalone script. I imagine that this is both for security and for performance. It does not have the complement of WordPress constants and hooks that we are accustomed to. In fact, it is completely and absolutely unpluggable (unless I missed something). It only includes the files that define the WP_Scripts and WP_Dependencies classes, and utility functions for those. It does not include any theme- or plugin-level items.
And it does something that completely shuts down the game. It instantiates a new, clean WP_Scripts object and then calls wp_default_scripts().
If you’ll recall, the WP_Scripts object in a normal WordPress request is a global singleton that manages all of the enqueued scripts. By creating a new instantiation, the load-scripts.php script has obliterated any notion of any enqueued scripts. Further, the wp_default_scripts() function is a hard-coded list of the JavaScript files that WordPress comes packaged with. While this function nominally triggers the ‘wp_default_scripts’ action, this is not usable here because no files are getting included in which I could take advantage of that hook. Further, it redefines do_action() and other API functions as blank functions that do nothing.
The load-scripts.php file then iterates through the requested JavaScript handles (comma-delimited GET) and sees if it recognizes them from the wp_default_scripts() list. As anything that’s in your own theme or in a plugin won’t be in this list, FAIL.
Though I see some possible options for me in the writing-a-plugin realm, I also sadly see that it would replicate functionality and am highly curious as to what the WordPress developers had in mind here.