Improving Versioning of WordPress Static Files

I was recently asked to improve the PageSpeed and YSlow scores of a WordPress-based website that I work on. One of the items that PageSpeed checks is “Don’t include a query string in the URL for static resources,” (under the “Leverage Proxy Caching” heading). I suspect this is most important when you are using a CDN, or otherwise run through a proxy server. WordPress puts a version in the query string of any JavaScript or CSS that’s enqueued using wp_enqueue_script() or wp_enqueue_style().

Interestingly, PageSpeed doesn’t seem to complain about the WordPress-included static resources that have a query string on the URL, only about static resources on 3rd-party URLs. I’m not sure why this is, but let’s remove the query strings from the WordPress-included static resources anyway, while still maintaining a version somewhere in the URL so we can set far-future Expires headers.

The format of a WordPress version tag is ver={version number}, like this: example.com/wp-content/themes/theme-name/style.css?ver=2.0.5.4, but example.com/ver2.0.5.4/wp-content/themes/theme-name/style.css should be more cacheable. There are two steps to making this happen:

1) Use Apache’s mod_rewrite to Serve the Correct File to the Updated URL

In step 2 we’ll be moving the version tag to the beginning of the URL, we only need one RewriteRule to make it work:

# This goes in your .htaccess file, before the WordPress section
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

## Cache-Busting ##
RewriteRule ^ver([\d\.\-a-zA-Z]+)/(.*) $2?ver=$1 [L] #match something with the version tag right at the beginning of the URL
## Done cache-busting ##

</IfModule>

The rule above matches any URL that starts with “ver” followed by any number of letters, numbers, periods, dashes, and underscores, captures that collection of characters up to the first forward slash, then it captures everything after the first forward slash. The URL is rewritten as $2?ver=$1 where $2 is everything after the version tag, (the original URL, with no version tag), and $1 is the version number that’s added, internally, to the query string, but never appears in the browser.

Note: It is probably possible to leave off the ?ver=$1, but I haven’t tried it yet.

Now example.com/wp-content/themes/theme-name/style.css?ver=2.0.5.4 and example.com/ver2.0.5.4/wp-content/themes/theme-name/style.css should both serve up the same style.css file.

2) Filter the URLs for Enqueued Scripts & Styles to Move the Version Tag from the Querystring into the Path

The URL for every enqueued Javascript is filtered by the script_loader_src filter, and the URL for every enqueued stylesheet is filtered by the style_loader_src filter, so we’ll use these filters to put the new, improved URL in the HTML. This is an absolute URL, so we can take it apart, move the parts around so that it looks how we like. Here’s the function I’m using to change the URLs, plus two lines to hook on to the filters:

/**
 * Changes WordPress's default versioned URLs from putting the verion in the
 * querystring to putting the version as part of the file page, (requires 
 * .htaccess modification to work). If there's no version in the querystring
 * one can be added by setting the $defaultversion.
 * @param  string  $url            The URL to change the location of the version.
 * @param  mixed   $defaultversion A string default verion. Defaults to false, (
 *                                 no default version).
 * @return string                  The new URL.
 */
function jb_better_versioned_url( $url, $defaultversion = false ) {

    // don't bother for the admin
    if ( ! is_admin() ) {

        // parse the URL
        $parts = parse_url( $url );

        // and the querystring
        $query = wp_parse_args( $parts['query'], array() );

        // check if there's a version in the querystring. If so, do more.
        if ( isset( $query['ver'] ) || false !== $defaultversion ) {

            if ( isset( $query['ver'] ) ) {
                // prepend "/ver" + {version value} to the *path*
                $parts['path'] = '/ver' . $query['ver'] . $parts['path'];

                // unset the version in the querystring, since it's in the path now.
                unset( $query['ver'] );
            } else {
                $parts['path'] = '/ver' . $defaultversion . $parts['path'];
            }

            // if this is a PHP file just let it be.
            if( ! preg_match( '~\.php$~', $parts['path'] ) ) {
            

                // start rebuilding the URL string
                $url = $parts['scheme'] . '://' . $parts['host'] . $parts['path'];

                // see if there's still anything in the query
                if ( count( $query ) > 0 ) {

                    // rebuild query with whatever remains
                    $parts['query'] = build_query( $query );

                    // append it to the URL string
                    $url .= '?' . $parts['query'];
                }
            }
        }
    }

    return $url;

}

