bradleyboy :: the online home of Brad Daily

AssetPackager for CakePHP

This past week, web geeks were abuzz with the news of ySlow, a Firebug plugin released by Yahoo! that analyzed the loading of your web site and made suggestions on how to improve it. ySlow gives your site a letter grade, which is really genius as nothing presents a challenge like seeing your website score an “F”.

Of particular interest to me was seeing how the new version of SlideShowPro Director fared. Not good. We got an F and it was in the mid 40s at that. Our biggest offense was loading 18 different javascript files and several CSS files as well. Also, we weren’t doing anything to minimize our CSS or JS files. Removing white space, line breaks, etc can save a lot of bandwidth.

Turns out that in the Rails world, there is already a tool to help you do this. It’s called AssetPackager. So, needing that very functionality in CakePHP, I set out to create AssetPackager for CakePHP.

The goal of AssetPackager is to have the best of both worlds. Having separate javascript and CSS files eases development headaches, so we want to maintain the ability to do that. Once in production, we want to combine and compress assets for performance. AssetPackager for CakePHP does this through a Cake console script and a custom helper. This only works with the Cake 1.2.x.x branch, since it relies on the new console.

Installation
Download AssetPackager for CakePHP here. Once you unzip the package, you should find a single “vendors” folder. Place the contents of that folder into either of your CakePHP vendors folders (ROOT/vendors or APP/vendors). That’s it for installation.

Configuration
One important part of any combination/compression strategy is the order in which the files are added. Often times (in both CSS and Javascript) the order that the files are loaded in is extremely important. AssetPackager for CakePHP maintains this order through the use of configuration files.

Throughout this article, I’m going to be using a javascript example but the same technique is used for CSS as well.

First, create a new file in app/webroot/js and name it _asset_config.php. Inside, we will define the different “groups” that we want combined. For example, let’s say I have the following javascript files: main.js, form.js, ajax.js. At runtime, we want all these to be combined to one file and compressed. In app/webroot/js/_asset_config.php we add this:

Code (php)
  1. $groups = array( ‘base’ => array( ‘main’, ‘form’, ‘ajax’ ) );

This defines a group (base) and its members. It also defines the order in which we want the files to be combined. You can add as many groups as you like:

Code (php)
  1. $groups = array( ‘base’ => array( ‘main’, ‘form’, ‘ajax’ ), ‘another’ => array( ‘main’, ‘form’ ) );

The View
The second step is using the custom helper included with AssetPackager for CakePHP to write our includes into the page. First, make sure your controller has initialized the asset_helper. (I do this in app_controller, since I use it throughout the application).

