File Coverage

blib/lib/Dist/Zilla/Plugin/Web/Bundle.pm
Criterion Covered Total %
statement 85 101 84.1
branch 20 40 50.0
condition 6 10 60.0
subroutine 18 20 90.0
pod 0 8 0.0
total 129 179 72.0


line stmt bran cond sub pod time code
1             package Dist::Zilla::Plugin::Web::Bundle;
2             $Dist::Zilla::Plugin::Web::Bundle::VERSION = '0.0.8';
3             # ABSTRACT: Bundle the library files into "tasks", using information from components.json
4              
5 2     2   678937 use Moose;
  2         330280  
  2         12  
6              
7             with 'Dist::Zilla::Role::FileGatherer';
8             with 'Dist::Zilla::Role::FileMunger';
9              
10 2     2   9776 use Dist::Zilla::File::Generated;
  2         5  
  2         87  
11              
12 2     2   675 use JSON -support_by_pp, -no_export;
  2         7577  
  2         16  
13 2     2   741 use Path::Class;
  2         28262  
  2         115  
14 2     2   1613 use IPC::Run qw( run );
  2         47823  
  2         103  
15 2     2   805 use File::ShareDir;
  2         8222  
  2         92  
16 2     2   866 use Capture::Tiny qw/capture/;
  2         13736  
  2         1791  
