File Coverage

blib/lib/Mojolicious/Command/browserify.pm
Criterion Covered Total %
statement 46 102 45.1
branch 6 34 17.6
condition 4 23 17.3
subroutine 10 15 66.6
pod 4 4 100.0
total 70 178 39.3


line stmt bran cond sub pod time code
1             package Mojolicious::Command::browserify;
2              
3             =head1 NAME
4              
5             Mojolicious::Command::browserify - A Mojolicious command for browserify
6              
7             =head1 DESCRIPTION
8              
9             L is a L command which handle
10             C dependencies and installation.
11              
12             =head1 SYNOPSIS
13              
14             # Bundle an asset from command line
15             \$ mojo browserify bundle input.js
16              
17             # Pass on "-t reactify" to browserify
18             \$ mojo browserify bundle -t reactify input.js
19              
20             # Watch input files and write output to out.js
21             \$ mojo browserify bundle -t reactify input.js -w -o out.js
22              
23             # Make a minified bundle
24             \$ MOJO_MODE=production mojo browserify bundle -t reactify input.js
25              
26             # Install dependencies
27             \$ mojo browserify install browserify
28             \$ mojo browserify install reactify
29              
30             # Check installed versions
31             \$ mojo browserify version
32              
33             =cut
34              
35 10     10   122810 use Mojo::Base 'Mojolicious::Command';
  10         30  
  10         50  
36 10     10   1492340 use Cwd 'abs_path';
  10         20  
  10         1570  
37 10     10   80 use File::Basename 'dirname';
  10         20  
  10         24530  
