bz_locations
+ CONCATENATE_ASSETS
+
IS_NULL
NOT_NULL
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
use constant LOCAL_FILE => 'bugzilla-update.xml'; # Relative to datadir.
+# When true CSS and JavaScript assets will be concatanted and minified at
+# run-time, to reduce the number of requests required to render a page.
+# Setting this to a false value can help debugging.
+use constant CONCATENATE_ASSETS => 1;
+
# These are unique values that are unlikely to match a string or a number,
# to be used in criteria for match() functions and other things. They start
# and end with spaces because most Bugzilla stuff has trim() called on it,
use File::Basename;
use File::Copy qw(move);
use File::Spec;
+use File::Slurp;
use IO::File;
use POSIX ();
"$assetsdir/.htaccess" => { perms => WS_SERVE, contents => <<EOT
# Allow access to .css files
-<FilesMatch \\.css\$>
+<FilesMatch \\.(css|js)\$>
Allow from all
</FilesMatch>
my $datadir = bz_locations->{'datadir'};
my $graphsdir = bz_locations->{'graphsdir'};
+ my $assetsdir = bz_locations->{'assetsdir'};
# If the graphs/ directory doesn't exist, we're upgrading from
# a version old enough that we need to update the $datadir/mining
# format.
_rename_file($oldparamsfile, "$datadir/$oldparamsfile");
}
+ # Remove old assets htaccess file to force recreation with correct values.
+ if (-e "$assetsdir/.htaccess") {
+ if (read_file("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
+ unlink("$assetsdir/.htaccess");
+ }
+ }
+
_create_files(%files);
if ($params->{index_html}) {
_create_files(%{$fs->{index_html}});
_remove_empty_css_files();
_convert_single_file_skins();
- _remove_dynamic_css_files();
+ _remove_dynamic_assets();
}
sub _remove_empty_css_files {
}
}
-# delete all automatically generated css files to force recreation at the next
-# request.
-sub _remove_dynamic_css_files {
- foreach my $file (glob(bz_locations()->{assetsdir} . '/*.css')) {
+# delete all automatically generated css/js files to force recreation at the
+# next request.
+sub _remove_dynamic_assets {
+ my @files = (
+ glob(bz_locations()->{assetsdir} . '/*.css'),
+ glob(bz_locations()->{assetsdir} . '/*.js'),
+ );
+ foreach my $file (@files) {
unlink($file);
}
write_file($file, $content);
}
- $file =~ s/^\Q$cgi_path\E\///;
+ $file =~ s/^\Q$cgi_path\E\///o;
return mtime_filter($file);
}
return 'url(../../' . dirname($source) . '/' . $url . ')';
}
+sub _concatenate_js {
+ return @_ unless CONCATENATE_ASSETS;
+ my ($sources) = @_;
+ return [] unless $sources && ref($sources);
+
+ my %files =
+ map {
+ (my $file = $_) =~ s/(^[^\?]+)\?.+/$1/;
+ $_ => $file;
+ } @$sources;
+
+ my $cgi_path = bz_locations()->{cgi_path};
+ my $skins_path = bz_locations()->{assetsdir};
+
+ # build minified files
+ my @minified;
+ foreach my $source (@$sources) {
+ next unless -e "$cgi_path/$files{$source}";
+ my $file = $skins_path . '/' . md5_hex($source) . '.js';
+ if (!-e $file) {
+ my $content = read_file("$cgi_path/$files{$source}");
+
+ # minimal minification
+ $content =~ s#/\*.*?\*/##sg; # block comments
+ $content =~ s#(^ +| +$)##gm; # leading/trailing spaces
+ $content =~ s#^//.+$##gm; # single line comments
+ $content =~ s#\n{2,}#\n#g; # blank lines
+ $content =~ s#(^\s+|\s+$)##g; # whitespace at the start/end of file
+
+ write_file($file, "/* $files{$source} */\n" . $content . "\n");
+ }
+ push @minified, $file;
+ }
+
+ # concat files
+ my $file = $skins_path . '/' . md5_hex(join(' ', @$sources)) . '.js';
+ if (!-e $file) {
+ my $content = '';
+ foreach my $source (@minified) {
+ $content .= read_file($source);
+ }
+ write_file($file, $content);
+ }
+
+ $file =~ s/^\Q$cgi_path\E\///o;
+ return [ $file ];
+}
+
# YUI dependency resolution
sub yui_resolve_deps {
my ($yui, $yui_deps) = @_;
'css_files' => \&css_files,
yui_resolve_deps => \&yui_resolve_deps,
+ concatenate_js => \&_concatenate_js,
# All classifications (sorted by sortkey, name)
'all_classifications' => sub {
[% SET yui = yui_resolve_deps(yui, yui_deps) %]
[% SET css_sets = css_files(style_urls, yui, yui_css) %]
- <link href="[% css_sets.unified_standard_skin FILTER html %]"
- rel="stylesheet" type="text/css">
+ [% IF constants.CONCATENATE_ASSETS %]
+ [% PROCESS format_css_link asset_url = css_sets.unified_standard_skin %]
+ [% ELSE %]
+ [% FOREACH asset_url = css_sets.standard %]
+ [% PROCESS format_css_link %]
+ [% END %]
+ [% FOREACH asset_url = css_sets.skin %]
+ [% PROCESS format_css_link %]
+ [% END %]
+ [% END %]
[% IF style %]
<style type="text/css">
[% END %]
[% IF css_sets.unified_custom %]
- <link href="[% css_sets.unified_custom FILTER html %]"
- rel="stylesheet" type="text/css">
+ [% IF constants.CONCATENATE_ASSETS %]
+ [% PROCESS format_css_link asset_url = css_sets.unified_custom %]
+ [% ELSE %]
+ [% FOREACH asset_rul = css_sets.custom %]
+ [% PROCESS format_css_link %]
+ [% END %]
+ [% END %]
[% END %]
[%# YUI Scripts %]
[% END %]
[% starting_js_urls.push('js/global.js') %]
- [% FOREACH javascript_url = starting_js_urls %]
+ [% FOREACH asset_url = concatenate_js(starting_js_urls) %]
[% PROCESS format_js_link %]
[% END %]
// -->
</script>
- [% FOREACH javascript_url = javascript_urls %]
+ [% FOREACH asset_url = concatenate_js(javascript_urls) %]
[% PROCESS format_js_link %]
[% END %]
<div id="message">[% message %]</div>
[% END %]
+[% BLOCK format_css_link %]
+ <link href="[% asset_url FILTER html %]" rel="stylesheet" type="text/css">
+[% END %]
+
[% BLOCK format_js_link %]
- <script type="text/javascript" src="[% javascript_url FILTER mtime FILTER html %]"></script>
+ <script type="text/javascript" src="[% asset_url FILTER mtime FILTER html %]"></script>
[% END %]