17              
18              
19             has 'lazy' => (
20             isa => 'Bool',
21             is => 'rw',
22            
23             default => 0
24             );
25              
26              
27              
28             has 'filename' => (
29             isa => 'Str',
30             is => 'rw',
31            
32             default => 'components.json'
33             );
34              
35              
36             has 'lib_dir' => (
37             isa => 'Str',
38             is => 'rw',
39            
40             default => 'lib'
41             );
42              
43              
44             has 'bundle_files' => (
45             is => 'rw',
46            
47             default => sub { {} }
48             );
49              
50              
51             has 'npm_root' => (
52             is => 'ro',
53            
54             lazy => 1,
55            
56             builder => '_get_npm_root',
57             );
58              
59              
60             has 'components' => (
61             is => 'ro',
62             lazy => 1,
63             builder => '_build_components_info',
64             );
65              
66              
67             #================================================================================================================================================================================================================================================
68             sub _build_components_info {
69 1     1   2 my ($self) = @_;
70              
71 1         27 my $content = file($self->filename)->slurp;
72              
73             #removing // style comments
74 1         289 $content =~ s!//.*$!!gm;
75              
76             #extracting from outermost {} brackets
77 1         4 $content =~ m/(\{.*\})/s;
78 1         3 $content = $1;
79            
80 1         27 my $json = JSON->new->relaxed->allow_singlequote->allow_barekey;
81              
82 1         67 return $json->decode($content);
83             }
84              
85              
86              
87             #================================================================================================================================================================================================================================================
88 1     1 0 87114 sub gather_files {
89             }
90              
91              
92             #================================================================================================================================================================================================================================================
93             # need to build bundles in the "munge" phase - to allow other munge plugins to modify the sources
94             sub munge_files {
95 1     1 0 7876 my ($self) = @_;
96            
97 1         5 $self->process_components();
98             }
99              
100              
101             #================================================================================================================================================================================================================================================
102             sub process_components {
103 1     1 0 2 my $self = shift;
104            
105 1 50       36 return unless -f $self->filename;
106            
107 1         32 my $components = $self->components;
108            
109 1         5 foreach my $component (keys(%$components)) {
110 7         836 $self->process_component($components, $component);
111             }
112            
113             # unless the lazy flag is set (false by default) - generate the content of all bundles right away
114             # otherwise leave them, allowing to other mungers to modify the individual source files before bundling
115 1 50       32 unless ($self->lazy) {
116 1         1 $_->content foreach (values(%{$self->bundle_files})) ;
  1         26  
117             }
118             }
119              
120              
121             #================================================================================================================================================================================================================================================
122             sub process_component {
123 7     7 0 9 my ($self, $components, $component) = @_;
124            
125 7         12 my $componentInfo = $components->{ $component };
126 7 100       15 $componentInfo = { contains => $componentInfo } if ref $componentInfo eq 'ARRAY';
127            
128 7         10 my $saveAs = $componentInfo->{ saveAs };
129            
130             $self->bundle_files->{ $component } = Dist::Zilla::File::Generated->new({
131            
132             name => $saveAs || "foo.js",
133            
134             code => sub {
135 4     4   7 my $bundle_content = '';
136 4         3 my $is_js = 1;
137            
138 4         31 foreach my $entry (@{$componentInfo->{ contains }}) {
  4         11  
139 7 50       1134 $is_js = 0 if $entry =~ /\.css$/;
140            
141 7 50       23 $bundle_content .= $self->get_entry_content($entry, $component) . ($is_js ? ";\n" : '');
142             }
143            
144 4   100     968 my $minify = $componentInfo->{ minify } || '';
145            
146 4 100       8 if ($minify eq 'yui') {
147 1         6 my $yui = dir( File::ShareDir::dist_dir('Dist-Zilla-Plugin-Web'), 'minifiers' )->file('yuicompressor-2.4.6.jar') . '';
148 1 50       203 my $type = $is_js ? 'js' : 'css';
149            
150 1         2 my ($child_out, $child_err);
151              
152 1         10 my $success = run [ "java", "-jar", "$yui", "--type", "$type" ], '<', \$bundle_content, '>', \$child_out, '2>', \$child_err;
153            
154 0 0       0 die "Error during minification with YUI: $child_err" unless $success;
155            
156 0         0 $bundle_content = $child_out;
157             }
158            
159 3         84 return $bundle_content;
160             }
161 7   100     239 });
162            
163             # only store the bundles that has "saveAs"
164 7 100       20 if ($saveAs) {
165 3         7 my $already_has_file = $self->get_dzil_file($saveAs);
166            
167 3 50       5 $self->zilla->prune_file($already_has_file) if $already_has_file;
168            
169 3         84 $self->add_file($self->bundle_files->{ $component });
170             }
171             }
172              
173              
174             #================================================================================================================================================================================================================================================
175             sub get_entry_content {
176 7     7 0 10 my ($self, $entry, $component) = @_;
177            
178 7 50 33     47 if ((ref $entry eq 'HASH') && $entry->{ text }) {
    100 33        
    50          
179            
180 0         0 return $entry->{ text };
181            
182             } elsif ($entry =~ /^\+(.+)/) {
183            
184 3         80 my $bundleFile = $self->bundle_files->{ $1 };
185            
186 3 50       5 die "Reference to non-existend bundle [$1] from [$component]" if !$bundleFile;
187            
188 3         75 return $bundleFile->content;
189            
190             } elsif ($entry !~ /\// && $entry !~ /\.js$/ && $entry !~ /\.css$/) {
191            
192 4         17 return $self->get_file_content($self->entry_to_filename($entry), $component);
193            
194             } else {
195 0         0 return $self->get_file_content($entry, $component);
196             }
197             }
198              
199              
200             #================================================================================================================================================================================================================================================
201             sub get_dzil_file {
202 7     7 0 8 my ($self, $file_name) = @_;
203            
204 7         6 my $found;
205            
206 7         7 for my $file (@{$self->zilla->files}) {
  7         163  
207            
208 72 100       2350 if ($file->name eq $file_name) {
209 4         167 $found = $file;
210            
211 4         8 last;
212             }
213             }
214            
215 7         98 return $found;
216             }
217              
218              
219             #================================================================================================================================================================================================================================================
220             sub get_file_content {
221 4     4 0 278 my ($self, $file_name, $component) = @_;
222            
223 4         11 my $found = $self->get_dzil_file($file_name);
224            
225             # return content of gathered file if found
226 4 50       16 return $found->content if $found;
227            
228             # return content of file in the distribution if presenteed
229 0 0       0 return file($file_name)->slurp . '' if -e $file_name;
230            
231             # when file name starts with "node_modules" also look in global modules (as last resort)
232 0 0       0 if ($file_name =~ m!^node_modules/(.*)!) {
233 0         0 my $npm_file_name = dir($self->npm_root)->file($1);
234            
235 0 0       0 return file($npm_file_name)->slurp . '' if -e $npm_file_name;
236             }
237            
238             # cry out
239 0         0 die "Can't find file [$file_name] in [$component]";
240             }
241              
242              
243             #================================================================================================================================================================================================================================================
244             sub _get_npm_root {
245            
246 0     0   0 my $child_exit_status;
247            
248             my ($npm_root, $stderr) = capture {
249 0     0   0 system('npm root -g');
250            
251 0         0 $child_exit_status = $? >> 8;
252 0         0 };
253            
254 0 0       0 die "Error when getting npm root: $child_exit_status" if $child_exit_status;
255            
256 0         0 chomp($npm_root);
257            
258 0         0 return $npm_root;
259             }
260              
261              
262              
263              
264             #================================================================================================================================================================================================================================================
265             sub entry_to_filename {
266 4     4 0 7 my ($self, $entry) = @_;
267            
268 4         11 my @dirs = split /\./, $entry;
269 4         5 $dirs[-1] .= '.js';
270            
271 4         123 return file($self->lib_dir, @dirs);
272             }
273              
274              
275 2     2   10 no Moose;
  2         3  
  2         16  
