From 3b86363bda39186cd8cff2a6a3d6fe4f225d86bd Mon Sep 17 00:00:00 2001 From: hylom Date: Tue, 21 Nov 2017 21:46:47 +0900 Subject: [PATCH] add JavaScriptLoader plugin --- .../lib/Newslash/Plugin/JavaScriptLoader.pm | 191 +++++++++++++++++++++ src/newslash_web/lib/Newslash/Web.pm | 3 + src/newslash_web/templates/common/header.html.tt2 | 9 +- 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 src/newslash_web/lib/Newslash/Plugin/JavaScriptLoader.pm diff --git a/src/newslash_web/lib/Newslash/Plugin/JavaScriptLoader.pm b/src/newslash_web/lib/Newslash/Plugin/JavaScriptLoader.pm new file mode 100644 index 00000000..e645c87b --- /dev/null +++ b/src/newslash_web/lib/Newslash/Plugin/JavaScriptLoader.pm @@ -0,0 +1,191 @@ +package Newslash::Plugin::JavaScriptLoader; +use Mojo::Base 'Mojolicious::Plugin'; + +use File::Spec; +use File::Basename qw(fileparse); +use Digest::MD5 qw(md5_hex); +use IPC::Open3; +use Symbol qw(gensym); +use IO::Select; +use POSIX ":sys_wait_h"; +use Mojo::Util qw(dumper); +use Encode; + +use constant KVS_PREFIX => "JSL:"; +use constant DEFAULT_MEM_EXPIRE => 60; +use constant DEFAULT_KVS_EXPIRE => 60; + +sub _kvs { + return shift->{app}->kvs; +} + +sub _cache { + return shift->{app}->cache; +} + +sub set_cache { + my ($self, $key, $value) = @_; + my $mem_expire = $self->{conf}->{mem_expire}; + my $kvs_expire = $self->{conf}->{kvs_expire}; + $mem_expire = DEFAULT_MEM_EXPIRE if !defined $mem_expire; + $kvs_expire = DEFAULT_KVS_EXPIRE if !defined $kvs_expire; + + $self->_kvs->set(KVS_PREFIX . $key, $value, $kvs_expire); + $self->_cache->set(KVS_PREFIX . $key, $value, $mem_expire); +} + +sub get_cache { + my ($self, $key) = @_; + my $expire = $self->{conf}->{mem_expire}; + $expire = DEFAULT_MEM_EXPIRE if !defined $expire; + + my $value = $self->_cache->get(KVS_PREFIX . $key); + return $value if defined $value; + + $value = $self->_kvs->get(KVS_PREFIX . $key); + if (defined $value) { + $self->_cache->set(KVS_PREFIX . $key, $value, $expire); + return $value; + } + + # no cache found + return; +} + +sub get_content { + my ($self, $basename) = @_; + my $content = $self->get_cache($basename); + return $content if defined $content; + + $content = $self->_load_js($basename); + if (defined $content) { + $self->set_cache($basename, $content); + $self->set_cache($content->{path}, $content); + return $content + } + return; +} + +sub cached_basedir { + return "/js_cache"; +} + +sub _load_js { + my ($self, $basename) = @_; + + # check if file exists + my $abs_path = $self->_basename_to_absolute_path($basename); + return if (!-f $abs_path || !-r $abs_path); + + # load file + open( my $fh, $abs_path ) or return; + my $js_body = do { local($/); <$fh> } ; + close($fh); + + my $md5; + if (utf8::is_utf8($js_body)) { + $md5 = md5_hex(Encode::encode('utf8', $js_body)); + } + else { + $md5 = md5_hex($js_body); + } + my $path; + if ($self->{mode} eq "development") { + my $basedir = $self->{conf}->{source_directory}; + $basedir =~ s/^public//g; + $path = "$basedir/$basename"; + } + else { + my ($base, $dir, $ext) = fileparse($basename, qw/\.[^.]*/); + $dir = "" if $dir eq "./"; + my $basedir = $self->cached_basedir; + $path = "$basedir/$dir${base}.$md5$ext"; + } + + my $result = { md5 => $md5, + content => $js_body, + path => $path, + type => "text/javascript", + }; + 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 register { + my ($self, $app, $conf) = @_; + + $self->{conf} = {%$conf, %{$app->config->{JavaScriptLoader}}}; + $self->{app} = $app; + $self->{mode} = $self->{conf}->{mode} || $app->mode; + + if (!$self->{conf}->{source_directory}) { + $app->log->warn("JavaScriptLoader: no source_directory given!"); + } + + if ($app->config->{TT2Renderer}) { + my $tt2r = $app->config->{TT2Renderer}->{self}; + $tt2r->add_NS_function('load_js', sub { + my ($c, $name) = @_; + my $content = $self->get_content($name); + if ($content) { + return ""; + } + $self->{app}->log->warn("JavaScriptLoader: cannot load $name"); + return ''; + }); + } + + my $r = $app->routes; + $r->any('/js_cache/*content_path' => sub { + my $c = shift; + my $path = $c->stash('content_path'); + if ($path) { + 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 && $content->{md5} ne $md5) { + $content = ""; + } + } + if ($content) { + $c->res->headers->content_type($content->{type}); + $c->res->body(encode_utf8($content->{content})); + $c->rendered(200); + return; + } + } + $c->rendered(404); + }); + + $app->helper(javascript_loader => sub { state $javascript_loader = $self; }); +} + + + +1; diff --git a/src/newslash_web/lib/Newslash/Web.pm b/src/newslash_web/lib/Newslash/Web.pm index 09643ec2..d598b909 100644 --- a/src/newslash_web/lib/Newslash/Web.pm +++ b/src/newslash_web/lib/Newslash/Web.pm @@ -120,6 +120,9 @@ sub startup { # quasi-static content $app->plugin('Newslash::Plugin::QuasiStaticContent'); + # javascript loader + $app->plugin('Newslash::Plugin::JavaScriptLoader'); + # user authorization $app->plugin('Newslash::Plugin::UserAuth'); diff --git a/src/newslash_web/templates/common/header.html.tt2 b/src/newslash_web/templates/common/header.html.tt2 index 059018f9..96c9e483 100644 --- a/src/newslash_web/templates/common/header.html.tt2 +++ b/src/newslash_web/templates/common/header.html.tt2 @@ -36,9 +36,16 @@ [%- END -%] + [%- IF production -%] - + [%- ELSE %] + [% NS.load_js("escape-html.js") %] + [% NS.load_js("base.js") %] + [% NS.load_js("ns-util.js") %] + [% NS.load_js("vue-newslash.js") %] + [%- END %] + -- 2.11.0