From 9c68564ab94ba3bd158c9133a0f3f56571f498e7 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 7 Apr 2012 16:39:04 +0200 Subject: [PATCH 1/1] Bug 1918 - Minify JavaScript and CSS This provides new Koha::Template::Plugin::Combine which is intended to replace direct include of JavaScript and CSS files. This plugin preserves order of included files when combining them. Code need cleanup and documentation, but attaching it to bug will provide easier way to try it out. --- Koha/Template/Plugin/Combine.pm | 141 +++++++++++++++++++ .../intranet-tmpl/prog/en/css/staff-global.css | 3 +- .../prog/en/includes/doc-head-close.inc | 50 ++++--- koha-tmpl/opac-tmpl/prog/en/css/opac.css | 3 - .../opac-tmpl/prog/en/includes/doc-head-close.inc | 143 +++++++++++++------- koha-tmpl/opac-tmpl/prog/en/js/script.js | 1 - 6 files changed, 261 insertions(+), 80 deletions(-) create mode 100644 Koha/Template/Plugin/Combine.pm diff --git a/Koha/Template/Plugin/Combine.pm b/Koha/Template/Plugin/Combine.pm new file mode 100644 index 0000000..c84bc7f --- /dev/null +++ b/Koha/Template/Plugin/Combine.pm @@ -0,0 +1,141 @@ +package Koha::Template::Plugin::Combine; +use Template::Plugin; +use base qw( Template::Plugin ); +use warnings; +use strict; + +# PLACK_DEBUG=1 always re-create files but report correct hit/miss ration + +use File::Slurp; +use Digest::MD5; +use Data::Dump qw(dump); + +use CSS::Minifier::XS; +use JavaScript::Minifier::XS; + +sub new { + my ($class, $context, @params) = @_; +#warn "## context ",dump( $context ); + bless { + _CONTEXT => $context, + js => [], # same as extension! + css => [], + inline => 1, + minify => 1, + }, $class; +} + +sub _html_params { + my $params = shift; + return join(' ', map { $_ . '="' . $params->{$_} . '"' } keys %$params ); +} + +sub javascript { + my ( $self, $params ) = @_; + warn "## combine.javascript ", dump( $params ); + $params->{type} ||= 'text/javascript'; + push @{ $self->{js} }, $params; + my $html = _html_params( $params ); + if ( $self->{inline} ) { + return "\n"; + } elsif ( my $cdata = $params->{cdata} ) { + return qq|\n|; + } else { + return "\n"; + } +} + +sub css { + my ( $self, $params ) = @_; + warn "## combine.css ",dump( $params ); + $params->{rel} ||= 'stylesheet'; + $params->{type} ||= 'text/css'; + die "no href in ",dump($params) unless exists $params->{href}; + my $html = _html_params( $params ); + + if ( exists $params->{media} ) { # FIXME not combined! + push @{ $self->{css_media} }, ""; + return ""; + } + + push @{ $self->{css} }, $params; + if ( $self->{inline}) { + return "\n"; + } else { + return "\n"; + } +} + +sub combined_files { + my ( $self, $what, $attr, $params ) = @_; +warn "## combined_files $what $attr ",dump($params); + my $key = join(' ', map { $_->{$attr} || $_->{cdata} } @{ $self->{$what} } ); + $key = Digest::MD5::md5_hex( $key ); # shorten filenames + warn "# $what $attr key = $key\n"; + my $htdocs = C4::Context->config('intrahtdocs'); + $htdocs =~ s{/intranet-tmpl/?$}{}; # FIXME DocumentRoot even for OPAC + + my ( $path, $url ); + if ( my $prefix = $params->{prefix} ) { + $path = "$htdocs/$prefix/combined/$key.$what"; + $url = "$prefix/combined/$key.$what"; + } else { + $path = "$htdocs/intranet-tmpl/combined/$key.$what"; + $url = "/intranet-tmpl/combined/$key.$what"; + } + $path =~ s{//+}{/}; # plack is picky about paths + if ( -e $path ) { + $Koha::Persistant::stats->{combine_files}->[0]++; # hit + return $url if ! $ENV{PLACK_DEBUG}; # FIXME no caching with debug + } + $Koha::Persistant::stats->{combine_files}->[1]++ if ! $ENV{PLACK_DEBUG}; # miss + + my $mix; + foreach my $include ( @{ $self->{$what} } ) { + if ( my $path = $include->{$attr} ) { + warn "combine_files $what $attr - $path"; + my $chunk = read_file("$htdocs/$path"); + die "$htdocs/$path found \@import" if $what eq 'css' && $chunk =~ m'@import'; + $mix .= "\n/* BEGIN $path */\n$chunk\n/* END $path */\n"; + } elsif ( my $cdata = $include->{cdata} ) { + warn "combine_files CDATA $cdata"; + $mix .= "\n/* BEGIN CDATA */\n$cdata\n/* END CDATA */\n"; + } + } + my $combined_size = length($mix); + if ( $self->{minify} ) { + if ( $what eq 'js' ) { + $mix = JavaScript::Minifier::XS::minify( $mix ); + } elsif ( $what eq 'css' ) { + $mix = CSS::Minifier::XS::minify( $mix ); + } else { + die "can't minify $what"; + } + my $size = length($mix); + warn sprintf "minify %s %d -> %d %.2f%%\n", $what, $combined_size, $size, $size * 100 / $combined_size; + } + write_file $path, $mix; + warn "## $path ", -s $path, "/$combined_size bytes\n"; + return $url; +} + +sub html { + my ( $self, $params ) = @_; + + if ( $self->{inline} ) { + warn "## css ",dump( $self->{css} ); + warn "## javascript ",dump( $self->{js} ); + my $mix; + my $js = $self->combined_files( 'js' => 'src', $params ); + my $css = $self->combined_files( 'css' => 'href', $params ); + return qq{ + + + + }; + } else { + return ""; + } +} + +1; diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css index e591a73..2cd263e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -1,5 +1,4 @@ -@import url("../lib/yui/reset-fonts-grids.css"); -@import url("../lib/yui/skin.css"); +/* import moved to koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc */ input:focus, textarea:focus { border-color:#538200; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc index 116d2e1..16a0f7a 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc @@ -1,44 +1,47 @@ +[% USE combine = Combine %] - - - +[% combine.css( href => themelang _ "/lib/jquery/jquery-ui.css" ) %] +[% combine.css( href => themelang _ "/lib/jquery/plugins/ui.tabs.css" ) %] +[% combine.css( media => "print" href => themelang _ "/css/print.css" ) %] [% IF ( intranetstylesheet ) %] - + [% combine.css( href => intranetstylesheet ) %] [% ELSE %] - + [% combine.css( href => yuipath _ "/reset-fonts-grids.css" ) %] + [% combine.css( href => yuipath _ "/skin.css" ) %] + [% combine.css( href => themelang _ "/css/staff-global.css" ) %] [% END %] [% IF ( IntranetUserCSS ) %][% END %] - - - - - - +[% combine.javascript( src => themelang _ "/lib/jquery/jquery.js" ) %] +[% combine.javascript( src => themelang _ "/lib/jquery/plugins/ui.tabs.js" ) %] +[% combine.javascript( src => themelang _ "/lib/jquery/jquery-ui.js" ) %] +[% combine.javascript( src => themelang _ "/lib/jquery/plugins/jquery.hotkeys.min.js" ) %] +[% combine.javascript( src => themelang _ "/lib/jquery/plugins/jquery.cookie.min.js" ) %] +[% combine.javascript( src => themelang _ "/lib/jquery/plugins/jquery.highlight-3.js" ) %] [% IF ( login ) %] - + [% combine.css( href => themelang _ "/css/login.css" ) %] [% END %] [% IF ( wizard ) %] - + [% combine.css( href => themelang _ "/css/wizard.css" ) %] [% END %] [% IF ( intranetcolorstylesheet ) %] - + [% combine.css( href => themelang _ "/css/" _ intranetcolorstylesheet ) %] [% END %] - - +[% combine.javascript( src => yuipath _ "/utilities/utilities.js" ) %] +[% combine.javascript( src => yuipath _ "/datasource/datasource-min.js" ) %] [% IF ( CircAutocompl ) %] - + [% combine.javascript( src => yuipath _ "/autocomplete/autocomplete-min.js" ) %] [% END %] - - - +[% combine.javascript( src => yuipath _ "/button/button-min.js" ) %] +[% combine.javascript( src => yuipath _ "/container/container_core-min.js" ) %] +[% combine.javascript( src => yuipath _ "/menu/menu-min.js" ) %] - +[% combine.javascript( src => themelang _ "/js/staff-global.js" ) %] [% IF ( intranetuserjs ) %] [% IF ( virtualshelves || intranetbookbag ) %] - + [% combine.javascript( src => themelang _ "/js/basket.js" ) %] [% END %] [% IF LocalCoverImages %] - +[% combine.javascript( src => themelang _ "/js/localcovers.js" ) %] [% END %] +[% combine.html( prefix => themelang ) %] diff --git a/koha-tmpl/opac-tmpl/prog/en/css/opac.css b/koha-tmpl/opac-tmpl/prog/en/css/opac.css index ea538aa..b10dd68 100644 --- a/koha-tmpl/opac-tmpl/prog/en/css/opac.css +++ b/koha-tmpl/opac-tmpl/prog/en/css/opac.css @@ -1,6 +1,3 @@ -@import url("../lib/yui/reset-fonts-grids.css"); -@import url("../lib/yui/skin.css"); - a { font-weight : bold; } diff --git a/koha-tmpl/opac-tmpl/prog/en/includes/doc-head-close.inc b/koha-tmpl/opac-tmpl/prog/en/includes/doc-head-close.inc index 3907f34..d5ea90e 100644 --- a/koha-tmpl/opac-tmpl/prog/en/includes/doc-head-close.inc +++ b/koha-tmpl/opac-tmpl/prog/en/includes/doc-head-close.inc @@ -2,50 +2,62 @@ -[% IF ( opacstylesheet ) %][% ELSE %] -[% IF ( opaclayoutstylesheet ) %][% ELSE %][% END %][% END %] +[% USE combine = Combine %] +[% IF ( opacstylesheet ) %] + [% combine.css( href => opacstylesheet ) %] +[% ELSE %] + [% IF ( opaclayoutstylesheet ) %] + [% combine.css( href => themelang _ "/css/" _ opaclayoutstylesheet ) %] + [% ELSE %] + [% combine.css( href => themelang _ "/css/opac.css" ) %] + [% END %] +[% END %] [% IF ( opaccolorstylesheet ) %] - + [% combine.css( href => themelang _ "/css/" _ opaccolorstylesheet ) %] [% END %] [% IF ( opacexternalsheet ) %] - + [% combine.css( href => opacexternalsheet ) %] [% END %] [% IF ( opac_css_override ) %] - + [% combine.css( href => therelang _ "/css/" _ opac_css_override ) %] [% END %] - +[% combine.css( href => themelang _ "/lib/jquery/plugins/ui.tabs.css" ) %] [% IF ( OPACUserCSS ) %][% END %] - - - - - - - +[% combine.css( href => yuipath _ "/reset-fonts-grids.css" ) %] +[% combine.css( href => yuipath _ "/skin.css" ) %] +[% combine.javascript( src => yuipath _ "/utilities/utilities.js" ) %] +[% combine.javascript( src => yuipath _ "/container/container-min.js" ) %] +[% combine.javascript( src => yuipath _ "/menu/menu-min.js" ) %] +[% combine.javascript( src => themelang _ "/lib/jquery/jquery.js" ) %] +[% combine.javascript( src => themelang _ "/lib/jquery/plugins/ui.tabs.js" ) %] +[% combine.javascript( src => themelang _ "/lib/jquery/plugins/jquery.hoverIntent.minified.js" ) %] +[% combine.javascript( src => themelang _ "/js/script.js" ) %] + [% IF ( OPACAmazonCoverImages ) %] - - +[% combine.javascript( cdata => "var NO_AMAZON_IMAGE = _(\"No cover image available\");" ) %] +[% combine.javascript( src => themelang _ "/js/amazonimages.js" ) %] [% END %] [% IF ( SyndeticsCoverImages ) %] - - [% END %] +[% combine.javascript( cdata => "var NO_AMAZON_IMAGE = _(\"No cover image available\");" ) %] +[% combine.javascript( src => themelang _ "/js/amazonimages.js" ) %] +[% END %] -[% IF ( opacbookbag ) %] +[% IF ( opacbookbag ) %] + [% combine.javascript( src => themelang _ "/js/basket.js" ) %] +[% ELSIF ( virtualshelves ) %] + [% combine.javascript( src => themelang _ "/js/basket.js" ) %] +[% ELSE %] + [% combine.javascript( cdata => "var readCookie;" ) %] +[% END %] +[% combine.javascript( cdata => "var cdata_test;" ) %] -[% IF ( opacuserlogin ) %][% IF ( TagsEnabled ) %][% END %][% ELSE %][% END %] + +[% IF ( opacbookbag ) %] +[% compose.javascript( src => themelang _ "/js/basket.js" ) %] +[% ELSIF ( virtualshelves ) %] +[% compose.javascript( src => themelang _ "/js/basket.js" ) %] +[% ELSE %] + +[% END %] + +[% IF ( opacuserlogin ) %] + [% IF ( TagsEnabled ) %] + [% combine.javascript( src => themelang _ "/js/tags.js" ) %] + [% END %] +[% END %] + [% IF ( GoogleJackets ) %] - +[% combine.javascript( src => themelang _ "/js/google-jackets.js" ) %] [% END %] [% IF OpenLibraryCovers %] - +[% combine.javascript( src => themelang _ "/js/openlibrary.js" ) %] +[% combine.javascript( src => themelang _ "/js/localcovers.js" ) %] [% END %] -[% IF ( BakerTaylorEnabled ) %] +[% IF ( BakerTaylorEnabled ) %] +[% combine.javascript( src => themelang _ "/js/bakertaylorimages.js" ) %] [% END %] + +[% END %] + [% IF ( GoogleIndicTransliteration ) %] - - + + [% combine.javascript( src => themelang _ "/js/googleindictransliteration.js" ) %] [% END %] - +[% combine.html( prefix => themelang ) %] diff --git a/koha-tmpl/opac-tmpl/prog/en/js/script.js b/koha-tmpl/opac-tmpl/prog/en/js/script.js index a3cfab8..67ea771 100644 --- a/koha-tmpl/opac-tmpl/prog/en/js/script.js +++ b/koha-tmpl/opac-tmpl/prog/en/js/script.js @@ -1,4 +1,3 @@ -function _(s) { return s } // dummy function for gettext function confirmDelete(message) { return (confirm(message) ? true : false); } -- 1.7.7.3