// hook onto the filters
add_filter( 'style_loader_src', 'jb_better_versioned_url' );
add_filter( 'script_loader_src', 'jb_better_versioned_url' );

With these two modifications all of your JavaScript & CSS will have the version in the path instead of the querystring. In addition, any images, fonts, or anything else referenced with relative URLs from your stylesheets will have the version in the beginning of their URL, which will be rewritten and the file served properly. This way if you ever have caching problems you can bump a version number and browsers will pull new versions of all your static resources.

A Word of Warning

This involves considerable coding acrobatics, and adds complexity to your WordPress-based site, for speed improvements that may not be very large, and may not exist at all, so use this method at your own risk.

I have this code in production, but would love to hear the opinions of others on it, that’s what the comments are for, or Twitter, or elsewhere – just let me know where the technique is discussed.

Conditionally Loading Javascript & CSS with Fancybox for WordPress

The Problem

I’ve been working on improving the performance of a client’s WordPress-based website recently and it’s become very clear to me just how much CSS & Javascript plugins load, even when it’s not needed.

On this particular website 47% of the Javascript and 57% of the CSS loaded on the site’s homepage is not required on the homepage, but is required elsewhere, so it should be loaded conditionally. Sometimes this is easy, but sometimes it’s not.

Fancybox for WordPress is not an easy plugin to make load client-side resources, after all, it’s made to show a lightbox on any image, and especially on any gallery. When the wp_enqueue_scripts action happens, where it is recommended that we set the scripts and styles that will be used on a page, we don’t yet know what will be on the page. Fortunately, we can call the wp_enqueue_script() function after that, during the body of the page, and those scripts will be printed in the wp_footer() function. The same is true for wp_enqueue_style(). So, here’s what we do:

  1. Stop Fancybox for WordPress from including its scripts & styles by default.
  2. If a gallery or image that we want to use Fancybox on is displayed, we tell WordPress to display the Fancybox scripts & styles in the footer.
  3. Enjoy faster pageloads.

Sounds simple, but there are a few interesting bits. Now for the code.

The Code

I use three functions to check if the Fancybox scripts & styles are needed. They can be in a theme’s functions.php, (where I have them at the moment), or they could even be rolled into their own plugin.

First tell Fancybox not to include its code by default. Instead of manually dequeuing every style & script, remove the Fancybox functions that do the enqueuing from the wp_enqueue_scripts and wp_head action hooks:

// don't enqueue Fancybox scripts by default.
function jb_dequeue_fancybox_css_js() {
    remove_action( 'wp_enqueue_scripts', 'mfbfw_styles' );
    remove_action( 'wp_enqueue_scripts', 'mfbfw_scripts' );
    remove_action( 'wp_head', 'mfbfw_init' );
}
add_action( 'wp', 'jb_dequeue_fancybox_css_js' );

Next create a function that re-enables Fancybox, and remembers that it did so. Here there are two static variables, which will hold their value between function calls, (kind of like hidden globals), which store state. The $fancybox_is_used variable is returned so this function can be called with no arguments to find out if Fancybox has been used on page. Note the priority of 25 when hooking mfbfw_init() to the wp_footer action. This is needed because mfbfw_init() prints some Javascript to the page that relies on jQuery, and WordPress prints the enqueued scripts in the wp_footer action with a priority of 20, so mfbfw_init() needs to execute with a priority higher than 20.

