Cloud Four Blog

Technical notes, War stories and anecdotes

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 */
}

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');

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:

100
101
102
103
104
105
106
107
108
	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";
	}

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.

5 Comments on “JavaScript GZIP Compression in WordPress: What’s Possible and what Hurts”

  1. Andrew says:

    Lyza, I can understand it's frustrating that WordPress 2.8 doesn't have an option to concatenate and compress scripts and stylesheets added by plugins but consider how that would work. To add these scripts the plugins will have to run, to run the plugins, WordPress has to run. So on each page load WordPress will have to run 3-4 times: once to output the page, second time for the CSS, third and fourth for the head and footer scripts.

    Apart form the extra server load (we are trying to save some energy too) that would be quite inefficient. It is possible to save a pre-made file containing the needed scripts already concatenated and compressed then include that file on the page and any other pages that load exactly the same scripts. However this would require certain server configuration, filesystem access, etc. (yes, that's the same way WP Super Cache and some other plugins work). I'm working on such plugin, perhaps drop me a line if you would like to test it.

  2. Kim says:

    Hey, I've been digging into this "solution" also.

    I think it indeed, comes down to the fact that all compressable javascripts should be in a "default" wordpress scripts directory.

    I managed to get it almost working, but my compressed javascripts did not show up in load-scripts.php, although they were mentioned in the GET parameter.

    If I manage to get it working, I'll let you know! Testing will always be on http://www.wenskaartenshop.be, which is a live size and receives 1000 daily visitors.

    Thanks for your opinion on this issue.
    Kind regards,
    Kim

  3. I realize that is article is about compressing js, but you may want to include define('COMPRESS_CSS', true); also.
    Following is what I have in my template's functions.php file. I am using the google CDN for loading jquery. Google automatically adds compression and the file may not have to be loaded twice if it is already cached on the client's browser.

    I guess some compression is better than none but I am disappointed that full js compressions isn't included in WP at this time.

    function my_init() {
    if (!is_admin()) {
    // comment out the next two lines to load the local copy of jQuery
    wp_deregister_script('jquery');
    wp_register_script('jquery', 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js', false, '1.3.2');
    wp_enqueue_script('jquery');

    wp_deregister_script('jquery-ui-core');
    wp_register_script('jquery-ui-core', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js', false, '1.7.2');
    wp_enqueue_script('jquery-ui-core');
    }

    global $compress_scripts, $concatenate_scripts;
    $compress_scripts = 1;
    $concatenate_scripts = 1;
    define('ENFORCE_GZIP', true);
    define('COMPRESS_CSS', true);

    }
    add_action('init', 'my_init');

  4. Hi,

    I just applied the hack to my blog and it works just fine after I enqueued Jquery and HoverIntent.
    Thanks a million ;-)

    One question please, what if my visitor’s browser doesn’t support gzip, what happens? I hope the site would load normally?

    P.S: Do you mind installing the subscribe to comments plug-in so I can keep up with future replies and comments on this post. Thanks

  5. One more please. Is there a way of setting a far future expiry header for the compressed files?