replace access token example with ... so that lint is happy (this is in the POD and...
[sgn.git] / lib / SGN / Controller / JavaScript.pm
blobe245fbdc91872889fc674069e662622e7617e98d
1 =head1 NAME
3 SGN::Controller::JavaScript - controller for serving minified
4 javascript
6 =cut
8 package SGN::Controller::JavaScript;
9 use Moose;
10 use namespace::autoclean;
11 use Moose::Util::TypeConstraints;
13 use HTTP::Status;
14 use File::Spec;
16 use Fcntl qw( S_ISREG S_ISLNK );
18 use JSAN::ServerSide;
19 use List::MoreUtils qw/ uniq first_index /;
21 use JSON;
22 use File::Slurp;
23 use Data::Dumper;
25 BEGIN { extends 'Catalyst::Controller' }
27 __PACKAGE__->config(
28 namespace => 'js',
29 js_path => SGN->path_to('js/build'),
30 dependency_json_path => SGN->path_to('js/build/mapping.json'),
31 js_legacy_path => SGN->path_to('js/source/legacy')
35 my $inc = subtype as 'ArrayRef';
36 coerce $inc, from 'Defined', via { [$_] };
38 has 'js_path' => (
39 is => 'ro',
40 isa => $inc,
41 coerce => 1,
43 has 'dependency_json_path' => (
44 is => 'ro',
45 isa => $inc,
46 coerce => 1,
48 has 'js_legacy_path' => (
49 is => 'ro',
50 isa => $inc,
51 coerce => 1,
55 =head1 PUBLIC ACTIONS
57 =head2 default
59 Serve a single (minified) javascript file from our js path.
61 =cut
63 sub default :Path('build') {
64 my ( $self, $c, @args ) = @_;
66 my $rel_file = File::Spec->catfile( @args );
68 # support caching with If-Modified-Since requests
69 my $full_file = File::Spec->catfile( $self->js_path->[0], $rel_file );
70 my ( $modtime ) = (stat( $full_file ))[9];
71 $c->throw_404 unless $modtime && -f _;
73 my $ims = $c->req->headers->if_modified_since;
74 if( $ims && $modtime && $ims >= $modtime ) {
75 $c->res->status( RC_NOT_MODIFIED );
76 $c->res->body(' ');
77 } else {
78 #include sourcemap header if a sourcemap exists
79 my $sourcemap_name = $rel_file.".map";
80 if( -f File::Spec->catfile( $self->js_path->[0], $sourcemap_name )){
81 $c->res->headers->header('SourceMap' => $sourcemap_name);
83 $c->res->headers->last_modified( $modtime );
84 $c->res->headers->content_type( "application/javascript" );
85 $c->res->body(join("",read_file($full_file)));
86 $c->res->headers->remove_header('content-length');
90 =head2 legacy
92 Serve a single (minified) javascript file from our js path.
94 =cut
96 sub legacy :Path('legacy') {
97 my ( $self, $c, @args ) = @_;
99 my $rel_file = File::Spec->catfile( @args );
101 # support caching with If-Modified-Since requests
102 my $full_file = File::Spec->catfile( $self->js_legacy_path->[0], $rel_file );
103 my ( $modtime ) = (stat( $full_file ))[9];
104 $c->throw_404 unless $modtime && -f _;
106 my $ims = $c->req->headers->if_modified_since;
107 if( $ims && $modtime && $ims >= $modtime ) {
108 $c->res->status( RC_NOT_MODIFIED );
109 $c->res->body(' ');
110 } else {
111 $c->stash->{js} = [ $rel_file ];
112 $c->forward('View::JavaScript::Legacy');
117 =head1 PRIVATE ACTIONS
119 =head2 get_js_module_dependencies
121 =cut
123 my $js_module_dependencies = {};
124 my $js_module_dependencies_modtime = 0;
125 sub get_js_module_dependencies :Private {
126 my ( $self, $names ) = @_;
128 my ( $modtime ) = (stat( $self->dependency_json_path->[0] ))[9];
129 if( ! $js_module_dependencies_modtime || $js_module_dependencies_modtime < $modtime) {
130 $js_module_dependencies = decode_json(read_file($self->dependency_json_path->[0]));
131 $js_module_dependencies_modtime = $modtime;
134 my $result = {
135 files => [],
136 legacy => []
139 # print Dumper $names;
140 # print Dumper $js_module_dependencies;
142 for my $n (@$names) {
143 if (exists $js_module_dependencies->{$n}){
144 push @{$result->{files}}, @{$js_module_dependencies->{$n}->{files}};
145 push @{$result->{legacy}}, @{$js_module_dependencies->{$n}->{legacy}};
149 # print Dumper $result;
151 return $result;
154 =head2 resolve_javascript_classes
156 =cut
158 sub resolve_javascript_classes :Private {
159 my ( $self, $c ) = @_;
161 my $jsan_classes = $c->stash->{jsan_classes};
162 my $js_modules = $c->stash->{js_modules};
164 my $module_deps = $self->get_js_module_dependencies($js_modules);
165 push @{ $jsan_classes }, @{$module_deps->{legacy}};
167 my @jsan_deps = uniq @$jsan_classes; #< do not sort, load order might be important
168 for (@jsan_deps) {
169 s/\.js$//;
170 s!\.!/!g;
172 # if prototype is present, move it to the front to prevent it
173 # conflicting with jquery
174 my $prototype_idx = first_index { /Prototype$/i } @jsan_deps;
175 if( $prototype_idx > -1 ) {
176 my ($p) = splice @jsan_deps, $prototype_idx, 1;
177 unshift @jsan_deps, $p;
180 # add in JSAN.use dependencies
181 my @deps = $self->_resolve_jsan_dependencies( \@jsan_deps );
183 for my $dep (@{$module_deps->{files}}) {
184 push @deps, File::Spec->catfile( "/js/build/", $dep )
187 $c->stash->{js_uris} = \@deps;
190 ########## helpers #########
192 sub _resolve_jsan_dependencies {
193 my ( $self, $files ) = @_;
194 local $_; #< stupid JSAN writes to $_
196 # resolve JSAN dependencies of these files
197 my $jsan = $self->new_jsan;
198 for my $f (@$files) {
199 $jsan->add( $f );
202 return $jsan->uris;
205 has _jsan_params => ( is => 'ro', isa => 'HashRef', lazy_build => 1 );
206 sub _build__jsan_params {
207 my ( $self ) = @_;
208 my $inc_path = $self->js_legacy_path;
209 die "multi-dir js_legacy_path not yet supported" if @$inc_path > 1;
210 my $js_dir = $inc_path->[0];
211 -d $js_dir or die "configured js_legacy_path '$js_dir' does not exist!\n";
212 return {
213 js_dir => "$js_dir",
214 uri_prefix => '/js/legacy',
217 sub new_jsan {
218 JSAN::ServerSide->new( %{ shift->_jsan_params } );