1 package Newslash::Plugin::JavaScriptLoader;
2 use Mojo::Base 'Newslash::Plugin::Preprocessor';
5 use File::Basename qw(basename);
7 use Mojo::JSON qw(decode_json encode_json);
8 use List::Util qw(any);
9 use Mojo::File qw(path);
11 use constant CACHE_KEY => "JavaScriptLoader";
12 use constant JS_CONTENT_TYPE => "application/javascript";
14 sub _generate_content {
15 my ($self, $pathname) = @_;
16 $pathname = $self->_strip_md5_from_path($pathname);
19 if ($self->{bundle_config}->{$pathname}) {
20 # target is bundle, so pack them
21 $js_content = $self->_pack_files($pathname);
24 # target is single file, load it
25 $js_content = $self->_load_js($pathname);
28 if (!defined $js_content) {
29 $self->{app}->log->error("JavaScriptLoader: generate contentfailed: $pathname");
32 return $self->_build_content_object($js_content, $pathname, JS_CONTENT_TYPE);
36 my ($self, $rel_pathname) = @_;
38 # check if file exists
39 my $abs_path = $self->_get_absolute_path($rel_pathname);
40 return if (!-f $abs_path || !-r $abs_path);
43 open( my $fh, "<:utf8", $abs_path ) or return;
44 my $js_body = do { local($/); <$fh> } ;
51 my ($self, $pathname) = @_;
52 $self->{app}->log->debug("JavaScriptLoader: packing to $pathname...");
53 my $base_dir = $self->_get_absolute_path($self->{conf}->{source_directory});
55 my $config = $self->{bundle_config}->{$pathname};
57 $self->{app}->log->error("JavaScriptLoader: no settings to pack $pathname...");
63 for my $target (@{$config->{prior}}) {
64 my $path = path($base_dir, $target);
65 next if $done{$path->basename};
66 $self->{app}->log->debug("JavaScriptLoader: pack $target ($path)");
67 push @result, Encode::decode_utf8($path->slurp);
69 #my $fh = FileHandle->new;
70 #if ($fh->open($path, "<:utf8")) {
71 # $js = do { local $/; <$fh> };
75 # $self->{app}->log->error("JavaScriptLoader: cannot open $path");
81 for my $target (path($base_dir)->list->each) {
82 my $basename = $target->basename;
83 next if $target->to_abs !~ $config->{target};
84 next if ( any { $target->to_abs =~ m/$_/ } $config->{exclude} );
85 next if $done{$basename};
87 $self->{app}->log->debug("JavaScriptLoader: pack $basename ($target)");
88 push @result, Encode::decode_utf8($target->slurp);
90 #my $fh = FileHandle->new;
91 #if ($fh->open($target, "<:utf8")) {
92 # $js = do { local $/; <$fh> };
96 # $self->{app}->log->error("JavaScriptLoader: cannot open $target");
102 my $joined = join("\n", @result);
103 $self->{app}->log->debug("JavaScriptLoader: packing to $pathname done.");
105 my @sources = keys %done;
106 $self->{bundled} ||= {};
107 for my $src (@sources) {
108 $self->{bundled}->{$src} = 1;
115 sub _load_bundle_config {
119 if ($self->{conf}->{config_file}) {
120 my $abs_path = $self->{conf}->{config_file};
121 if ($abs_path !~ m|^/|) {
122 $abs_path = path($self->{app}->home, $abs_path)->to_abs;
127 my $json_body = Encode::decode_utf8(path($abs_path)->slurp);
128 $self->{bundle_config} = decode_json($json_body);
129 #$self->load_all_bundles;
132 $self->{app}->log->warn("JavaScriptLoader: cannot read config file: $self->{conf}->{config_file} ($abs_path)");
137 sub load_all_bundles {
139 for my $target (keys %{$self->{bundle_config}}) {
140 my $content = $self->get_content($target, {reload => 1});
145 my ($self, $target) = @_;
146 my $pool = $self->{bundled} // {};
147 return $pool->{$target};
151 my ($self, $app, $conf) = @_;
153 # set default config value
154 my $cnf = $app->config->{JavaScriptLoader} ||= {};
155 $cnf->{source_directory} //= "public/js";
156 $cnf->{mode} //= "development";
157 $cnf->{config_file} //= "public/js/bundle-config.json";
158 $cnf->{use_bundle} //= 1;
159 $cnf->{use_compression} ||= 0;
161 $self->{conf} = {%$conf, %$cnf};
163 $self->{mode} = $self->{conf}->{mode} || $app->mode;
164 $self->{compress} = $cnf->{use_compression};
165 $self->{bundled} = {};
167 if (!$self->{conf}->{source_directory}) {
168 $app->log->warn("JavaScriptLoader: no source_directory given!");
170 $self->_load_bundle_config;
172 $app->helper('load_js', sub {
174 # check if target is bundled
175 return '' if $self->_is_bundled($name);
177 my $path = $self->get_md5_path($name);
179 $self->{app}->log->error("JavaScriptLoader: cannot load $name");
182 return "<script src='$path'></script>";
185 my $r = $app->routes;
186 my $base_url = $self->{conf}->{base_url};
187 if (substr($base_url, -1, 1) ne "/") {
188 $base_url = $base_url . "/";
191 $r->any("${base_url}*content_path" => sub {
193 my $path = $base_url . $c->stash('content_path');
196 my $content = $self->get_content($path);
199 if ($c->req->headers->if_none_match
201 && $c->req->headers->if_none_match eq "\"$content->{md5}\"") {
205 $c->res->headers->content_type($content->{type});
206 my $output = $content->{content};
209 if ($self->{compress}
210 && $content->{type} =~ m{^(text/|application/json|application/javascript)}
211 && ($c->req->headers->accept_encoding // '') =~ /gzip/i
212 && $content->{gz_content}
214 # use gzip compression
216 $c->res->headers->append(Vary => 'Accept-Encoding');
217 $c->res->headers->content_encoding('gzip');
219 $output = $content->{gz_content};
223 $c->res->headers->content_type($content->{type});
224 if ($content->{md5}) {
225 $c->res->headers->etag("\"$content->{md5}\"");
229 $c->res->body($output);
237 $app->helper(javascript_loader => sub { state $javascript_loader = $self; });