38              
39             my $NPM = $ENV{NODE_NPM_BIN} || 'npm';
40              
41             $ENV{MOJO_LOG_LEVEL} ||= 'info';
42              
43             =head1 ATTRIBUTES
44              
45             =head2 description
46              
47             Short description of command, used for the command list.
48              
49             =head2 usage
50              
51             Usage information for command, used for the help screen.
52              
53             =cut
54              
55             has description => "Manage browserify.";
56             has usage => <<"HERE";
57             # Bundle an asset from command line
58             \$ mojo browserify bundle input.js
59              
60             # Pass on "-t reactify" to browserify
61             \$ mojo browserify bundle -t reactify input.js
62              
63             # Watch input files and write output to out.js
64             \$ mojo browserify bundle -t reactify input.js -w -o out.js
65              
66             # Make a minified bundle
67             \$ MOJO_MODE=production mojo browserify bundle -t reactify input.js
68              
69             # Install dependencies
70             \$ mojo browserify install browserify
71             \$ mojo browserify install reactify
72              
73             # Check installed versions
74             \$ mojo browserify version
75              
76             HERE
77              
78             =head1 METHODS
79              
80             =head2 bundle
81              
82             This method will bundle a JavaScript file from the command line.
83              
84             =cut
85              
86             sub bundle {
87 0     0 1 0 my $self = shift->_parse_bundle_args(@_);
88              
89 0         0 require Mojolicious::Plugin::AssetPack;
90 0         0 require Mojolicious::Plugin::Browserify::Processor;
91 0         0 require Mojolicious::Static;
92              
93 0         0 my $assetpack = Mojolicious::Plugin::AssetPack->new;
94 0         0 my $processor = Mojolicious::Plugin::Browserify::Processor->new;
95              
96 0         0 $assetpack->out_dir(Cwd::getcwd)->{static} = Mojolicious::Static->new;
97 0 0       0 $assetpack->minify(1) if $processor->environment eq 'production';
98 0   0     0 $processor->browserify_args($self->{browserify_args} || []);
99 0 0       0 chdir dirname $self->{in} or die "Could not chdir to $self->{in}: $!\n";
100              
101 0   0     0 do {
102 0         0 my $javascript = Mojo::Util::slurp($self->{in});
103 0         0 $processor->process($assetpack, \$javascript, $self->{in});
104 0 0       0 if ($self->{out}) {
105 0         0 Mojo::Util::spurt($javascript, $self->{out});
106 0         0 $self->app->log->info("Wrote $self->{out}");
107             }
108             else {
109 0         0 $self->_printf("%s\n", $javascript);
110             }
111             } while $self->{watch} and $self->_watch($processor);
112             }
113              
114             =head2 install
115              
116             This method will run C. It will die unless
117             browserify was installed.
118              
119             =cut
120              
121             sub install {
122 10     10 1 7900 my $self = shift;
123 10   50     60 my $module = shift || 'browserify';
124 10     9   120 my $exit_value = $self->_npm(install => $module, sub { shift->_printf('%s', shift); });
  9         189  
125 9 50       279 die "'npm install browserify' failed: $exit_value\n" if $exit_value;
126 0         0 $self->_printf("\nbrowserify was installed\n");
127             }
128              
129             =head2 run
130              
131             Run command and call L or L.
132              
133             =cut
134              
135             sub run {
136 0     0 1 0 my $self = shift;
137 0   0     0 my $action = shift || '';
138              
139 0 0       0 exec perldoc => __FILE__ if $action eq 'help';
140 0 0       0 return print $self->usage unless $action =~ /^(bundle|install|version)$/;
141 0         0 return $self->$action(@_);
142             }
143              
144             =head2 version
145              
146             Print version information.
147              
148             =cut
149              
150             sub version {
151 9     9 1 5769 my ($self, @args) = @_;
152              
153 9         117 require Mojolicious::Plugin::Browserify;
154 9         45 $self->_printf("REQUIRED\n");
155 9         333 $self->_printf(" %-40s %s\n", 'Mojolicious::Plugin::Browserify', Mojolicious::Plugin::Browserify->VERSION);
156 9         81 $self->_npm_version($_) for qw( browserify );
157 7         189 $self->_printf("\nOPTIONAL\n");
158 7         49 $self->_npm_version($_) for qw( react reactify uglifyjs );
159 1         25 $self->_printf("\n");
160             }
161              
162             sub _npm {
163 54     54   132 my $cb = pop;
164 54         465 my ($self, @cmd) = @_;
165 54         97 my $pid;
166              
167 54 50       1702 pipe my $CHILD_READ, my $CHILD_WRITE or die "Unable to create pipe to npm: $!";
168 54   50     37482 $pid = fork // die "Could not fork npm: $!\n";
169              
170 54 100       1506 if ($pid) {
171 45         963 close $CHILD_WRITE;
172 45         613 local $_;
173 45         16815236 $self->$cb($_) while <$CHILD_READ>;
174 45         58405 waitpid $pid, 0;
175 45         1998 return $? >> 8;
176             }
177              
178 9         557 close $CHILD_READ;
179 9         241 close STDERR;
180 9         894 open STDOUT, '>&', fileno($CHILD_WRITE);
181 9         120 { exec $NPM => @cmd }
  9         0  
182 0         0 my $err = $!;
183 0         0 print "Could not exec $NPM: $err (Is npm installed?)\n";
184 0         0 exit $err;
185             }
186              
187             sub _npm_version {
188 24     24   199 my ($self, $module) = @_;
189 24         50 my $format = " %-40s %-14s (%s)\n";
190 24         40 my ($installed, $latest);
191              
192 24 50   20   253 $self->_npm(qw( --json list ), $module, sub { $installed = $1 if /"version"\D+([\d\.]+)/ });
  20         76802  
193 20 50   16   486 $self->_npm(view => $module => 'version', sub { $latest = $1 if /([\d\.]+)/ });
  16         78087  
194 16   50     525 $self->_printf($format, $module, $installed || 'Not installed', $latest || 'Unknown');
      50        
195             }
196              
197             sub _parse_bundle_args {
198 0     0     my ($self, @args) = @_;
199              
200 0           while (@args) {
201 0   0       my $arg = shift @args // next;
202 0 0         if ($arg =~ /^--?w/) { $self->{watch} = 1; next }
  0            
  0            
203 0 0         if ($arg =~ /^--?o/) { $self->{out} = abs_path(shift @args); next }
  0            
  0            
204 0 0         if (-r $arg) { $self->{in} = abs_path($arg); next }
  0            
  0            
205 0           push @{$self->{browserify_args}}, $arg;
  0            
206             }
207              
208 0 0         die "Usage: mojo browserify bundle \n" unless $self->{in};
209 0 0 0       die "-o is required in watch mode (-w).\n" if $self->{watch} and !$self->{out};
210 0           $self;
211             }
212              
213 0     0     sub _printf { shift; printf shift, @_; }
  0            
214              
215             sub _watch {
216 0     0     my ($self, $processor) = @_;
217 0           my @watch = ($self->{in}, values %{$processor->{node_modules}});
  0            
218 0           my $cache = {};
219              
220 0           $self->app->log->debug("Watching @watch");
221              
222 0           while (1) {
223 0           for my $file (@watch) {
224 0 0         my $mtime = (stat $file)[9] or next;
225 0   0       $cache->{$file} ||= $mtime;
226 0 0         return 1 unless $cache->{$file} == $mtime;
227             }
228 0           sleep 1;
229             }
230             }
231              
232             =head1 COPYRIGHT AND LICENSE
233              
234             Copyright (C) 2014, Jan Henning Thorsen
235              
236             This program is free software, you can redistribute it and/or modify it under
237             the terms of the Artistic License version 2.0.
238              
239             =head1 AUTHOR
240              
241             Jan Henning Thorsen - C
242              
243             =cut
244              
245             1;