Code (php)
  1. vendor(‘asset_packager/asset_helper’);
  2.  
  3. class AppController extends Controller {
  4.     var $helpers = array(‘Asset’);

Now to the view itself. Before, you would have had something like this:

Code (php)
  1. e($javascript->link(‘main’));
  2. e($javascript->link(‘form’));
  3. e($javascript->link(‘ajax’));

We can now replace that with:

Code (php)
  1. e($asset->js(‘base’));

Here’s how it works: If your application is in any DEBUG level other than 0 (thus indicating you are in development mode), the asset_helper will write in the includes one at a time just like you were doing before. However, if you are in DEBUG level 0 (production), the asset_helper looks for a file in your js folder like this: base_1185637913.js. The timestamp ensures that your end users always get the new version, regardless of whether they had the previous version cached.

So, how does that base_1185637913.js file get created?

The Console
The heart of AssetPackager for CakePHP is a Cake console script. First, for a help screen of all the options and commands, enter:

Code (shell)
  1. /path/to/console/cake asset_packager help

The easiest way to run it is interactively, like so:

Code (shell)
  1. /path/to/console/cake asset_packager

Once it has started, it will ask you what assets you would like to combine. Since we are doing just javascript right now, we enter J. It loads your config file, combines all the javascript into one file, then uses JSMin to strip all the whitespace, line breaks, etc out of the file (NOTE: JSMin requires PHP5, if you are on PHP4, AssetPackager for CakePHP will silently skip this step). The finished file is then named using the group name (base in our case) and a timestamp. If multiple versions exist in the js folder, the asset_helper always loads the most recent version. To clean up old versions, just run:

Code (shell)
  1. /path/to/console/cake asset_packager clean

That’s it! Try changing your DEBUG setting to 0 and watch what happens. All your individual includes should now be replaced with one include to your combined, compressed javascript file.

AssetPackager for CakePHP is released under the MIT license, so feel free to modify to your own tastes.

You are reading an archived post, written on Saturday, July 28th, 2007. Feel free to leave a comment or trackback from your own site.

» Next post:
   All blogged out

« Previous post:
   Movin’ on up

12 Responses to “AssetPackager for CakePHP”

  1. Max Says:

    Hi Bradley,

    Thanks for creating a promising Tool !
    What was the YSlow score after using the AssetPackager on SlideshowProDirector ?

    Cheers,
    Max

  2. bradleyboy Says:

    Max - Everything else being equal, this change alone boosts the ySlow score by 16 points. YMMV, as Director uses a *lot* of javascript.

  3. Sam’s random musings » Mod Expires Says:

    [...] Yahoo! recently released an amazing tool called “YSlow”. It helps analyze the pages you’ve built and find problem points and bottlenecks. As bradleyboy said, there is nothing quite like working to fix that letter grade. [...]

  4. Grant Cox Says:

    This is very cool. It’s something that should really be used on every project (no real reason not to), but for that it could be a little more automated and transparent.

    Manual updating via console. I just know that people will forget this step after updating their JS, and pull their hair out wondering why the compressed version just doesn’t work properly. How about it automatically generates the compressed files if they don’t exist? Perhaps running as DEBUG > 0 destroys any existing compressed versions (in case they are out of date), and so the first request of DEBUG 0 will create them again? It would be one additional file_exists check every production request, but you’ve probably got higher overhead than that with the current timestamp method (I havne’t looked at your code yet).

    The config file. I usually have a few CSS / JS includes in each layout which would be easily replaced as you displayed above. But I also usually have custom CSS / JS includes in each of my views - and I would like these to be included in the single page JS/CSS, without having to manage combinations in the config file.
    What about if this helper worked like RosSoft’s HeadHelper ( http://rossoft.wordpress.com/2006/03/28/register-head-tags-from-helpers-2/ ), or the “echo $scripts_for_layout;” that Cake 1.2 provides? The AssetPackager can generate some identifier from all the script names that are passed to it, and automatically know what file to actually generate/include.

    I’m busy on a non-Cake project at the moment so I can’t really implement these myself just yet. But if you don’t do it first I do plan to - I’m using a customised version of the HeadHelper at the moment and this would be a good addition.

  5. Geoff Ford Says:

    Awesome utility. It’s going into to my collection :)

    I also would like to see it a little more automated. I like the idea of in DEBUG = 0 it just has a file_exists() and skips all the timestamp stuff.

    Great work.

  6. bradleyboy Says:

    Grant & Geoff - Thanks for the comments, good input.

    One thing that is important to point out is that the timestamp portion of the filename is key because of another optimization trick - Expires Headers. With the unique filename, you can set the expires header for your javascript and CSS files to way out in the future, since any change to those files will result in a new filename and thus your user’s browser cache of your previous file won’t cause problems.

    I understand your points with regards to automation, but I think that is a job left for your deployment process. I use Capistrano for deployment, so I just dropped in a line to my deploy recipe to run AssetPackager after the latest code has been put in place. Same thing for our packaging script for SlideShowPro Director. I think one thing many Cake bakers can learn from the Rails crowd is good, automated deployment.

    That being said, the script ought to have a fallback of some sorts should the compressed version be absent. Maybe if no compressed version exists we just revert to including all the singles just like we do in development and print a warning via a comment in the HTML.

    Anyway, all that said I’m still open to any code submissions you might have to make the cleaner/easier.

  7. Grant Cox Says:

    Bradley - I agree that this could be done by a deployment script, and is probably a better way to do it. However, I generally have to work in a studio full of designers, and even version control is too complicated for them. It would be nice if we could enforce a “only developers update the live site”, but the fact is they are used to FTP and it is more efficient to let them edit CSS directly. And I haven’t used Capistrano before, but ANT tasks weren’t so amazing that I wanted to enforce them!

    I have been playing around with implementing this functionality into the HeadHelper, and it’s been going quite well.

    I’ve realised that compressing all of the JS into a single file isn’t necessarily more efficient. For the cases where I have a little custom JS on each page, it means that the larger libraries are duplicated into each page’s unique package. So I’ll rework this to support grouping, causing more than a single package to be created.

    Also, the file naming is a little annoying. As you pointed out it is a good idea to have a unique filename for each modified version to avoid browser caching. However, when a new version is created it would be nice to destroy all the old ones (just to clean up), and keeping track of these is annoying. I appreciate the simplicity of the config file :)

    Anyway, I’ll keep on plugging. It currently supports JsMin and Packer for JS, and CSS Tidy for the CSS. And no change to any view files, as I already use the HeadHelper for all includes.

  8. Automatic Asset Packer CakePHP Helper :: PseudoCoder.com Says:

    [...] A little while ago I wrote a post about how I manually combine/pack Javascript files. Even as I was writing it I was thinking “hey, this would be a great CakePHP plugin/component/helper. Before I could code it Brad Daily from beat me to it. However there are a couple of things I didn’t like about his method, particularly the need for configuration files (how anti Cake!) and the need to run a console script to generated the packed versions. [...]

  9. Aaron Shafovaloff Says:

    Wonderful! I can put this to immediate use!

    PS Beautiful blog template.

  10. Aaron Shafovaloff Says:

    Bradley, this needs to be updated for the latest SVN build of CakePHP 1.2. They aren’t using global variables anymore like DEBUG. Try:

    Configure::read(’debug’);

    Also, I think the way they do the console has changed?

  11. goed Says:

    Thanks for the tutorial, this helped me alot and saved me tons of time :)

    regards,
    goed

  12. Chris Says:

    Yes it no longer works… even with the configure::read it gives an error:
    Notice (8): Undefined offset: -1 [APP\vendors\asset_packager\asset_helper.php, line 48]

Leave a Reply