package Apache2::Filter::Minifier::JavaScript;
###############################################################################
# Required inclusions.
use strict;
use warnings;
use Apache2::Filter qw(); # $f
use Apache2::RequestRec qw(); # $r
use Apache2::RequestUtil qw(); # $r->dir_config
use Apache2::Log qw(); # $log->*()
use APR::Table qw(); # dir_config->get() and headers_out->unset()
use Apache2::Const -compile => qw(OK DECLINED);
use Time::HiRes qw(gettimeofday tv_interval);
###############################################################################
# Load up the JS minifier modules.
use JavaScript::Minifier;
eval { require JavaScript::Minifier::XS; };
###############################################################################
# Version number.
our $VERSION = '1.04';
###############################################################################
# MIME-Types we're willing to minify.
my %mime_types = (
'text/javascript' => 1,
'text/ecmascript' => 1,
'application/javascript' => 1,
'application/ecmascript' => 1,
'application/x-javascript' => 1,
);
###############################################################################
# Subroutine: handler($filter)
###############################################################################
# JavaScript minification output filter.
sub handler {
my $f = shift;
my $r = $f->r;
my $log = $r->log;
# assemble list of acceptable MIME-Types
my %types = (
%mime_types,
map { $_=>1 } $r->dir_config->get('JsMimeType'),
);
# determine Content-Type of document
my ($ctype) = ($r->content_type =~ /^(.+?)(?:;.*)?$/);
unless ($ctype) {
$log->info( "unable to determine content type; skipping : URL ", $r->uri );
return Apache2::Const::DECLINED;
}
# only process JS documents
unless (exists $types{$ctype}) {
$log->info( "skipping request to ", $r->uri, " (not a JS document)" );
return Apache2::Const::DECLINED;
}
# figure out which minifier module/function we're supposed to be using;
# either an explicit minifier function/package, or our list of acceptable
# minifiers
my $minifier;
my @possible = $r->dir_config->get('JsMinifier') || (
'JavaScript::Minifier::XS',
'JavaScript::Minifier',
);
foreach my $maybe (@possible) {
no strict 'refs';
# explicit function name
if (defined &{"$maybe"}) {
$minifier = sub { $maybe->(shift) };
last;
}
# package name; look for "minify()" function
if (defined &{"${maybe}::minify"}) {
my $func = \&{"${maybe}::minify"};
$minifier = ($maybe eq 'JavaScript::Minifier')
? sub { $func->(input=>shift) }
: sub { $func->(shift) };
last;
}
}
unless ($minifier) {
$log->info( "no JavaScript minifier available; declining" );
return Apache2::Const::DECLINED;
}
# gather up entire document
my $ctx = $f->ctx;
while ($f->read(my $buffer, 4096)) {
$ctx .= $buffer;
}
# unless we're at the end, store the JS for our next invocation
unless ($f->seen_eos) {
$f->ctx( $ctx );
return Apache2::Const::OK;
}
# if we've got JS to minify, minify it
if ($ctx) {
my $t_st = [gettimeofday()];
my $min = eval { $minifier->($ctx) };
if ($@) {
# minification failed; log error and send original JS
$log->error( "error minifying: $@" );
$f->print( $ctx );
}
else {
# minification ok; log results and send minified JS
my $t_dif = tv_interval($t_st);
my $l_min = length($min);
my $l_js = length($ctx);
$log->debug( "JS minified $l_js to $l_min : t:$t_dif : URL ", $r->uri );
$r->headers_out->unset( 'Content-Length' );
$f->print( $min );
}
}
return Apache2::Const::OK;
}
1;
=head1 NAME
Apache2::Filter::Minifier::JavaScript - JS minifying output filter
=head1 SYNOPSIS
PerlOutputFilterHandler Apache2::Filter::Minifier::JavaScript
# if you need to supplement MIME-Type list
PerlSetVar JsMimeType text/json
# if you want to explicitly specify the minifier to use
#PerlSetVar JsMinifier JavaScript::Minifier::XS
#PerlSetVar JsMinifier JavaScript::Minifier
#PerlSetVar JsMinifier MY::Minifier::function
=head1 DESCRIPTION
C is a Mod_perl2 output filter which
minifies JavaScript using C or
C.
Only JavaScript documents are minified, all others are passed through
unaltered. C comes with a list of
several known acceptable MIME-Types for JavaScript documents, but you can
supplement that list yourself by setting the C PerlVar
appropriately (use C for a single new MIME-Type, or C
when you want to add multiple MIME-Types).
Given a choice, using C is preferred over
C, but we'll use whichever one you've got available. If
you want to explicitly specify which minifier you want to use, set the
C PerlVar to the name of the package/function that implements the
minifier. Minification functions are expected to accept a single parameter
(the JavaScript to be minified) and to return the minified JavaScript on
completion. If you specify a package name, we look for a C function
in that package.
=head2 Caching
Minification does require additional CPU resources, and it is recommended that
you use some sort of cache in order to keep this to a minimum.
Being that you're already running Apache2, though, here's some examples of a
mod_cache setup:
Disk Cache
# Cache root directory
CacheRoot /path/to/your/disk/cache
# Enable cache for "/js/" location
CacheEnable disk /js/
Memory Cache
# Cache size: 4 MBytes
MCacheSize 4096
# Min object size: 128 Bytes
MCacheMinObjectSize 128
# Max object size: 512 KBytes
MCacheMaxObjectSize 524288
# Enable cache for "/js/" location
CacheEnable mem /js/
=head1 METHODS
=over
=item handler($filter)
JavaScript minification output filter.
=back
=head1 AUTHOR
Graham TerMarsch (cpan@howlingfrog.com)
Many thanks to Geoffrey Young for writing C, from which several
things were lifted. :)
=head1 COPYRIGHT
Copyright (C) 2007, Graham TerMarsch. All Rights Reserved.
This is free software; you can redistribute it and/or modify it under the same
license as Perl itself.
=head1 SEE ALSO
L,
L,
L,
L.
=cut