File Coverage

blib/lib/Mojolicious/Plugin/Browserify/Processor.pm
Criterion Covered Total %
statement 40 119 33.6
branch 1 40 2.5
condition 2 16 12.5
subroutine 11 19 57.8
pod 3 3 100.0
total 57 197 28.9


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::Browserify::Processor;
2              
3             =head1 NAME
4              
5             Mojolicious::Plugin::Browserify::Processor - An AssetPack processor for browserify
6              
7             =head1 DESCRIPTION
8              
9             L is a
10             L preprocessor.
11              
12             =head1 SYNOPSIS
13              
14             use Mojolicious::Lite;
15              
16             plugin "AssetPack";
17             app->asset->preprocessors->remove($_) for qw( js jsx );
18              
19             my $browserify = Mojolicious::Plugin::Browserify::Processor->new;
20             app->asset->preprocessors->add($browserify);
21             app->asset("app.js" => "/js/main.js");
22              
23             get "/app" => "app_js_inlined";
24             app->start;
25              
26             __DATA__
27             @@ app_js_inlined.js.ep
28             %= asset "app.js" => {inline => 1}
29              
30             See also L for a simpler API.
31              
32             =cut
33              
34 13     13   379586 use Mojo::Base 'Mojolicious::Plugin::AssetPack::Preprocessor';
  13         15  
  13         59  
35 13     13   129537 use Mojo::Util;
  13         17  
  13         544  
36 13     13   351 use Cwd ();
  13         17  
  13         273  
37 13     13   130 use File::Basename 'dirname';
  13         15  
  13         901  
38 13     13   97 use File::Path 'make_path';
  13         32  
  13         1307  
39 13     13   78 use File::Spec;
  13         14  
  13         291  
40 13     13   7521 use File::Which ();
  13         13987  
  13         384  
41 13   50 13   68 use constant DEBUG => $ENV{MOJO_ASSETPACK_DEBUG} || 0;
  13         15  
  13         765  
42 13     13   56 use constant CACHE_DIR => '.browserify';
  13         25  
  13         25276  
