use Mojo::JSON qw(decode_json encode_json);
use List::Util qw(any);
use Mojo::File qw(path);
+use IO::Compress::Gzip qw(gzip);
use constant KVS_PREFIX => "JSL:";
use constant DEFAULT_MEM_EXPIRE => 60;
}
sub get_content {
- my ($self, $basename, $options) = @_;
- my $content = $self->get_cache($basename);
- return $content if defined $content;
+ my ($self, $basename, $opts) = @_;
+ my $content;
+ $opts ||= {};
+
+ if (!$opts->{reload}) {
+ $content = $self->get_cache($basename);
+ return $content if defined $content;
+ }
+
+ # no content hits in cache, then read from file(s)
+ if ($self->{bundle_config}->{$basename}) {
+ # target is bundle, so pack them
+ $content = $self->_pack_files($basename);
+ }
+ else {
+ # target is single file, load it
+ $content = $self->_load_js($basename);
+ }
- $content = $self->_load_js($basename, $options);
if (defined $content) {
$self->set_cache($basename, $content);
$self->set_cache($content->{path}, $content);
return;
}
-sub cached_basedir {
- return "/js_cache";
-}
-
sub _load_js {
- my ($self, $basename, $options) = @_;
+ my ($self, $basename) = @_;
# check if file exists
my $abs_path = $self->_basename_to_absolute_path($basename);
return $result;
}
-sub _basename_to_absolute_path {
- my ($self, $basename) = @_;
- my $base_dir = $self->{conf}->{base_directory};
- if (!$base_dir) {
- $base_dir = $self->{app}->home;
- }
- if (!$base_dir) { # fallback
- $base_dir = "./";
- }
- my $src_dir = $self->{conf}->{source_directory};
- my $path = File::Spec->catdir($base_dir, $src_dir, $basename);
- $path = File::Spec->rel2abs($path);
- return $path;
-}
-
-sub _split_md5 {
- my ($self, $path) = @_;
- if ($path =~ m/^(.+)\.([0-9a-f]+)(\.\w+)$/) {
- return ($1 . $3, $2);
- }
- return ($path, "");
-}
-
-sub _insert_md5 {
- my ($self, $path, $md5) = @_;
-
- my ($base, $dir, $ext) = fileparse($path, qw/\.[^.]*/);
- $dir = "" if $dir eq "./";
- return "${dir}${base}.${md5}${ext}";
-}
-
-sub execute_packing {
- my ($self, $config_file) = @_;
- $self->{app}->log->info("JavaScriptLoader: reading config file $config_file ...");
-
- my $json_body = decode('utf-8', path($config_file)->slurp);
-
- my $config = decode_json($json_body);
- for my $filename (keys %$config) {
- $self->_pack_file($filename, $config->{$filename});
- }
-}
-
-sub _pack_file {
- my ($self, $filename, $config) = @_;
+sub _pack_files {
+ my ($self, $filename) = @_;
$self->{app}->log->debug("JavaScriptLoader: packing to $filename...");
-
my $target_dir = path($self->{app}->home, $self->{conf}->{source_directory})->to_abs;
+ my $config = $self->{bundle_config}->{$filename};
+ if (!$config) {
+ $self->{app}->log->error("JavaScriptLoader: no settings to pack $filename...");
+ return;
+ }
+
my @result;
my %done;
for my $target (@{$config->{prior}}) {
-
my $path = path($target_dir, $target);
next if $done{$path->basename};
$self->{app}->log->debug("JavaScriptLoader: pack $target ($path)");
}
my $joined = join("\n", @result);
- $self->{bundles}->{$filename} = $joined;
-
- for my $k (keys %done) {
- $self->{bundled}->{$k} = $filename;
- }
-
my $md5;
if (utf8::is_utf8($joined)) {
$md5 = md5_hex(Encode::encode('utf8', $joined));
else {
$md5 = md5_hex($joined);
}
- $self->{bundles_hash}->{$filename} = $md5;
$self->{app}->log->debug("JavaScriptLoader: packing to $filename done. md5: $md5");
+
+ my @sources = keys %done;
+ $self->{bundled} ||= {};
+ for my $src (@sources) {
+ $self->{bundled}->{$src} = 1;
+ }
+
+ my $basedir = $self->cached_basedir;
+ my $result = { md5 => $md5,
+ content => $joined,
+ path => "$basedir/$filename",
+ type => "text/javascript",
+ sources => \@sources,
+ };
+ return $result;
+}
+
+sub cached_basedir {
+ return "/js_cache";
+}
+
+sub _basename_to_absolute_path {
+ my ($self, $basename) = @_;
+ my $base_dir = $self->{conf}->{base_directory};
+ if (!$base_dir) {
+ $base_dir = $self->{app}->home;
+ }
+ if (!$base_dir) { # fallback
+ $base_dir = "./";
+ }
+ my $src_dir = $self->{conf}->{source_directory};
+ my $path = File::Spec->catdir($base_dir, $src_dir, $basename);
+ $path = File::Spec->rel2abs($path);
+ return $path;
+}
+
+sub _split_md5 {
+ my ($self, $path) = @_;
+ if ($path =~ m/^(.+)\.([0-9a-f]+)(\.\w+)$/) {
+ return ($1 . $3, $2);
+ }
+ return ($path, "");
}
+sub _insert_md5 {
+ my ($self, $path, $md5) = @_;
+
+ my ($base, $dir, $ext) = fileparse($path, qw/\.[^.]*/);
+ $dir = "" if $dir eq "./";
+ return "${dir}${base}.${md5}${ext}";
+}
-sub load_all {
+sub load_bundle_config {
my ($self) = @_;
- # load config file and process JS files
+ # load config file
if ($self->{conf}->{config_file}) {
my $abs_path = $self->{conf}->{config_file};
if ($abs_path !~ m|^/|) {
$abs_path = path($self->{app}->home, $abs_path)->to_abs;
}
- if (-f $abs_path && -r $abs_path) {
- $self->execute_packing($abs_path);
+ # read bundle config
+ if (-r $abs_path) {
+ my $json_body = decode('utf-8', path($abs_path)->slurp);
+ $self->{bundle_config} = decode_json($json_body);
+ $self->load_all_bundles;
}
else {
$self->{app}->log->warn("JavaScriptLoader: cannot read config file: $self->{conf}->{config_file} ($abs_path)");
}
}
+sub load_all_bundles {
+ my $self = shift;
+ for my $target (keys %{$self->{bundle_config}}) {
+ my $content = $self->get_content($target, {reload => 1});
+ }
+}
+
+sub _is_bundled {
+ my ($self, $target) = @_;
+ my $pool = $self->{bundled} // {};
+ return $pool->{$target};
+}
+
sub register {
my ($self, $app, $conf) = @_;
# set default config value
my $cnf = $app->config->{JavaScriptLoader} ||= {};
- $cnf->{source_directory} ||= "public/js";
- $cnf->{mode} ||= "development";
- $cnf->{config_file} ||= "public/js/bundle-config.json";
- $cnf->{use_bundle} = 1 if !defined $cnf->{use_bundle};
+ $cnf->{source_directory} //= "public/js";
+ $cnf->{mode} //= "development";
+ $cnf->{config_file} //= "public/js/bundle-config.json";
+ $cnf->{use_bundle} //= 1;
+ $cnf->{use_compression} ||= 0;
$self->{conf} = {%$conf, %$cnf};
$self->{app} = $app;
$self->{mode} = $self->{conf}->{mode} || $app->mode;
- $self->{bundles} = {};
- $self->{bundles_hash} = {};
+ $self->{compress} = $cnf->{use_compression};
+
$self->{bundled} = {};
if (!$self->{conf}->{source_directory}) {
$app->log->warn("JavaScriptLoader: no source_directory given!");
}
+ $self->load_bundle_config;
$app->helper('load_js', sub {
my ($c, $name) = @_;
+ # check if target is bundled
+ return '' if $self->_is_bundled($name);
- if ($self->{conf}->{use_bundle}) {
- return '' if $self->{bundled}->{$name};
- }
my $content = $self->get_content($name);
if ($content) {
- return "<script src='$content->{path}'></script>";
+ my $md5_path = $self->_insert_md5($content->{path},
+ $content->{md5});
+ return "<script src='$md5_path'></script>";
}
$self->{app}->log->warn("JavaScriptLoader: cannot load $name");
return '';
});
- $app->helper('load_js_bundle', sub {
- my ($c, $name) = @_;
- return '' if !$self->{conf}->{use_bundle};
-
- if (!$self->{bundles_hash}->{$name}) {
- $self->{app}->log->warn("JavaScriptLoader: cannot load bandles - $name");
- return '';
- }
- my $filename = $self->_insert_md5($name, $self->{bundles_hash}->{$name});
- my $path = join('/', $self->cached_basedir, $filename);
- return "<script src='$path'></script>";
- });
-
my $r = $app->routes;
$r->any($self->cached_basedir . '/*content_path' => sub {
my $c = shift;
my $path = $c->stash('content_path');
if ($path) {
- my ($basename, $md5) = $self->_split_md5($path);
-
- if ($self->{bundles_hash}->{$basename}
- && $self->{bundles_hash}->{$basename} eq $md5) {
- $c->res->headers->content_type("application/javascript");
- $c->res->body(encode_utf8($self->{bundles}->{$basename}));
- $c->rendered(200);
- return;
- }
-
my $content = $self->get_cache($path);
+
if (!$content) {
# no cache, so try to generate
+ my ($basename, $md5) = $self->_split_md5($path);
$content = $self->get_content($basename);
# check md5
}
}
if ($content) {
- $c->res->headers->content_type($content->{type});
- $c->res->body(encode_utf8($content->{content}));
+ if ($self->{compress}
+ && $content->{type} =~ m{^text/}
+ && ($c->req->headers->accept_encoding // '') =~ /gzip/i
+ ) {
+ # use gzip compression
+ # Add header
+ $c->res->headers->append(Vary => 'Accept-Encoding');
+ $c->res->headers->content_encoding('gzip');
+
+ # Compress content with gzip
+ my $gzip_in = encode_utf8($content->{content});
+ gzip(\$gzip_in, \my $compressed);
+ $c->res->headers->content_type($content->{type});
+ $c->res->body($compressed);
+
+ }
+ else {
+ $c->res->headers->content_type($content->{type});
+ $c->res->body(encode_utf8($content->{content}));
+ }
$c->rendered(200);
return;
}