The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Apache2::Response::FileMerge - Easily merge JavaScript and CSS into a single file dynamically.

SYNOPSIS

Apache2::Response::FileMerge gives you the ability to merge, include, minimize and compress multiple JavaScript and CSS files into a single file (respective of type) to place anywhere into an HTML document. All handled by an easy to configure mod_perl Response handler with absolutely no alteration needed for existing JavaScript or CSS.

DESCRIPTION

Problem(s) Solved

There are a number of best practices on how to generate content into a web page. Yahoo!, for example, publishes such a document (http://developer.yahoo.com/performance/rules.html) and is relatively well respected as it contains a number of good and useful tips for high-performance sites along with sites that are less strained but are still trying to conserve the resources they have. The basis of this module will contribute to the resolution of three of these points and one that is not documented there.

File Merging

A common problem with the standard development of sites is the number of <script/>, <style/> and other static file includes that may/must be made in a single page. Each requiring time, connections... overhead. Although this isn't a revolutionary solution, it is in terms of simple mod_perl handlers that can easily be integrated into a single site. Look to 'URI PROTOCOL' to see how this module will let you programaticlly merge multiple files into a single file, allowing you to drop from 'n' <s(?:cript|style)> tags to a single file (per respective type).

File Minimization

A feature that can be administered programatically (see ATTRIBUTES), will minimize whitespace usage for all CSS/Javascript files that leave the server.

File Compression

A feature that can be administered programatically (see ATTRIBUTES), will gzip the content before leaving the server. Now, I can't ever imagine the need to apply compression to a style or script file without wanting to apply it to /all/ content. That said, I recommend the use of mod_gzip (http://sourceforge.net/projects/mod-gzip/) rather than this attribute. Still, I wanted to implement it, so I did.

C-Style Inlcudes

Merging files through a URI protocol is useful, however if you have a large-scale application written in javascript, you quickly introduce namespacing, class heirarchies and countless dependancies and files. That said, it's tough to ask a developer "List all the dependancies this class has, taking each of it's super-classes and encapsualted heirarchies into consideration". Most modern languages take care of this by allowing the developer to include it's particular dependancies in the application code in it's particular file. That said, this module lets you do the same thing with CSS and Javascript.

As an example:

    /**
     * foo/bar.js
     * @inherits foo.js
     **/

     // Rather tha including foo.js as it's required by foo/bar.js,
     // simply include it directly in the file with the following
     // syntax:

     /** Include foo.js **/
     Foo.Bar = {};
     Foo.Bar.prototype = Foo;

Where, with that example, the file 'foo.js' will be a physical replacement of the include statement and therefore will no longer need to be added to the URI.

ATTRIBUTES

cache

    Apache2::Response::FileMerge->cache();

Will enable HTTP 304 return codes, respecting the If-Modified-Since keyword and therefore preventing the overhead of scraping through files again.

Given the nature of the module, the mtime of the requested document will be the newest mtime of all the combined documents.

Furthermore, the server will only find the mtime of a collection of documents when it reads the disk for the content. Therfore, when enabled, any changes to the underlying files will also require a reload/graceful of the server to pick up the changes and discontinue the 304 return for the particular URI.

stats

    Apache2::Response::FileMerge->stats();

Will include statictics (pre-minimization) in a valid comment section at the top of the document. Something like the following can be expected:

    /*
             URI: /js/foo.bar-bar.baz.js
           mtime: 1229843477
           Cache: 1
        Minimize: 0
        Compress: 0
          Render: 0.0628039836883545
    */

minimize

    Apache2::Response::FileMerge->minimize();

Will use <JavaScript::Minifier> to minimize the Javascript and CSS::Minifier to minimize CSS, if installed.

compress

    Apache2::Response::FileMerge->compress();

Will use <Compress::Zlib> to compress the document, if installed.

file_separator

    # Will change the separator from '~' to '-'
    Apache2::Response::FileMerge->file_separator('-');

Will change the default file separator from '~' to any character you choose. The default is '~' as defined in the URI PROTOCOL section.

document_root

    Apache2::Response::FileMerge->document_root('/var/www/custom-docroot');

Will change the module's relative document root from the servers default and defined root to that of any string passed to the attribute.

append_inc_name

Will append each inclucded file name, as a comment, into the included file. The intent is only to find a file that needs attention within the merged document with a little more ease. A nice feature when developing swarths of JS/CSS code.

Where, for example, if the handler merged 'foo/bar.js' and 'foo/bar/baz.js', the output would look similar to the following (when enabled, default off):

    /* foo/bar.js */
    Foo.Bar = function(){}();

    /* foo/bar/baz.js */
    Foo.Bar.Baz = function(){}();

EXAMPLES

httpd.conf

If all you want is the URI protocol and C-style includes, this is all you have to do:

    # httpd.conf
    <LocationMatch "\.js$">
        SetHandler perl-script
        PerlResponseHandler Apache2::Response::FileMerge
    </LocationMatch>

C-Style includes

This can be applied to either CSS or JS at any point in your document. The moduel will implicitly trust the developer and therefore must be syntaxually correct in all cases. The handler will inject the code of the included file into it's literal location.

The include will be respective of the DocumentRoot of the server.

Note the double-asterisks ('**') comment to indicate the include.

The 'Include' keyword is required (but can be replaced with 'Inc' if you're lazy like me).

    /** Include foo/bar/baz.js **/

    /** Include foo/bar/baz.css **/

In all cases, the intent is that any file that is consumed by this module can also be rendered and executed without this module, which is the point behind the commented include structure.

URI Protocol

The URI will also allow you to include files. The URI will include files in the exact order they are listed, from left to right. Furthermore, if a URI that is requested is already included in a dependant file, the handler will only include the first instance of the file (which will generally be the first Include point).

The URI will be respective of directory location relative to the DocumentRoot.

'.' implies directory traversal.

'~' implies file separation (Default, see ATTRIBUTES).

    # File foo/bar.js will be loaded, which is in the '/js/' directory
    http://...com/js/foo.bar.js

    # Will do the same as above, but makes less sense IMHO
    http://...com/js.foo.bar.js

    # File foo/bar.js will be loaded, which is in the document root
    http://...com/foo.bar.js

    # Will include foo.js and foo/bar.js respectively
    http://...com/foo-foo.bar.js

File Name Variable Substitution

File names, both in the url, and in the C-style includes, are subject to possible variable-substitution. Any parameters in the url's query string will be checked as variables in file names. All variables must be prefixed with "{" and suffixed with "}"; no spaces allowed (don't ask). For example:

    { var } // Doesn't work
    [ var ] // Doesn't work
    [var]   // Doesn't work
    {var}   // Works!

Further examples:

    http://www.example.com/foo.bar~baz.qux.js?var=example

    # Later, in foo/bar.js:
    /** Include languages/{var}/foobar_text.js **/

    # Resolves to:
    /** Include languages/example/foobar_text.js **/

URI PROTOCOL

The generall usefulness of the advanced URI protocol is to combine files that are seemingly not dependant upon one another. See the EXAMPLES section for more details on this.

TROUBLESHOOTING AND COMMON ISSUES

There is a common barrier with Apache (v1+, v2+) servers (also others, though Apache is the most susceptible) where you will be served a Forbidden page rather than the requested JS/CSS source when a file name grows larger than 256 characters. When combining multiple files with deep relative path locations, this is an easy threshold to cross. A work-around to this issue is by using variable substitution to put the file-name as a query string parameter rather than the file name, where the total lenght of a URI isn't limited as tightly as the file name itself.

For example:

    http://yourdomain.com/js/dir.dir2.dir<.. 256 characters later>.dirn.file.js

Can be altered to:

    http://yourdomain.com/{file}/?file=js/dir.dir2.dir<.. 256 characters later>.dirn.file.js

The big, big caveat being that the current implementation of this module will only process varaibles once and in a single-dimension. Therefore, if you are troubleshooting a URL that already utilizes variable substitution, such as:

    http://yourdomain.com/js/{lang}.dir.dir2.dir<.. 256 characters later>.dirn.file.js?lang=en

And you attempt to convert it to:

    http://yourdomain.com/{file}/?file=js/{lang}.dir.dir2.dir<.. 256 characters later>.dirn.file.js&lang=en

The "lang" variable will not be substitued as it lies within the querystring rather than the path. For these cases, we recommend you put the secondary variable substitution into an "Include" rather than the path, which should be sufficient in most cases.

KNOWN ISSUES

mod_perl v1.x

This will only work as a mod_perl 2.x PerlResponseHandler. If there is demand for 1.x, I will take the time to dynamically figure out what the right moduels, API, etc to use will be. For now, being that /I/ only use mod_perl 2.x, I have decided to not be overly clumsy with the code to take into consideration a platform people may not use.

CPAN shell installation

The unit tests each require Apache::Test to run. Yet, there are a lot of conditions that would prevent you from actually having mod_perl installed on a system of which you are trying to install this module. Although I don't really see the need or think it's good practice to install Apache2 namespaced modules without mod_perl, I have not made Apache::Test a prerequisite of this module for the case I mentioned earlier. That said, no unit tests will pass without mod_perl already installed and therefore will require a force install if that is what you would like. If that method is preferred, it is always possible to re-test the module via the CPAN shell once mod_perl is installed.

At the time of this writing, Apache::Test is included with the mod_perl 2.x distribution.

SEE ALSO

Compress::Zlib
JavaScript::Minifier
CSS::Minifier

AUTHORS

Trevor Hall, <wazzuteke@cpan.org>

Original author and maintainer.

Romuald Brunet

Generously submitted a patch adopting HTTP::Date to support POSIX locales and standard international date formats; particularly useful for file-modification based caching.

Stephen Howard (http://search.cpan.org/~howars/)

Submitted concept and patches for file/dir name substituion.

COPYRIGHT AND LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

3 POD Errors

The following errors were encountered while parsing the POD:

Around line 577:

Unknown directive: =item1

Around line 657:

'=item' outside of any '=over'

Around line 671:

You forgot a '=back' before '=head1'