05 Jan 2012

By Nick Benson of PressEnter!   Filed Under ExpressionEngine   Comments Comments
Tags developer tips, javascript, search engine optimization, seo

As a savvy developer, you already understand there are lots of good reasons to optimize the performance of your websites - users, search engines, and clients all love it when content loads in a snappy fashion.  In this article, we’ll review some of the mechanical factors that impact the performance of the JavaScript and CSS that’s included in an ExpressionEngine site.

Don’t Download It if You Don’t Need To

Perhaps the quickest way of saving time and bandwidth is to prevent your users from having to download something from you in the first place.  Use Google’s CDN to serve any of the popular JavaScript libraries (jQuery, jQuery UI, Prototype, etc.) that you’re including on your site.  This has two advantages: one, Google delivers the libraries free of charge over a fast, reliable network; two, if a user has already visited another website that included said file, it will already be cached and stored on their browser, so they won’t have to download it again.

Compression & Minification

One technique for optimizing CSS and JavaScript files is to make them as small as possible, so they download quickly.  The first step in compressing CSS or JS is minification.  In short, minification removes all of the formatting that makes your code easy to maintain, but is functionally unnecessary – little things like comments, tabs, and human readable function and variable names.

The minification process will transform poetically structured CSS like this:

/**************************************************************
Wide Column
***************************************************************/

@media screen and (min-width: 768px){
  #home .layout-wide-container{
    width: 63.889%; /* 644px / 1008px */
    float: left;
  }
}

Into this horrid (but functional) mess:

