NAME

Catalyst::Controller::Combine - Combine JS/CSS Files

VERSION

version 0.15

SYNOPSIS

# use the helper to create your Controller
script/myapp_create.pl controller Js Combine

# or:
script/myapp_create.pl controller Css Combine

# DONE. READY FOR USE.

# Just use it in your template:
# will deliver all JavaScript files concatenated (in Js-Controller)
<script type="text/javascript" src="/js/file1/file2/.../filex.js"></script>

# will deliver all CSS files concatenated (in Css-Controller)
<link rel="stylesheet" type="text/css" href="/css/file1/file2/.../filex.css" />

# in the generated controller you may add this to allow minification
# the trick behind is the existence of a sub named 'minify'
# inside your Controller.

use JavaScript::Minifier::XS qw(minify);
    # or:
use CSS::Minifier::XS qw(minify);

DESCRIPTION

Catalyst Controller that concatenates (and optionally minifies) static files like JavaScript or CSS into a single request. Depending on your configuration, files are also auto-added with a simple dependency-management.

The basic idea behind concatenation is that all files one Controller should handle reside in a common directory.

Assuming you have a directory with JavaScript files like:

root/static/js
 |
 +-- prototype.js
 |
 +-- helpers.js
 |
 +-- site.js

Then you could combine all files in a single tag (assuming your directory for the Controller is set to 'static/js' -- which is the default):

<script type="text/javascript" src="/js/prototype/helpers/site.js"></script>

If you add a dependency into your Controller's config like:

__PACKAGE__->config(
    ...
    depend => {
        helpers => 'prototype',
        site    => 'helpers',
    },
    ...
);

Now, the URI to retrieve the very same JavaScript files can be shortened:

<script type="text/javascript" src="/js/site.js"></script>

CONFIGURATION

A simple configuration of your Controller could look like this:

__PACKAGE__->config(
    # the directory to look for files
    # defaults to 'static/<<action_namespace>>'
    dir => 'static/js',

    # the (optional) file extension in the URL
    # defaults to action_namespace
    extension => 'js',

    # optional dependencies
    depend => {
        scriptaculous => 'prototype',
        builder       => 'scriptaculous',
        effects       => 'scriptaculous',
        dragdrop      => 'effects',
        slider        => 'scriptaculous',
        myscript      => [ qw(slider dragdrop) ],
    },

    # name of the minifying routine (defaults to 'minify')
    # will be used if present in the package
    minifier => 'minify',

    # should a HTTP expire header be set? This usually means, 
    # you have to change your filenames, if there a was change!
    expire => 1,

    # time offset (in seconds), in which the file will expire
    expire_in => 60 * 60 * 24 * 365 * 3, # 3 years

    # mimetype of response if wanted
    # will be guessed from extension if possible and not given
    # falls back to 'text/plain' if not guessable
    mimetype => 'application/javascript',
);

CONFIGURATION OPTIONS

TODO: writeme...

METHODS

do_combine :Action

the do_combine Action-method may be used like this (eg in YourApp:Controller:Js):

sub default :Path {
    my $self = shift;
    my $c = shift;

    $c->forward('do_combine');
}

However, a predeclared default method like this is already present -- see below.

All files in the remaining URL will be concatenated to a single resulting stream and optionally minified if a sub named 'minify' in your Controller's package namespace exists.

Thus, inside your Controller a simple

# for JavaScript you may do
use JavaScript::Minifier::XS qw(minify);

# for CSS quite similar:
use CSS::Minifier::XS qw(minify);

will do the job and auto-minify the stream.

If you specify an include configuration option you also could recursively include other files into the generated stream. (Think about @import in css files).

default :Path

a standard handler for your application's controller

maps to the path_prefix of your actual controller and consumes the entire URI

uri_for :Private

handle uri_for requests (not intentionally a Catalyst-feature :-) requires a patched uri_for method in your app! my one looks like the sub below.

If this method is used, the URI will only contain files that will not automatically get added in by dependency resolution. Also, a simple GET-parameter is added that reflects the unix-timestamp of the most resent file that will be in the list of combined files. This helps the browser to do proper caching even if files will change. Admittedly this is most of the time needed during development.

# in my app.pm:
sub uri_for {
    my $c = shift;
    my $path = shift;
    my @args = @_;

    if (blessed($path) && $path->class && $path->class->can('uri_for')) {
        #
        # the path-argument was a component that can help
        # let the controller handle this for us
        #   believe me, it can do it!
        #
        return $c->component($path->class)->uri_for($c, $path, @args);
    }

    #
    # otherwise fall back into the well-known behavior
    #
    $c->next::method($path, @args);
}

# alternatively, using Catalyst 5.8 you may do this:
around 'uri_for' => sub {
    my $orig = shift;
    my $c = shift;
    my $path = shift;
    my @args = @_;

    if (blessed($path) && $path->class && $path->class->can('uri_for')) {
        #
        # let the controller handle this for us
        #   believe me, it can do it!
        #
        return $c->component($path->class)->uri_for($c, $path, @args);
    }

    return $c->$orig($path, @args);
};

GOTCHAS

Please do not use namespace::autoclean if you intend to enable a minifier. The black magic behind the scenes tries to determine your intention to minify by searching for a sub called minify inside the controller's package. However, this sub is imported by eg JavaScript::Minifier::XS and will be kicked out of the controller by namespace::autoclean.

AUTHOR

Wolfgang Kinkeldei, <wolfgang@kinkeldei.de>

LICENSE

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