File Coverage

blib/lib/Plack/Middleware/Assets.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             #
2             # This file is part of Plack-Middleware-Assets
3             #
4             # This software is Copyright (c) 2013 by Moritz Onken.
5             #
6             # This is free software, licensed under:
7             #
8             # The (three-clause) BSD License
9             #
10             package Plack::Middleware::Assets;
11             {
12             $Plack::Middleware::Assets::VERSION = '1.0.0';
13             }
14              
15             # ABSTRACT: Concatenate and minify JavaScript and CSS files
16 3     3   85030 use strict;
  3         7  
  3         131  
17 3     3   16 use warnings;
  3         5  
  3         101  
18              
19 3     3   18 use base 'Plack::Middleware';
  3         5  
  3         357  
20             use Plack::Util::Accessor
21 3     3   18 qw( separator filter content minify files key mtime type type_class expires extension );
  3         6  
  3         31  
22              
23 3     3   380 use Digest::MD5 qw(md5_hex);
  3         7  
  3         204  
24 3     3   3149 use HTTP::Date ();
  3         11533  
  3         70  
25 3     3   2988 use Class::Load ();
  0            
  0            
26              
27             sub new {
28             my $class = shift;
29             my $self = $class->SUPER::new(@_);
30              
31             # set defaults
32             $self->separator(1) unless ( defined $self->separator );
33             $self->minify(1) unless ( defined $self->minify );
34             $self->filter(1) unless ( defined $self->filter );
35             $self->_build_content;
36             return $self;
37             }
38              
39             sub _build_content {
40             my $self = shift;
41             local $/;
42              
43             my $type = $self->type
44             || (
45             ( grep {/\.css$/} @{ $self->files } ) ? 'css'
46             : ( grep {/\.js$/} @{ $self->files } ) ? 'js'
47             : 'plain'
48             );
49             $self->type($type);
50             my $class = __PACKAGE__ . "::Type::$type";
51             eval { Class::Load::load_class($class) }
52             or die "$class could not be loaded: $@";
53             $self->type_class($class);
54              
55             my $separator = $self->separator;
56              
57             # use default comment format unless format was specified
58             $separator = $class->separator
59             if $separator && $separator !~ /%s/;
60              
61             $self->content(
62             join(
63             "\n",
64             map {
65             open my $fh, '<', $_ or die "$_: $!";
66             ( $separator ? sprintf( $separator, $_ ) : '' ) . <$fh>
67             } @{ $self->files }
68             )
69             );
70              
71             $self->content( $self->_transform('filter') ) if $self->filter;
72             $self->content( $self->_transform('minify') ) if $self->minify;
73              
74             $self->key( md5_hex( $self->content ) );
75             my @mtime = map { ( stat($_) )[9] } @{ $self->files };
76             $self->mtime( ( reverse( sort(@mtime) ) )[0] );
77             }
78              
79             sub _transform {
80             my ( $self, $transform ) = @_;
81             return $self->content
82             if ( $transform ne 'minify'
83             && $ENV{PLACK_ENV}
84             && $ENV{PLACK_ENV} eq 'development' );
85             no strict 'refs';
86             my $run = $self->$transform;
87             my $method;
88             if ( ref $run eq 'CODE' ) {
89             $method = $run;
90             }
91             elsif ( $run && $self->type_class->can($transform) ) {
92             $method = $self->type_class . "::$transform";
93             }
94             return $self->content unless ($method);
95              
96             local $_ = $self->content;
97             return $method->($_);
98             }
99              
100             sub serve {
101             my $self = shift;
102             my $type = $self->type;
103              
104             return [
105             200,
106             [ 'Content-Type' => $self->type_class->content_type,
107             'Content-Length' => length( $self->content ),
108             'Last-Modified' => HTTP::Date::time2str( $self->mtime ),
109             'Expires' =>
110             HTTP::Date::time2str( time + ( $self->expires || 2592000 ) ),
111             ],
112             [ $self->content ]
113             ];
114             }
115              
116             sub call {
117             my $self = shift;
118             my $env = shift;
119              
120             if ( $ENV{PLACK_ENV} && $ENV{PLACK_ENV} eq 'development' ) {
121             my @mtime = map { ( stat($_) )[9] } @{ $self->files };
122             $self->_build_content
123             if ( $self->mtime < ( reverse( sort(@mtime) ) )[0] );
124             }
125              
126             $env->{'psgix.assets'} ||= [];
127             my $extension = $self->extension || $self->type_class->extension;
128             my $url = '/_asset/' . $self->key . '.' . $extension;
129             push( @{ $env->{'psgix.assets'} }, $url );
130             return $self->serve if $env->{PATH_INFO} eq $url;
131             return $self->app->($env);
132             }
133              
134             package Plack::Middleware::Assets::Type::plain;
135             {
136             $Plack::Middleware::Assets::Type::plain::VERSION = '1.0.0';
137             }
138             use strict;
139             use warnings;
140              
141             sub content_type {'text/plain'}
142             sub separator { }
143             sub extension {'txt'}
144              
145             package Plack::Middleware::Assets::Type::css;
146             {
147             $Plack::Middleware::Assets::Type::css::VERSION = '1.0.0';
148             }
149             use strict;
150             use warnings;
151             use base 'Plack::Middleware::Assets::Type::plain';
152             use CSS::Minifier::XS qw(minify);
153              
154             sub content_type {'text/css'}
155             sub separator {"/* %s */\n"}
156             sub extension {'css'}
157              
158             package Plack::Middleware::Assets::Type::js;
159             {
160             $Plack::Middleware::Assets::Type::js::VERSION = '1.0.0';
161             }
162             use strict;
163             use warnings;
164             use base 'Plack::Middleware::Assets::Type::plain';
165             use JavaScript::Minifier::XS qw(minify);
166              
167             sub content_type {'application/javascript'}
168             sub separator {"/* %s */\n"}
169             sub extension {'js'}
170              
171             1;
172              
173             __END__