@media screen and (min-width: 768px){#home .layout-wide-container{width:63.889%;float:left}

Once our files are minified (stay tuned for details on how to do that), the next step is Gzip compression.  If a client’s browser supports Gzip (and all of the modern ones do), the server will essentially send a .zip version of each requested file to the browser, which also saves time and bandwidth.The folks at HTML5 Boilerplate have an excellent bit of code for enabling Gzip via .htaccess.

Note: ExpressionEngine has an option hidden away in the control panel for enabling Gzip – that method only applies to resources served by EE directly, and isn’t as efficient as enabling via .htaccess.  In other words, don’t enable Gzip via ExpressionEngine.

Meet Minimee

Minimee is an ExpressionEngine add-on that automatically minifies, combines, and caches the JavaScript and CSS files that are included in your site.

The best way to quickly appreciate what it does is to see a template that includes a Minmee tag:

{exp:minimee:css}
  <link rel="stylesheet" href="http://pressenter.com/?css=site/.css.reset.v.1326840194">
  <link rel="stylesheet" href="/scripts/superfish/superfish.css">
  <link rel="stylesheet" href="http://pressenter.com/?css=site/.css.layout.v.1326838524">
  <link rel="stylesheet" href="http://pressenter.com/?css=site/.css.typography.v.1325543635">
  <link rel="stylesheet" href="/scripts/syntaxhighlighter_3.0.83/styles/shCore.css">
  <link rel="stylesheet" href="/scripts/syntaxhighlighter_3.0.83/styles/shGWcodeLive.css">
{/exp:minimee:css}

{exp:minimee:js}
  <script src="/scripts/typekit.js"></script>
  <script src="/scripts/modernizr.custom.2.0.6.js"></script>
  <script src="/scripts/head.load.min.js"></script>
{/exp:minimee:js}

… and then see the output it produces:

<link rel="stylesheet" href="http://pressenter.com/cache/1325707364722f4dc6fa7da32bc867ad730b1c2326.css">

<script src="http://pressenter.com/cache/132570556499e4079b7fd2843e25eebdf24c1c8846.js"></script>

As you can see, Minimee has combined all of those CSS and JS resources into a pair of minified files.  They have unique, cache busting names, and won’t change unless the JS or CSS contained within the sources change.  In other words, users will end up downloading two files (one for CSS, one for JavaScript) instead of a plethora of them.

Note: Minimee will only update the cache files when certain local resources (JS files, CSS files, or EE templates with CSS code on your server) change; it doesn’t automatically update if you include a remote resource or an EE template with JavaScript.

JavaScript Belongs in the Footer… Right?

Traditional wisdom held that JavaScript files should be included in the footer of your HTML, as the browser has to wait for JS files to load before it can render a page.  There are a few libraries that need to be in the header (Typekit and Modernizr are good examples), but, with enough finagling, it’s usually possible to get the rest of your JS in the footer, where it won’t interfere with page rendering.  Some of these headaches can be avoided if your JavaScript is loaded asynchronously.

Meet Head JS

Head JS allows you to include JavaScript in the header by including other scripts asynchronously; in short, it downloads the JS in the background while the page continues to render.  While Minimee isn’t compatible with Head JS yet, it is a good way to grab jQuery (and any other libraries you might need) from Google’s CDN.

Note: By default, Head JS also includes HTML5 shivs and feature detection a' la Modernizr.  If you’re only interested in the asynchronous resource loading, grab head.load.min.js.

Putting it All Together in ExpressionEngine

Keeping Minimee’s current limitations in mind (at the moment it cannot return a path to the JS file needed for Head JS), we take a practical, compromise approach, which allows us to keep fairly maintainable code while taking partial advantage of asynchronous resource loading.

We’ll include three items in our header:

  • Minimee’d CSS
    • Reset
    • Any CSS included with JS libraries
    • Layout, etc.
  • Minimee’d JS
    • Asynchronous Typekit
    • Modernizr
    • Head JS
  • Head JS
    • Any Resources needed from Google’s CDN

And finally, in our footer:

  • Minimeed’d JS
    • Any JS that you need to combine and minify, and doesn’t require being loaded in the head.  This code can eventually move up to Head JS when Minimee is updated to support it.

Example Header Template

{exp:minimee:css}
  <link rel="stylesheet" href="http://pressenter.com/?css=site/.css.reset.v.1326840194">
  <link rel="stylesheet" href="/scripts/superfish/superfish.css">
  <link rel="stylesheet" href="http://pressenter.com/?css=site/.css.layout.v.1326838524">
  <link rel="stylesheet" href="http://pressenter.com/?css=site/.css.typography.v.1325543635">
  <link rel="stylesheet" href="/scripts/syntaxhighlighter_3.0.83/styles/shCore.css">
  <link rel="stylesheet" href="/scripts/syntaxhighlighter_3.0.83/styles/shGWcodeLive.css">
{/exp:minimee:css}

{exp:minimee:js debug="yes"}
  <script src="/scripts/typekit.js"></script>
  <script src="/scripts/modernizr.custom.2.0.6.js"></script>
  <script src="/scripts/head.load.min.js"></script>
{/exp:minimee:js}

<script>
  head.js("http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js", "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js");
</script>  

Example Footer Template

{exp:minimee:js}
  <script src="/scripts/superfish/superfish.js"></script>
  <script src="/scripts/syntaxhighlighter_3.0.83/scripts/shCore.js"></script>
  <script src="/scripts/syntaxhighlighter_3.0.83/scripts/shBrushSql.js"></script>
  <script src="/scripts/syntaxhighlighter_3.0.83/scripts/shBrushEE.js"></script>
  <script src="/scripts/syntaxhighlighter_3.0.83/scripts/shBrushPhp.js"></script>
  <script src="/scripts/syntaxhighlighter_3.0.83/scripts/shBrushJScript.js"></script>
  <script src="/scripts/syntaxhighlighter_3.0.83/scripts/shBrushCss.js"></script>
  <script src="/scripts/syntaxhighlighter_3.0.83/scripts/shBrushXml.js"></script>
  <script src="/scripts/footer.js"></script>
  <script src="/scripts/css3-mediaqueries.js"></script>
{/exp:minimee:js}

Please note, if you have any code that relies on jQuery, or any other resource being loaded from Head JS, be sure to include them in head.ready() tags, as there's a possibility they may try to execute before jQuery has finished loading:

head.ready(function() {
  // Something that relies on jQuery here
});

Faster Page Loads, Improved SEO, Happier Users

With minification, combination, Gzip, and asynchronous loading, you should observe a noticeable improvement in the performance of your ExpressionEngine website.  If you'd like immediate, quantitative feedback, review your YSlow and Page Speed scores before and after implementation - you'll pick up several points on both.

Comments