function jb_fancybox_is_used( $used = false ) {

    // this is returned so we can call this function with no arguments to learn
    // if Fancybox has been used on a particular page.
    static $fancybox_is_used = false;

    // remember if Fancybox has been re-enabled already, so we don't enqueue the 
    // scripts multiple times
    static $fancybox_is_setup = false;

    if( $used === true ) {
        $fancybox_is_used = true;
    }

    if( $fancybox_is_used && ! $fancybox_is_setup ) {
        if( function_exists( 'mfbfw_styles' ) ) {
            mfbfw_styles();  // enqueue fancybox styles
            mfbfw_scripts(); // enqueue fancybox scripts
            // the 25 is important. WordPress prints footer scripts in the
            // wp_footer action with a priority of 20, and mfbfw_init() has to
            // be called after the footer scripts are already on the page.
            add_action( 'wp_footer', 'mfbfw_init', 25 ); 
        }
        $fancybox_is_setup = true;
    }

    return $fancybox_is_used;
}

Finally, make a function that looks for places where Fancybox is used on the page. On the site I’m working on the CSS class fancybox is used on images and in galleries that I want to be Fancyboxed, so I look for the string “fancybox” in the_content filter, and when it’s found I call jb_fancybox_is_used( true ) to re-enable Fancybox on the page. I added this to the_content with priority 11 because shortcodes, including gallery shortcodes, are executed at priority 10, and I want to be able to look through the output of short codes for the fancybox CSS class:

function jb_hunt_for_fancybox( $content ) {

    if( false !== stripos( $content, 'fancybox') || false !== stripos( $content, 'thickbox' ) ) {
        jb_fancybox_is_used( true );
    }
    return $content;
}
add_filter( 'the_content', 'jb_hunt_for_fancybox', 11 );

If you include a something you want to fancybox in a template you can call jb_fancybox_is_used( true ) manually from the template file to include the CSS & Javascript.

Other Ways

This isn’t the only way to conditionally include Fancybox’s Javascript & CSS. Instead of using jb_hunt_for_fancybox() to filter the_content there’s probably an action or filter in the gallery shortcode that jb_fancybox_is_used() could be hooked onto. It may even be possible to use the $wp_query object in an action hook just before wp_enqueue_scripts to determine if there is content on the page that needs to be Fancyboxed, let that decide whether or not to run jb_dequeue_fancybox_css_js(), and forget about the other two functions.

Let’s Do Better

Plugin authors should be working hard to only add what is needed to each page load. Who is doing a great job? How can we hack our themes to bend other plugins to our will? Comment or tweet @johnbeales to let me know.

Generating Form Elements with Javascript in IE 10

There’s a DOM manipulation gotcha in IE10 that just got me, and Google didn’t help much, so I’m giving Google something to show anyone who has this problem in the future.

When you dynamically generate a form input in Internet Explorer 10 the you must set the type attribute before doing anything else to the element, otherwise any values you set will be ignored when submitting the form, and in some cases will not be displayed. When inspecting elements using IE’s developer tools, however, the correct value appears in the generated document tree.

So this works as expected:

var sub = document.createElement('input');
sub.setAttribute('type', 'submit');
sub.setAttribute('value', 'Submit Generated Button');


But this doesn't:

var sub = document.createElement('input');
sub.setAttribute('value', 'Submit Generated Button');
sub.setAttribute('type', 'submit');

This is particularly hard to catch with radio buttons and checkboxes, (this is what got me). Their default value is “on” which doesn’t tell me much, especially if you’re submitting an array of them.

Here’s a demo. The first button shows its value, (and submits its value), and the second shows, (and submits) the default of “Submit Query.”

The only documentation I found on this behaviour is a passing sentence in the createElement documentation at MSDN. When they say “then set the type property to the appropriate value in the next line of code” they’re serious about the next line of code.