43              
44             =head1 ATTRIBUTES
45              
46             =head2 browserify_args
47              
48             $array_ref = $self->browserify_args;
49             $self= $self->browserify_args([ -g => "reactify" ]);
50              
51             Command line arguments that will be passed on to C.
52              
53             =head2 environment
54              
55             $str = $self->environment;
56             $self = $self->environment($str);
57              
58             Should be either "production" or "development" (default). This variable will
59             be passed on as C to C.
60              
61             =head2 extensions
62              
63             $array_ref = $self->extensions;
64             $self = $self->extensions([qw( js jsx )]);
65              
66             Specifies the extensions browserify should look for.
67              
68             =head2 executable
69              
70             $path = $self->executable;
71              
72             Holds the path to the "browserify" executable. Default to just "browserify".
73             C can also be found in C<./node_modules/.bin/browserify>, in the
74             current project directory.
75              
76             =cut
77              
78             has browserify_args => sub { [] };
79             has environment => sub { $ENV{MOJO_MODE} || $ENV{NODE_ENV} || 'development' };
80             has extensions => sub { ['js'] };
81             has executable => sub { shift->_executable('browserify') || 'browserify' };
82              
83             =head1 METHODS
84              
85             =head2 can_process
86              
87             $bool = $self->can_process;
88              
89             Returns true if browserify can be executed.
90              
91             =cut
92              
93 0 0   0 1 0 sub can_process { -f $_[0]->executable ? 1 : 0 }
94              
95             =head2 checksum
96              
97             $str = $self->checksum($text, $path);
98              
99             Returns the checksum for a given chunk of C<$text>. C<$text> is a
100             scalar ref containing the text from the asset. The default is
101             to use L.
102              
103             =cut
104              
105             sub checksum {
106 0     0 1 0 my ($self, $text, $path) = @_;
107 0         0 my $map = {};
108              
109 0         0 $self->_node_module_path;
110 0         0 $self->_find_node_modules($text, $path, $map);
111 0         0 Mojo::Util::md5_sum($$text, join '', map { Mojo::Util::slurp($_) } values %$map);
  0         0  
112             }
113              
114             =head2 process
115              
116             Used to process the JavaScript using C.
117              
118             =cut
119              
120             sub process {
121 0     0 1 0 my ($self, $assetpack, $text, $path) = @_;
122 0         0 my $environment = $self->environment;
123 0         0 my $cache_dir = File::Spec->catdir($assetpack->out_dir, CACHE_DIR);
124 0         0 my $map = {};
125 0         0 my @extra = @{$self->browserify_args};
  0         0  
126 0         0 my ($err, @modules);
127              
128 0         0 local $ENV{NODE_ENV} = $environment;
129 0 0 0     0 mkdir $cache_dir or die "mkdir $cache_dir: $!" unless -d $cache_dir;
130 0         0 $self->_node_module_path;
131 0         0 $self->_find_node_modules($text, $path, $map);
132 0         0 $self->{node_modules} = $map;
133              
134 0         0 for my $module (grep {/^\w/} keys %$map) {
  0         0  
135 0         0 push @modules, $self->_outfile($assetpack, "$module-$environment.js");
136 0 0 0     0 next if -e $modules[-1] and (stat _)[9] >= (stat $map->{$module})[9];
137 0         0 make_path(dirname $modules[-1]);
138 0         0 $self->_run([$self->executable, @extra, -r => $module, -o => $modules[-1]], undef, undef, \$err);
139             }
140              
141 0 0       0 if (!length $err) {
142 0         0 push @extra, map { -u => $_ } grep {/^\w/} keys %$map;
  0         0  
  0         0  
143 0         0 $self->_run([$self->executable, @extra, -e => $path], undef, $text, \$err);
144             }
145 0 0       0 if (length $err) {
    0          
146 0         0 $self->_make_js_error($err, $text);
147             }
148             elsif (length $$text) {
149 0         0 $$text = join "\n", (map { Mojo::Util::slurp($_) } @modules), $$text;
  0         0  
150 0 0       0 $self->_minify($text, $path) if $assetpack->minify;
151             }
152              
153 0         0 return $self;
154             }
155              
156             sub _executable {
157 3     3   7 my ($self, $name) = @_;
158 3   33     53 my $paths = $self->{node_module_path} || $self->_node_module_path;
159              
160 3         12 for my $p (@$paths) {
161 0         0 my $local = Cwd::abs_path(File::Spec->catfile($p, '.bin', $name));
162 0 0 0     0 return $local if $local and -e $local;
163             }
164              
165 3         17 return File::Which::which($name);
166             }
167              
168             sub _find_node_modules {
169 0     0   0 my ($self, $text, $path, $uniq) = @_;
170              
171 0         0 while ($$text =~ m!\brequire\s*\(\s*(["'])(.+)+\1\s*\)\s*!g) {
172 0         0 my $module = $2;
173 0         0 warn "[Browserify] require($module) from $path\n" if DEBUG;
174 0 0       0 next if $uniq->{$module};
175 0 0       0 $module =~ /^\w/
176             ? $self->_follow_system_node_module($module, $path, $uniq)
177             : $self->_follow_relative_node_module($module, $path, $uniq);
178             }
179              
180 0         0 return keys %$uniq;
181             }
182              
183             sub _follow_relative_node_module {
184 0     0   0 my ($self, $module, $path, $uniq) = @_;
185              
186 0         0 for my $ext ("", map {".$_"} @{$self->extensions}) {
  0         0  
  0         0  
187 0         0 my $file = File::Spec->catfile(split '/', "$module$ext");
188 0 0       0 return if $uniq->{"$module$ext"};
189 0 0       0 next unless -f $file;
190 0         0 $uniq->{"$module$ext"} = $file;
191 0         0 my $js = Mojo::Util::slurp($file);
192 0         0 return $self->_find_node_modules(\$js, $file, $uniq);
193             }
194              
195 0         0 die "Could not find JavaScript module '$module'";
196             }
197              
198             sub _follow_system_node_module {
199 0     0   0 my ($self, $module, $path, $uniq) = @_;
200 0         0 my $p;
201              
202 0         0 for my $prefix (@{$self->{node_module_path}}) {
  0         0  
203 0 0       0 return $uniq->{$module} = $p if -e ($p = File::Spec->catfile($prefix, $module, 'package.json'));
204 0 0       0 return $uniq->{$module} = $p if -e ($p = File::Spec->catfile($prefix, $module, 'index.js'));
205 0 0       0 return $uniq->{$module} = $p if -e ($p = File::Spec->catfile($prefix, "$module.js"));
206             }
207              
208 0         0 die "Could not find JavaScript module '$module' in @{$self->{node_module_path}}";
  0         0  
209             }
210              
211             sub _minify {
212 0     0   0 my ($self, $text, $path) = @_;
213 0         0 my $uglifyjs = $self->_executable('uglifyjs');
214 0         0 my $err = '';
215              
216 0 0       0 if ($uglifyjs) {
217 0         0 $self->_run([$uglifyjs, qw( -m -c )], $text, $text, \$err);
218             }
219             else {
220 0         0 require JavaScript::Minifier::XS;
221 0         0 $$text = JavaScript::Minifier::XS::minify($$text);
222 0 0       0 $err = 'JavaScript::Minifier::XS failed' unless $$text;
223             }
224              
225 0 0       0 if (length $err) {
226 0         0 $self->_make_js_error($err, $text);
227             }
228             }
229              
230             sub _node_module_path {
231 3     3   7 my $self = shift;
232 3         71 my @cwd = File::Spec->splitdir(Cwd::getcwd);
233 3         6 my @path;
234              
235 3         5 do {
236 12         66 my $p = File::Spec->catdir(@cwd, 'node_modules');
237 12         12 pop @cwd;
238 12 50       149 push @path, $p if -d $p;
239             } while (@cwd);
240              
241 3         5 warn "[Browserify] node_module_path=[@path]\n" if DEBUG;
242 3         24 return $self->{node_module_path} = \@path;
243             }
244              
245             sub _outfile {
246 0     0     my ($self, $assetpack, $name) = @_;
247 0           my $path = $assetpack->{static}->file(File::Spec->catfile(CACHE_DIR, $name));
248              
249 0 0 0       return $path if $path and -e $path;
250 0           return File::Spec->catfile($assetpack->out_dir, CACHE_DIR, $name);
251             }
252              
253             =head1 COPYRIGHT AND LICENSE
254              
255             Copyright (C) 2014, Jan Henning Thorsen
256              
257             This program is free software, you can redistribute it and/or modify it under
258             the terms of the Artistic License version 2.0.
259              
260             =head1 AUTHOR
261              
262             Jan Henning Thorsen - C
263              
264             =cut
265              
266             1;