Minify and Compress all your JavaScript files into One, on the Fly

As my applications have grown in complexity, I’ve followed a path probably quite similar to many of you with respect to .js file maintenance. In the beginning I had one js file to include in the site’s/app’s header, containing just a few basic js functions used across the site.

As my JavaScript codebase grew, I added more .js files, trying to specialize the files and even went through the trouble of including specific files on some pages and not on others. Then I adopted a framework (jQuery in my case) and that is just one more script tag.

At some point I became aware of the minifcation trend for JS and CSS files, and began looking at how much bandwidth I could save per page load by doing so.  Using an online minifier, I began minifying each .js file after every modification.  This became very unmanageable very quickly. I also had to consider the impact of multiple file loads on the browser and how that impacts performance.

I decided to find a way to automate this process.  I stumbled upon the JSmin class PHP class, which is an implementation of Douglas Crockford’s JSMin.  The solution would implement JSmin, but with a wrapper class that would read each .js file, minify (and compress if possible), and then output into a single file.  More helpful ideas were found in this blog article at verens.com.

What I came up with accomplishes the following:

– Given an array of .js filenames, reads and minifies each, writes to a single new file.

– Reads file modification date of each file, if none are newer than the auto-generated output file, the process is skipped.

This results in an on-the-fly minifier that only runs when JavaScript code has been modified in any one of the original files.  This makes code deployment simpler….just sync updated js files to the appropriate directory.

I’ve encountered a couple of negatives which are easily mitigated.  First, in production the process is slow…sometimes 15 seconds. That first user to hit the site after a new js file has been uploaded is going to think the server is down.  Remedy this by uploading at off-peak times and immediately surf to the site yourself, saving an unwitting user the 15 second wait.  Second, I’ve experienced some kind of funky file collision on occasion which resulted in the minification running on every page load (think 15 second page loads for every page, every time), so when syncing from test to prod I will typically delete the generated file from test first, so prod can then generate its own clean file.

So here’s the script:

/**
 * Wrapper class for JSMin javascript minification script
 *
 * based on http://verens.com/archives/2008/05/20/efficient-js-minification-using-php/
 *
 * @author Chris Renner
 */
 
include('JSMin.php');
 
class App_Minifier {
 
    /**
     * Constructor not implemented
     */
    public function __construct() {}
 
    /**
     * Concatenate and minify multiple js files and return filename/path to the merged file
     * @param string $source_dir
     * @param string $cache_dir
     * @param array $scripts
     * @return string
     */
    public static function fetch($source_dir, $cache_dir, $scripts) {
 
        $cache_file = self::get_filename($scripts);
 
        $result = self::compare_files($source_dir, $cache_dir, $scripts, $cache_file);
 
        if(!$result) {
 
            $contents = NULL;
 
            foreach($scripts as $file) {
 
                $contents .= file_get_contents($source_dir . '/' . $file . '.js');
 
            }
 
            // turned off due to performance issues on production 6-9-10
            $code = "";
 
            $minified  = JSMin::minify($contents);
 
            $fp = @fopen($cache_dir . '/' . $cache_file, "w");
            @fwrite($fp, $minified);
            @fclose($fp);
 
	}
 
        return $cache_dir . '/' . $cache_file;
 
    }
 
    /**
     * input array of js file names
     * converts array into string and returns hash of the string
     * as the new filename for the minified js file
     * @param array $scripts
     * @return string
     */
    public static function get_filename($scripts) {
 
        $filename = md5(implode('_', $scripts)) . '.js';
 
        return $filename;
 
    }
 
    /**
     * we're going to compare the modified date of the source files
     * against the hash file if it exists and return true if the hash
     * file is newer and
     * return false if its older or if hash file doesn't exist
     * @param string $source_dir
     * @param string $cache_dir
     * @param array $scripts
     * @param string $cache_file
     * @return boolean
     */
    public static function compare_files($source_dir, $cache_dir, $scripts, $cache_file) {
 
        if(!file_exists($cache_dir . '/' . $cache_file)) {
            return false;
        }
 
        $cache_modified = filemtime($cache_dir . '/' . $cache_file);
 
        foreach($scripts as $source_file) {
 
            $source_modified = filemtime($source_dir . '/' . $source_file . '.js');
 
            if($source_modified > $cache_modified) {
                return false;
            }
 
        }
 
        return true;
 
    }
 
}

And here’s how you would call it in your boostrapping file, etc.

