OSDN Git Service

JavaScriptLoader: add caching and gzip compression
authorhylom <hylom@users.sourceforge.jp>
Tue, 2 Oct 2018 10:53:30 +0000 (19:53 +0900)
committerhylom <hylom@users.sourceforge.jp>
Tue, 2 Oct 2018 10:53:30 +0000 (19:53 +0900)
src/newslash_web/lib/Newslash/Plugin/JavaScriptLoader.pm
src/newslash_web/lib/Newslash/Web.pm
src/newslash_web/templates/common/header.html.tt2

index 0165365..8cdf534 100644 (file)
@@ -13,6 +13,7 @@ use Encode;
 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;
@@ -56,11 +57,25 @@ sub get_cache {
 }
 
 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);
@@ -69,12 +84,8 @@ sub get_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);
@@ -113,59 +124,20 @@ sub _load_js {
     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)");
@@ -186,12 +158,6 @@ sub _pack_file {
     }
 
     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));
@@ -199,23 +165,74 @@ sub _pack_file {
     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)");
@@ -223,72 +240,67 @@ sub load_all {
     }
 }
 
+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
@@ -297,8 +309,26 @@ sub register {
                         }
                     }
                     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;
                     }
index d699698..5ad3651 100644 (file)
@@ -171,12 +171,6 @@ sub startup {
 
     # javascript loader
     $app->plugin('Newslash::Plugin::JavaScriptLoader');
-    if ($maintenance_mode) {
-        $app->log->info("bypassing javasciprt loading...");
-    }
-    else {
-       $app->javascript_loader->load_all;
-    }
 
     # user authorization
     $app->plugin('Newslash::Plugin::UserAuth');
index da616ff..ec8360c 100644 (file)
@@ -32,9 +32,9 @@
     </script>
     [%- END -%]
 
-    [% helpers.load_js_bundle("ns_all.js") %]
-
-    [%- IF !JavaScriptLoader.use_bundle %]
+    [%- IF JavaScriptLoader.use_bundle %]
+    [% helpers.load_js("ns_all.js") %]
+    [%- ELSE -%]
     [% helpers.load_js("ui.js") %]
     [% helpers.load_js("newslash.js") %]
     [% helpers.load_js("escape-html.js") %]
@@ -42,6 +42,7 @@
     [% helpers.load_js("ns-util.js") %]
     [% helpers.load_js("vue-newslash.js") %]
     [%- END -%]
+
     [%- IF user.is_admin %]
     [% helpers.load_js("newslash-admin.js") %]
     [%- END -%]