276             __PACKAGE__->meta->make_immutable(inline_constructor => 0);
277              
278              
279             1;
280              
281             __END__
282              
283             =pod
284              
285             =encoding UTF-8
286              
287             =head1 NAME
288              
289             Dist::Zilla::Plugin::Web::Bundle - Bundle the library files into "tasks", using information from components.json
290              
291             =head1 VERSION
292              
293             version 0.0.8
294              
295             =head1 SYNOPSIS
296              
297             In your F<dist.ini>:
298              
299             [NPM::Bundle]
300             filename = components.json ; default
301              
302             In your F<components.json>:
303              
304             {
305            
306             Core : {
307             contains : [
308             "KiokuJS.Reference",
309            
310             "KiokuJS.Exception",
311             "KiokuJS.Exception.Network",
312             "KiokuJS.Exception.Format",
313             "KiokuJS.Exception.Overwrite",
314             "KiokuJS.Exception.Update",
315             "KiokuJS.Exception.Remove",
316             "KiokuJS.Exception.LookUp",
317             "KiokuJS.Exception.Conflict"
318             ],
319             }
320            
321            
322             Prereq : {
323            
324             contains : [
325             "node_modules/task-joose-stable/joose-stable.js",
326             "node_modules/joosex-attribute/joosex-attribute.js"
327             ],
328             },
329            
330            
331             All : {
332             saveAs : 'kiokujs-all.js',
333            
334             contains : [
335             "+Core",
336             "+Prereq"
337             ]
338             },
339            
340            
341             AllMin : {
342             saveAs : 'kiokujs-all-min.js',
343            
344             minify : 'yui',
345            
346             contains : [
347             "+All"
348             ]
349             }
350             }
351              
352             =head1 DESCRIPTION
353              
354             This plugins concatenates several source files into single bundle using the information from components.json file.
355              
356             This files contains a simple JavaScript assignment (to allow inclusion via <script> tag) of the JSON structure.
357              
358             First level entries of the JSON structure defines a bundles. Each bundle is an array of entries.
359              
360             Entry, starting with the "+" prefix denotes the content of another bundle.
361              
362             All other entries denotes the javascript files from the "lib" directory. For example entry "KiokuJS.Reference" will be fetched
363             as the content of the file "lib/KiokuJS/Reference.js"
364              
365             All bundles are stored as "lib/Task/Distribution/Name/BundleName.js", assuming the name of the distrubution is "Distribution-Name"
366             and name of bundle - "BundleName". During release, all bundles also gets added to the root of distribution as
367             "task-distribution-name-bundlename.js". To enable the latter feature for regular builds add the `roots_only_for_release = 0` config option
368              
369             =head1 AUTHOR
370              
371             Nickolay Platonov <nplatonov@cpan.org>
372              
373             =head1 COPYRIGHT AND LICENSE
374              
375             This software is copyright (c) 2015 by Nickolay Platonov.
376              
377             This is free software; you can redistribute it and/or modify it under
378             the same terms as the Perl 5 programming language system itself.
379              
380             =cut