Also, who chooses “Submit Query” as a default value for a submit button in 2013 anyway? Are they trying to confuse people? Shouldn’t it just be “Submit”?

Getting Started with HTML5

I’m working on a project now were we’ve decided to go with as pure HTML5 as posible, and it’s a breath of fresh air. Things work more or less how they should, and Internet Explorer is even playing along, with a little help. Getting started was a bit of a trick, though, as it can be hard to find information on how HTML5 works without diving into specification documents, which is never fun, or easy, (if you don’t want to read the story, skip straight to the resources).

I hadn’t been following the development of HTML5 with more than a passing interest. I figured that when it was ready, then I would start using it. I also understood that there were different parts that may reach completion at different times, and was keeping my eye open for some sort of “completion” signal. 2009’s 24 Ways was that signal for me. There were several articles on using HTML5 features along with their CSS3 counterparts, and enough evidence that browser support is there to start my investigation.

Here’s the deal: Basic HTML5 support is pretty good in webkit-based browsers, alright, (read usable), in Gecko, and kind of lacking in Internet Explorer. However, if you can rely on Javascript being present, (which I can in my project), there’s an HTML5 Shiv Javascript by Remy Sharp that makes it so that you can style HTML5 in Internet Explorer. Add it using a conditional comment and you’re good to go.

So, we have useable cross-browser support, but where do we turn to learn about which tags are in, which are out, the correct doctype and mime-type, and all that? We could read the specification, (and we will have to read a bit, at least), but it would be nice if there was an introduction to HTML5 somewhere. It turns out that Robert Nyman has written an Introduction to HTML5. It’s detailed enough to get you started, but not so detailed that you get lost, (like the spec), and if you’re looking to be convinced of the value of HTML5, check out HTML5: Tool of Satan, or Yule of Santa?, Have a Field Day with HTML5 Forms, and Breaking out the Edges of the Browser from 24 Ways 2009.

Once you dive a little deeper you’ll find that there are elements of HTML5 that you need more in-depth information for, so it’s time to turn to the spec. However, there are 2 groups, (W3C and WHATWG), working on HTML5, and therefore 2 spec documents, (fun!). Fortunately, the two groups have the same editor, so they’re more or less working on the same thing. I find the WHATWG HTML5 document easier to read, but if you prefer the W3C version, go nuts.

Finally, the whole content-type debate that’s been going on for what seems like centuries is still a mess. In HTML5 you’re supposed to include a Document Type Definition and there should be no namespaces on the HTML element if you’re serving as text/html, and you’re supposed to serve in application/xhtml+xml if you want to use namespaces, or force XML validation, or anything like that. The problem is that Internet Explorer really doesn’t like application/xhtml+xml, (it shows the raw XML document), so if you need a namespace for some reason, (for example, you want to use Facebook Connect on the site), you can’t serve valid markup.

So, that’s it. HTML5 has arrived, or at least parts of it. If you can rely on Javascript being present, or rely on IE users not using your web app, you can go ahead and start using it. Here’s a quick recap of the resources:

Using Spaces to Work Around VMWare Fusion’s Limitations

It appears that when running Windows as a virtual machine in VMWare Fusion, (I’m using version 3.0.1), any windows programs that I am running with elevated permissions, (“run as administrator”), don’t appear in the OS X dock with Fusion in unity mode. What’s more, if I can see a portion of the window and I click it, it doesn’t come to the foreground.

My workaround for this, which worked very well, was to run Fusion in fullscreen mode in another Space. Then I was able to do my OS X testing in one space, flip to Windows to make changes very easily, (The default to move between spaces is ctrl + (arrow key), but I changed it to cmd + (arrow key) and it is much easier for me), and keep my E-mail & stuff open in a third space.

If you don’t have spaces active, in OS X 10.6 they’re in System Preferences > Exposé & Spaces > Spaces then just check the box that says “Enable Spaces” and adjust your shortcuts at the bottom of the window, if desired.