// create array of .js filenames to be minified
$scripts = array('jquery', 'jquery.colorbox', 'jquery.livequery', 'jquery.tipsy', 'jquery.validate', 'functions', 'menu', 'childtables', 'datepicker');
 
// call the fetch static method, supplying the source dir, target dir and the scripts array
$scriptfile = App_Minifier::fetch('scripts', 'temp', $scripts);
 
// put the result in a script tag in your html header

Yes I realize that a static class perhaps wasn’t the best choice, but it works and it keeps memory usage to a minimum. I’d probably write it differently today, and may yet refactor it to remove the static.

The output $scriptfile will be a .js filename, generated by hashing the concatenation of all the filenames in the scripts array. This permits different combinations of files to produce different output files, if that’s something you need.

Also note my comment in fetch() about the gzip feature not being used. This caused problems in my particular environment so I’m not using it, but it may work for some of you and I’d be eager to hear from you if it does. To enable, just change line 50 from

$minified  = JSMin::minify($contents);

to

$minified  = JSMin::minify($code . $contents);

In my specific example I was loading as many as 9 different .js files per page load, totaling 250kb. Now all that JavaScript loads in 1 file measuring 147kb.

Oh, and don’t forget to download JSMin.php from github.

Source and Powerpoint from AJAX Made Easy with jQuery

As promised, attached is the source code and presentation from my presentation/demo yesterday. You’ll need a PHP capable web server to run the cities.php file. However, by using a simple txt file with a static JSON string of cities, you could pass the entire city list to your javascript function and do the search filtering there instead of on the server side. Just a thought…

Presentation
Source Code

Sunsetting IE6 on your own

Anyone that does front end web development or web design hates Internet Explorer 6.  Why?  First released in 2001, IE6 is dog slow, very buggy, and essentially completely non-standards compliant.

These issues manifest themselves in two primary categories as best I can tell:  javaScript bugs/behaviors, and layout/CSS behaviors or missing features.

So, developers learn to code around these things.  In CSS this means a lot of hacks and html conditional statements to load custom css, js and htc files to fix many CSS-related deficiencies such as the lack of 24-channel PNG transparency.  As to JavaScript, I’ve found that jQuery cures the need for IE6-specific code, but we continue to see IE6-specific bugs/glitches that are difficult or impossible to isolate, duplicate, or fix.

Unfortunately for enterprise developers, our users seem to be behind the curve when compared to browser adoption. Much of our target audience is still on Windows XP, and depending on IT policies and budget constraints, abandonment of IE6 in the enterprise has lagged behind the consumer segment.

In the enterprise environment I work in, my primary application sees about 200 visits per day across the institution.  About a year ago I began a crusade (no, that is not too strong of a word when the context is IE6) to purge IE6 from the realm of users accessing my web apps.  I had three goals in mind: 1) Reduce userland bugs/glitches and errors caused by IE6, 2) Improve the overall user experience via a superior browser, and 3) Reduce the amount of time I have to spend working around IE6’s craptasticness.

I have to give this caveat before continuing further: As my institution has begun deploying Win7 machines, our IE6 usage has plummeted like a brick.  Admittedly this is the biggest single factor in the reduction of IE6 users against my web apps.  However, there is something to be said for encouraging your users to upgrade and warning them that their experience may be poor with IE6.

Step 1 is to determine the user’s browser when they hit your site.  On my web app’s login page, I use a simple conditional similar to the below example to display a formatted message to the user:

if(eregi('MSIE 6.0', $_SERVER['HTTP_USER_AGENT'])) {
    echo 'You are accessing this site using Internet Explorer 6.0.  We make every reasonable attempt to ensure this site is compatible with IE6, but due to the age and lack of web standards compliance in IE6, you may experience some errors and bugs that are beyond our control.  For best performance, we STRONGLY recommend a more modern browser, such as <a href="http://www.mozilla.org/firefox">Mozilla Firefox</a>, <a href="http://www.apple.com/safari/download">Apple Safari</a>, or <a href="http://www.microsoft.com/windows/Internet-explorer/default.aspx">Microsoft Internet Explorer 8</a>, which are all available for free.  Firefox and Safari are recommended for the best overall experience and performance, though Internet Explorer 7 and 8 are also fully compatible. Less than 7.5% of traffic on this site is from users on IE6.  Therefore, at some point we will make a decision not to support IE6 any longer, as it takes considerable effort to maintain this backward compatibility. PLEASE UPGRADE YOUR BROWSER SOON!';
}

Wrap that message with some bold styling to grab the user’s attention. Then start watching your browser traffic with Google Analytics.

Once IE9 is out of beta, I’ll change the message to include it in the recommended browsers, but for now I want to funnel my users into a browser with some CSS3 support, because I’ve incorporated a lot of it into my app and I think it improves the user experience a great deal.

What I’ve seen in the past few months is a steady reduction in IE6 traffic from the mid 30% range this time last year, to 10% a couple of months ago, down to 7.1% this past week. I believe the biggest reduction has been deployment of Win7 desktops, but the incremental drive from 10% down to current levels seems to be due to the above user warning.

The other thing I’ve been doing is verbally encouraging use of Firefox any time the issue comes up. When I get a call or email about a bug, if I can’t replicate it in Safari then it is almost always a browser specific issue, and when asked the user almost always informs me he or she is using IE6.

Once IE6 usage hits 5% I am going to change the warning message to say that IE6 is no longer supported. Of course I’ll continue to make accommodations, but I’m not going to go out of my way to make IE6 users comfortable in the application–it just doesn’t make sense any longer.

Toggle Table Rows with jQuery, no plug-in required

Last week I encountered a situation with one of our web apps that required a toggle-able table row. Essentially the existing table was populated with information from four different tables. In addition to several joins there were some nested queries, and it was killing the MySQL server.

So, I decided to pull the information from the nested queries out of the rows and use a hidden table row below each existing row where I would show the formerly nested information using an ajax call to retrieve. Removing the nested queries significantly improved query performance.

The biggest problem in this was creating a table row that toggles. The native toggle() jquery function didn’t work at first, and this is because a table row’s display attribute is “table-row”, and not “block” as toggle() resets to.

I found this jQuery plugin by Janko at Warp Speed for doing exactly what I need. It is simple and works quite well with the minimum necessary amount of code. This is accomplished by using jQuery calls to add an anonymous click function to the even rows. However, there are some drawbacks. First, if you observe the demo, notice that a click anywhere in the parent row triggers the toggle.  This is great unless you have a link in the parent row.  What to do if you have, say, a table of employees and you want a link to their full record in the parent row?

Quite unintentionally, after fiddling with jQuery for a couple of hours I came up with a solution.  Only after I tested did I realize I had Janko’s plugin commented out.  I was able to confirm my solution works with no plugin required.  Rather than using the odd/even selectors in jQuery, I have used row ID numbers as you will see in the demo.  To exempt the cell with the link from the anonymous click function, I have added the clickable class to each cell instead.  If you don’t want this extra layer of complexity, just strip out all the parent() business from the JavaScript.

Please help yourself to the code and let me know what you think. View the demo…

P.S. In case you are wondering why I have an empty third row for each record:  This is to preserve the jQuery-based alternating table row highlighting that uses the jQuery even selector.  Since Janko’s solution also relies on this method, it was wiping out my table striping.

Basic AJAX with PHP and jQuery

I gave a live tutorial/demo at work today for room full of folks. I’ve posted the source code and powerpoint below. The title is pretty self explanatory…just a very simple demonstration of AJAX techniques using jQuery JavaScript and PHP on the server side in a very crude Twitter-like mini-posting app.

Basic AJAX with PHP and jQuery – Source Code

Powerpoint Presentation

Add cool, Apple-like reflections to your images

Cow’s Reflection.js allows you to add reflections to images on your webpages. It uses unobtrusive javascript to keep your code clean.

It works in all the major browsers – Internet Explorer 5.5+, Mozilla Firefox 1.5+, Safari, Google Chrome and Opera 9+. On older browsers, it’ll degrade and your visitors won’t notice a thing. Best of all, it’s under 5KB.

This is so simple its almost impossible to screw up.  Step 1) upload reflection.js to your file system. 2) Include in your HTML header using <script></script> tags, 3) add class=”reflect” to any image you want to have a reflection.

Here’s an example:

_0012_front_t

There is going to be a small performance hit as the script processes the image, so I wouldn’t recommend going crazy with this feature inside a web app.  However, for marquee images and static pages, this is a really slick “Web 2.0” addition to any website.  For those developing for a large IE6 installed base, I’d caution against extensive use of this script.  I’ve not tested it, but my guess is with Safari you’d never notice a performance impact.  With Firefox it should be fairly minimal and still worth the trade-off.

And yes, I’m also using this on this site.