line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package HTML::DeferableCSS; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Simplify management of stylesheets in your HTML |
4
|
|
|
|
|
|
|
|
5
|
4
|
|
|
4
|
|
527381
|
use v5.10; |
|
4
|
|
|
|
|
53
|
|
6
|
4
|
|
|
4
|
|
2443
|
use Moo 1.006000; |
|
4
|
|
|
|
|
40228
|
|
|
4
|
|
|
|
|
23
|
|
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
6708
|
use Carp; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
221
|
|
9
|
4
|
|
|
4
|
|
1930
|
use Devel::StrictMode; |
|
4
|
|
|
|
|
1665
|
|
|
4
|
|
|
|
|
240
|
|
10
|
4
|
|
|
4
|
|
1575
|
use File::ShareDir 1.112 qw/ dist_file /; |
|
4
|
|
|
|
|
75732
|
|
|
4
|
|
|
|
|
237
|
|
11
|
4
|
|
|
4
|
|
1752
|
use MooX::TypeTiny; |
|
4
|
|
|
|
|
1231
|
|
|
4
|
|
|
|
|
24
|
|
12
|
4
|
|
|
4
|
|
53172
|
use List::Util 1.45 qw/ first uniqstr /; |
|
4
|
|
|
|
|
99
|
|
|
4
|
|
|
|
|
537
|
|
13
|
4
|
|
|
4
|
|
3656
|
use Path::Tiny; |
|
4
|
|
|
|
|
47043
|
|
|
4
|
|
|
|
|
238
|
|
14
|
4
|
|
|
4
|
|
2020
|
use Types::Path::Tiny qw/ Dir File Path /; |
|
4
|
|
|
|
|
518727
|
|
|
4
|
|
|
|
|
39
|
|
15
|
4
|
|
|
4
|
|
5011
|
use Types::Common::Numeric qw/ PositiveOrZeroInt /; |
|
4
|
|
|
|
|
90565
|
|
|
4
|
|
|
|
|
35
|
|
16
|
4
|
|
|
4
|
|
4249
|
use Types::Common::String qw/ NonEmptySimpleStr SimpleStr /; |
|
4
|
|
|
|
|
216203
|
|
|
4
|
|
|
|
|
55
|
|
17
|
4
|
|
|
4
|
|
3044
|
use Types::Standard qw/ Bool CodeRef HashRef Maybe Tuple /; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
27
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# RECOMMEND PREREQ: Type::Tiny::XS |
20
|
|
|
|
|
|
|
|
21
|
4
|
|
|
4
|
|
7183
|
use namespace::autoclean; |
|
4
|
|
|
|
|
60226
|
|
|
4
|
|
|
|
|
25
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
our $VERSION = 'v0.4.0'; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
has aliases => ( |
28
|
|
|
|
|
|
|
is => 'ro', |
29
|
|
|
|
|
|
|
isa => STRICT ? HashRef [Maybe[SimpleStr]] : HashRef, |
30
|
|
|
|
|
|
|
required => 1, |
31
|
|
|
|
|
|
|
coerce => sub { |
32
|
|
|
|
|
|
|
return { map { $_ => $_ } @{$_[0]} } if ref $_[0] eq 'ARRAY'; |
33
|
|
|
|
|
|
|
return $_[0]; |
34
|
|
|
|
|
|
|
}, |
35
|
|
|
|
|
|
|
); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
has css_root => ( |
39
|
|
|
|
|
|
|
is => 'ro', |
40
|
|
|
|
|
|
|
isa => Dir, |
41
|
|
|
|
|
|
|
coerce => 1, |
42
|
|
|
|
|
|
|
required => 1, |
43
|
|
|
|
|
|
|
); |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
has url_base_path => ( |
47
|
|
|
|
|
|
|
is => 'ro', |
48
|
|
|
|
|
|
|
isa => SimpleStr, |
49
|
|
|
|
|
|
|
default => '/', |
50
|
|
|
|
|
|
|
); |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
has prefer_min => ( |
54
|
|
|
|
|
|
|
is => 'ro', |
55
|
|
|
|
|
|
|
isa => Bool, |
56
|
|
|
|
|
|
|
default => 1, |
57
|
|
|
|
|
|
|
); |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
has css_files => ( |
61
|
|
|
|
|
|
|
is => 'lazy', |
62
|
|
|
|
|
|
|
isa => STRICT |
63
|
|
|
|
|
|
|
? HashRef [ Tuple [ Maybe[Path], Maybe[NonEmptySimpleStr], PositiveOrZeroInt ] ] |
64
|
|
|
|
|
|
|
: HashRef, |
65
|
|
|
|
|
|
|
builder => 1, |
66
|
|
|
|
|
|
|
coerce => 1, |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
|
69
|
4
|
|
|
4
|
|
1181
|
use constant PATH => 0; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
260
|
|
70
|
4
|
|
|
4
|
|
33
|
use constant NAME => 1; |
|
4
|
|
|
|
|
16
|
|
|
4
|
|
|
|
|
215
|
|
71
|
4
|
|
|
4
|
|
27
|
use constant SIZE => 2; |
|
4
|
|
|
|
|
18
|
|
|
4
|
|
|
|
|
6952
|
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
sub _build_css_files { |
74
|
28
|
|
|
28
|
|
7240
|
my ($self) = @_; |
75
|
|
|
|
|
|
|
|
76
|
28
|
|
|
|
|
100
|
my $root = $self->css_root; |
77
|
28
|
|
|
|
|
67
|
my $min = $self->prefer_min; |
78
|
|
|
|
|
|
|
|
79
|
28
|
|
|
|
|
49
|
my %files; |
80
|
28
|
|
|
|
|
95
|
for my $name (keys %{ $self->aliases }) { |
|
28
|
|
|
|
|
122
|
|
81
|
38
|
|
|
|
|
18255
|
my $base = $self->aliases->{$name}; |
82
|
38
|
100
|
|
|
|
163
|
if (!$base) { |
|
|
100
|
|
|
|
|
|
83
|
6
|
|
|
|
|
21
|
$files{$name} = [ undef, undef, 0 ]; |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
elsif ($base =~ m{^(\w+:)?//}) { |
86
|
4
|
|
|
|
|
19
|
$files{$name} = [ undef, $base, 0 ]; |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
else { |
89
|
28
|
100
|
|
|
|
80
|
$base = $name if $base eq '1'; |
90
|
28
|
|
|
|
|
76
|
$base =~ s/(?:\.min)?\.css$//; |
91
|
28
|
100
|
|
|
|
120
|
my @bases = $min |
92
|
|
|
|
|
|
|
? ( "${base}.min.css", "${base}.css", $base ) |
93
|
|
|
|
|
|
|
: ( "${base}.css", "${base}.min.css", $base ); |
94
|
|
|
|
|
|
|
|
95
|
28
|
|
|
45
|
|
135
|
my $file = first { $_->exists } map { path( $root, $_ ) } @bases; |
|
45
|
|
|
|
|
1408
|
|
|
84
|
|
|
|
|
2145
|
|
96
|
28
|
100
|
|
|
|
848
|
unless ($file) { |
97
|
4
|
|
|
|
|
27
|
$self->log->( error => "alias '$name' refers to a non-existent file" ); |
98
|
0
|
|
|
|
|
0
|
next; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
# PATH NAME SIZE |
101
|
24
|
|
|
|
|
114
|
$files{$name} = [ $file, $file->relative($root)->stringify, $file->stat->size ]; |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
24
|
|
|
|
|
25016
|
return \%files; |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
has cdn_links => ( |
110
|
|
|
|
|
|
|
is => 'ro', |
111
|
|
|
|
|
|
|
isa => STRICT ? HashRef [NonEmptySimpleStr] : HashRef, |
112
|
|
|
|
|
|
|
predicate => 1, |
113
|
|
|
|
|
|
|
); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
has use_cdn_links => ( |
117
|
|
|
|
|
|
|
is => 'lazy', |
118
|
|
|
|
|
|
|
isa => Bool, |
119
|
|
|
|
|
|
|
builder => 'has_cdn_links', |
120
|
|
|
|
|
|
|
); |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
has inline_max => ( |
124
|
|
|
|
|
|
|
is => 'ro', |
125
|
|
|
|
|
|
|
isa => PositiveOrZeroInt, |
126
|
|
|
|
|
|
|
default => 1024, |
127
|
|
|
|
|
|
|
); |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
has defer_css => ( |
131
|
|
|
|
|
|
|
is => 'ro', |
132
|
|
|
|
|
|
|
isa => Bool, |
133
|
|
|
|
|
|
|
default => 1, |
134
|
|
|
|
|
|
|
); |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
has include_noscript => ( |
138
|
|
|
|
|
|
|
is => 'lazy', |
139
|
|
|
|
|
|
|
isa => Bool, |
140
|
|
|
|
|
|
|
builder => 'defer_css', |
141
|
|
|
|
|
|
|
); |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
has preload_script => ( |
145
|
|
|
|
|
|
|
is => 'lazy', |
146
|
|
|
|
|
|
|
isa => File, |
147
|
|
|
|
|
|
|
coerce => 1, |
148
|
1
|
|
|
1
|
|
536
|
builder => sub { dist_file( qw/ HTML-DeferableCSS cssrelpreload.min.js / ) }, |
149
|
|
|
|
|
|
|
); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
has link_template => ( |
153
|
|
|
|
|
|
|
is => 'ro', |
154
|
|
|
|
|
|
|
isa => CodeRef, |
155
|
|
|
|
|
|
|
builder => sub { |
156
|
18
|
|
|
18
|
|
134
|
return sub { sprintf('<link rel="stylesheet" href="%s">', @_) }, |
157
|
26
|
|
|
26
|
|
9218
|
}, |
158
|
|
|
|
|
|
|
); |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
has preload_template => ( |
163
|
|
|
|
|
|
|
is => 'lazy', |
164
|
|
|
|
|
|
|
isa => CodeRef, |
165
|
|
|
|
|
|
|
builder => sub { |
166
|
2
|
|
|
2
|
|
21
|
my ($self) = @_; |
167
|
2
|
100
|
|
|
|
8
|
if ($self->simple) { |
168
|
|
|
|
|
|
|
return sub { |
169
|
1
|
|
|
1
|
|
16
|
sprintf('<link rel="preload" as="style" href="%s">' . |
170
|
|
|
|
|
|
|
'<link rel="stylesheet" href="%s" media="print" onload="this.media=\'all\';this.onload=null;">', |
171
|
|
|
|
|
|
|
$_[0], $_[0]) |
172
|
|
|
|
|
|
|
} |
173
|
1
|
|
|
|
|
20
|
} |
174
|
|
|
|
|
|
|
else { |
175
|
1
|
|
|
1
|
|
20
|
return sub { sprintf('<link rel="preload" as="style" href="%s" onload="this.onload=null;this.rel=\'stylesheet\'">', $_[0]) }; |
|
1
|
|
|
|
|
16
|
|
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
}, |
178
|
|
|
|
|
|
|
); |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
has asset_id => ( |
182
|
|
|
|
|
|
|
is => 'ro', |
183
|
|
|
|
|
|
|
isa => NonEmptySimpleStr, |
184
|
|
|
|
|
|
|
predicate => 1, |
185
|
|
|
|
|
|
|
); |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
has log => ( |
189
|
|
|
|
|
|
|
is => 'ro', |
190
|
|
|
|
|
|
|
isa => CodeRef, |
191
|
|
|
|
|
|
|
builder => sub { |
192
|
|
|
|
|
|
|
return sub { |
193
|
8
|
|
|
8
|
|
30
|
my ($level, $message) = @_; |
194
|
8
|
100
|
|
|
|
111
|
croak $message if ($level eq 'error'); |
195
|
2
|
|
|
|
|
31
|
carp $message; |
196
|
26
|
|
|
26
|
|
686
|
}; |
197
|
|
|
|
|
|
|
}, |
198
|
|
|
|
|
|
|
); |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
has simple => ( |
202
|
|
|
|
|
|
|
is => 'ro', |
203
|
|
|
|
|
|
|
isa => Bool, |
204
|
|
|
|
|
|
|
default => 0, |
205
|
|
|
|
|
|
|
); |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub check { |
209
|
5
|
|
|
5
|
1
|
3204
|
my ($self) = @_; |
210
|
|
|
|
|
|
|
|
211
|
5
|
|
|
|
|
123
|
my $files = $self->css_files; |
212
|
|
|
|
|
|
|
|
213
|
3
|
100
|
|
|
|
174
|
scalar(keys %$files) or |
214
|
|
|
|
|
|
|
return $self->log->( error => "no aliases" ); |
215
|
|
|
|
|
|
|
|
216
|
2
|
|
|
|
|
13
|
return 1; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub href { |
221
|
28
|
|
|
28
|
1
|
2729
|
my ($self, $name, $file) = @_; |
222
|
28
|
50
|
66
|
|
|
113
|
$file //= $self->_get_file($name) or return; |
223
|
28
|
100
|
|
|
|
75
|
if (defined $file->[PATH]) { |
224
|
20
|
|
|
|
|
71
|
my $href = $self->url_base_path . $file->[NAME]; |
225
|
20
|
100
|
|
|
|
75
|
$href .= '?' . $self->asset_id if $self->has_asset_id; |
226
|
20
|
100
|
66
|
|
|
367
|
if ($self->use_cdn_links && $self->has_cdn_links) { |
227
|
1
|
|
33
|
|
|
33
|
return $self->cdn_links->{$name} // $href; |
228
|
|
|
|
|
|
|
} |
229
|
19
|
|
|
|
|
286
|
return $href; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
else { |
232
|
8
|
|
|
|
|
27
|
return $file->[NAME]; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub link_html { |
238
|
21
|
|
|
21
|
1
|
3447
|
my ( $self, $name, $file ) = @_; |
239
|
21
|
100
|
|
|
|
58
|
if (my $href = $self->href( $name, $file )) { |
240
|
17
|
|
|
|
|
59
|
return $self->link_template->($href); |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
else { |
243
|
4
|
|
|
|
|
15
|
return ""; |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub inline_html { |
249
|
9
|
|
|
9
|
1
|
865
|
my ( $self, $name, $file ) = @_; |
250
|
9
|
50
|
66
|
|
|
40
|
$file //= $self->_get_file($name) or return; |
251
|
9
|
100
|
|
|
|
39
|
if (my $path = $file->[PATH]) { |
252
|
8
|
100
|
|
|
|
22
|
if ($file->[SIZE]) { |
253
|
6
|
|
|
|
|
24
|
return "<style>" . $file->[PATH]->slurp_raw . "</style>"; |
254
|
|
|
|
|
|
|
} |
255
|
2
|
|
|
|
|
10
|
$self->log->( warning => "empty file '$path'" ); |
256
|
2
|
|
|
|
|
934
|
return ""; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
else { |
259
|
1
|
|
|
|
|
8
|
$self->log->( error => "'$name' refers to a URI" ); |
260
|
0
|
|
|
|
|
0
|
return; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub link_or_inline_html { |
266
|
5
|
|
|
5
|
1
|
2517
|
my ($self, @names ) = @_; |
267
|
5
|
|
|
|
|
12
|
my $buffer = ""; |
268
|
5
|
|
|
|
|
21
|
foreach my $name (uniqstr @names) { |
269
|
8
|
50
|
|
|
|
187
|
my $file = $self->_get_file($name) or next; |
270
|
8
|
100
|
100
|
|
|
61
|
if ( $file->[PATH] && ($file->[SIZE] <= $self->inline_max)) { |
271
|
3
|
|
|
|
|
9
|
$buffer .= $self->inline_html($name, $file); |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
else { |
274
|
5
|
|
|
|
|
14
|
$buffer .= $self->link_html($name, $file); |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
} |
277
|
5
|
|
|
|
|
186
|
return $buffer; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub deferred_link_html { |
282
|
6
|
|
|
6
|
1
|
2483
|
my ($self, @names) = @_; |
283
|
6
|
|
|
|
|
13
|
my $buffer = ""; |
284
|
6
|
|
|
|
|
10
|
my @deferred; |
285
|
6
|
|
|
|
|
24
|
for my $name (uniqstr @names) { |
286
|
7
|
50
|
|
|
|
17
|
my $file = $self->_get_file($name) or next; |
287
|
7
|
100
|
100
|
|
|
58
|
if ($file->[PATH] && $file->[SIZE] <= $self->inline_max) { |
|
|
100
|
|
|
|
|
|
288
|
1
|
|
|
|
|
7
|
$buffer .= $self->inline_html($name, $file); |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
elsif ($self->defer_css) { |
291
|
2
|
|
|
|
|
8
|
my $href = $self->href($name, $file); |
292
|
2
|
|
|
|
|
7
|
push @deferred, $href; |
293
|
2
|
|
|
|
|
34
|
$buffer .= $self->preload_template->($href); |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
else { |
296
|
4
|
|
|
|
|
12
|
$buffer .= $self->link_html($name, $file); |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
6
|
100
|
|
|
|
223
|
if (@deferred) { |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
$buffer .= "<noscript>" . |
303
|
2
|
100
|
|
|
|
36
|
join("", map { $self->link_template->($_) } @deferred ) . |
|
1
|
|
|
|
|
36
|
|
304
|
|
|
|
|
|
|
"</noscript>" if $self->include_noscript; |
305
|
|
|
|
|
|
|
|
306
|
2
|
100
|
|
|
|
36
|
$buffer .= "<script>" . |
307
|
|
|
|
|
|
|
$self->preload_script->slurp_raw . |
308
|
|
|
|
|
|
|
"</script>" unless $self->simple; |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
|
312
|
6
|
|
|
|
|
201
|
return $buffer; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub _get_file { |
316
|
37
|
|
|
37
|
|
76
|
my ($self, $name) = @_; |
317
|
37
|
50
|
|
|
|
107
|
unless (defined $name) { |
318
|
0
|
|
|
|
|
0
|
$self->log->( error => "missing name" ); |
319
|
0
|
|
|
|
|
0
|
return; |
320
|
|
|
|
|
|
|
} |
321
|
37
|
50
|
|
|
|
829
|
if (my $file = $self->css_files->{$name}) { |
322
|
37
|
|
|
|
|
1098
|
return $file; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
else { |
325
|
0
|
|
|
|
|
|
$self->log->( error => "invalid name '$name'" ); |
326
|
0
|
|
|
|
|
|
return; |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
1; |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
__END__ |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=pod |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
=encoding UTF-8 |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=head1 NAME |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
HTML::DeferableCSS - Simplify management of stylesheets in your HTML |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=head1 VERSION |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
version v0.4.0 |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=head1 SYNOPSIS |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
use HTML::DeferableCSS; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
my $css = HTML::DeferableCSS->new( |
354
|
|
|
|
|
|
|
css_root => '/var/www/css', |
355
|
|
|
|
|
|
|
url_base_path => '/css', |
356
|
|
|
|
|
|
|
inline_max => 512, |
357
|
|
|
|
|
|
|
simple => 1, |
358
|
|
|
|
|
|
|
aliases => { |
359
|
|
|
|
|
|
|
reset => 1, |
360
|
|
|
|
|
|
|
jqui => 'jquery-ui', |
361
|
|
|
|
|
|
|
site => 'style', |
362
|
|
|
|
|
|
|
}, |
363
|
|
|
|
|
|
|
cdn => { |
364
|
|
|
|
|
|
|
jqui => '//cdn.example.com/jquery-ui.min.css', |
365
|
|
|
|
|
|
|
}, |
366
|
|
|
|
|
|
|
); |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
$css->check or die "Something is wrong"; |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
... |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
print $css->deferred_link_html( qw[ jqui site ] ); |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=head1 DESCRIPTION |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
This is an experimental module for generating HTML-snippets for |
377
|
|
|
|
|
|
|
deferable stylesheets. |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
This allows the stylesheets to be loaded asynchronously, allowing the |
380
|
|
|
|
|
|
|
page to be rendered faster. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
Ideally, this would be a simple matter of changing stylesheet links |
383
|
|
|
|
|
|
|
to something like |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
<link rel="preload" as="stylesheet" href="...."> |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
but this is not well supported by all web browsers. So a web page needs |
388
|
|
|
|
|
|
|
to use some L<JavaScript|https://github.com/filamentgroup/loadCSS> |
389
|
|
|
|
|
|
|
to handle this, as well as a C<noscript> block as a fallback. |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
This module allows you to simplify the management of stylesheets for a |
392
|
|
|
|
|
|
|
web application, from development to production by |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=over |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
=item * |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
declaring all stylesheets used by your web application; |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
=item * |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
specifying remote aliases for stylesheets, e.g. from a CDN; |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
=item * |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
enable or disable the use of minified stylesheets; |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=item * |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
switch between local copies of stylesheets or CDN versions; |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=item * |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
automatically inline small stylesheets; |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=item * |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
use deferred-loading stylesheets, which requires embedding JavaScript |
419
|
|
|
|
|
|
|
code as a workaround for web browsers that do not support these |
420
|
|
|
|
|
|
|
natively. |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
=back |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=head2 aliases |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
This is a required hash reference of names and their relative |
429
|
|
|
|
|
|
|
filenames to L</css_root>. |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
It is recommended that the F<.css> and F<.min.css> suffixes be |
432
|
|
|
|
|
|
|
omitted. |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
If the name is the same as the filename (without the extension) than |
435
|
|
|
|
|
|
|
you can simply use C<1>. (Likewise, an empty string or C<0> disables |
436
|
|
|
|
|
|
|
the alias: |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
my $css = HTML::DeferableCSS->new( |
439
|
|
|
|
|
|
|
aliases => { |
440
|
|
|
|
|
|
|
reset => 1, |
441
|
|
|
|
|
|
|
gone => 0, # "gone" will be silently ignored |
442
|
|
|
|
|
|
|
one => "1.css", # |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
... |
445
|
|
|
|
|
|
|
); |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
If all names are the same as their filenames, then an array reference |
448
|
|
|
|
|
|
|
can be used: |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
my $css = HTML::DeferableCSS->new( |
451
|
|
|
|
|
|
|
aliases => [ qw( foo bar } ], |
452
|
|
|
|
|
|
|
... |
453
|
|
|
|
|
|
|
); |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
If an alias is disabled, then it will simply be ignored, e.g. |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
$css->deferred_link_html('gone') |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
Returns an empty string. This allows you to disable a stylesheet in |
460
|
|
|
|
|
|
|
your configuration without having to remove all references to it. |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
Absolute paths cannot be used. |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
You may specify URLs instead of files, but this is not recommended, |
465
|
|
|
|
|
|
|
except for cases when the files are not available locally. |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
=head2 css_root |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
This is the required root directory where all stylesheets can be |
470
|
|
|
|
|
|
|
found. |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=head2 url_base_path |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
This is the URL prefix for stylesheets. |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
It can be a full URL prefix. |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=head2 prefer_min |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
If true (default), then a file with the F<.min.css> suffix will be |
481
|
|
|
|
|
|
|
preferred, if it exists in the same directory. |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
Note that this does not do any minification. You will need separate |
484
|
|
|
|
|
|
|
tools for that. |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
=head2 css_files |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
This is a hash reference used internally to translate L</aliases> |
489
|
|
|
|
|
|
|
into the actual files or URLs. |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
If files cannot be found, then it will throw an error. (See |
492
|
|
|
|
|
|
|
L</check>). |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=head2 cdn_links |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
This is a hash reference of L</aliases> to URLs. (Only one URL per |
497
|
|
|
|
|
|
|
alias is supported.) |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
When L</use_cdn_links> is true, then these URLs will be used instead |
500
|
|
|
|
|
|
|
of local versions. |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
=head2 has_cdn_links |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
This is true when there are L</cdn_links>. |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=head2 use_cdn_links |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
When true, this will prefer CDN URLs instead of local files. |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=head2 inline_max |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
This specifies the maximum size of an file to inline. |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
Local files under the size will be inlined using the |
515
|
|
|
|
|
|
|
L</link_or_inline_html> or L</deferred_link_html> methods. |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
Setting this to 0 disables the use of inline links, unless |
518
|
|
|
|
|
|
|
L</inline_html> is called explicitly. |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=head2 defer_css |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
True by default. |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
This is used by L</deferred_link_html> to determine whether to emit |
525
|
|
|
|
|
|
|
code for deferred stylesheets. |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
=head2 include_noscript |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
When true, a C<noscript> element will be included with non-deffered |
530
|
|
|
|
|
|
|
links. |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
This defaults to the same value as L</defer_css>. |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
=head2 preload_script |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
This is the pathname of the F<cssrelpreload.js> file that will be |
537
|
|
|
|
|
|
|
embedded in the resulting code. |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
The script comes from L<https://github.com/filamentgroup/loadCSS>. |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
You do not need to modify this unless you want to use a different |
542
|
|
|
|
|
|
|
script from the one included with this module. |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
=head2 link_template |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
This is a code reference for a subroutine that returns a stylesheet link. |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
=head2 preload_template |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
This is a code reference for a subroutine that returns a stylesheet |
551
|
|
|
|
|
|
|
preload link. |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
=head2 asset_id |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
This is an optional static asset id to append to local links. It may |
556
|
|
|
|
|
|
|
refer to a version number or commit-id, for example. |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
This is useful to ensure that changes to stylesheets are picked up by |
559
|
|
|
|
|
|
|
web browsers that would otherwise use cached copies of older versions |
560
|
|
|
|
|
|
|
of files. |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
=head2 has_asset_id |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
True if there is an L</asset_id>. |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
=head2 log |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
This is a code reference for logging errors and warnings: |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
$css->log->( $level => $message ); |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
By default, this is a wrapper around L<Carp> that dies when the level |
573
|
|
|
|
|
|
|
is "error", and emits a warning for everything else. |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
You can override this so that errors are treated as warnings, |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
log => sub { warn $_[1] }, |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
or that warnings are fatal, |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
log => sub { die $_[1] }, |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
or even integrate this with your own logging system: |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
log => sub { $logger->log(@_) }, |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
=head2 simple |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
When true, this enables a simpler method of using deferable CSS, |
590
|
|
|
|
|
|
|
without the need for the C<loadCSS> script. |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
It is false by default, for backwards compatability. But it is |
593
|
|
|
|
|
|
|
recommended that you set this to true. |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
See L<https://www.filamentgroup.com/lab/load-css-simpler/>. |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
=head1 METHODS |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
=head2 check |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
This method instantiates lazy attributes and performs some minimal |
602
|
|
|
|
|
|
|
checks on the data. (This should be called instead of L</css_files>.) |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
It will throw an error or return false (depending on L</log>) if there |
605
|
|
|
|
|
|
|
is something wrong. |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
This was added in v0.3.0. |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
=head2 href |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
my $href = $css->href( $alias ); |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
This returns this URL for an alias. |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
=head2 link_html |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
my $html = $css->link_html( $alias ); |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
This returns the link HTML markup for the stylesheet referred to by |
620
|
|
|
|
|
|
|
C<$alias>. |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=head2 inline_html |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
my $html = $css->inline_html( $alias ); |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
This returns an embedded stylesheet referred to by C<$alias>. |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=head2 link_or_inline_html |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
my $html = $css->link_or_inline_html( @aliases ); |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
This returns either the link HTML markup, or the embedded stylesheet, |
633
|
|
|
|
|
|
|
if the file size is not greater than L</inline_max>. |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
Note that a stylesheet will be inlined, even if there is are |
636
|
|
|
|
|
|
|
L</cdn_links>. |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
=head2 deferred_link_html |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
my $html = $css->deferred_link_html( @aliases ); |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
This returns the HTML markup for the stylesheets specified by |
643
|
|
|
|
|
|
|
L</aliases>, as appropriate for each stylesheet. |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
If the stylesheets are not greater than L</inline_max>, then it will |
646
|
|
|
|
|
|
|
embed them. Otherwise it will return the appropriate markup, |
647
|
|
|
|
|
|
|
depending on L</defer_css>. |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
=for Pod::Coverage PATH NAME SIZE |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
=head1 KNOWN ISSUES |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
=head2 Content-Security-Policy (CSP) |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
If a web site configures a |
656
|
|
|
|
|
|
|
L<Content-Security-Policy|https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP> |
657
|
|
|
|
|
|
|
setting to disable all inlined JavaScript, then the JavaScript shim will |
658
|
|
|
|
|
|
|
not work. |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=head2 XHTML Support |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
This module is written for HTML5. |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
It does not support XHTML self-closing elements or embedding styles |
665
|
|
|
|
|
|
|
and scripts in CDATA sections. |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
=head2 Encoding |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
All files are embedded as raw files. |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
No URL encoding is done on the HTML links or L</asset_id>. |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
=head2 It's spelled "Deferrable" |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
It's also spelled "Deferable". |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
=head1 SOURCE |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
The development version is on github at L<https://github.com/robrwo/HTML-DeferableCSS> |
680
|
|
|
|
|
|
|
and may be cloned from L<git://github.com/robrwo/HTML-DeferableCSS.git> |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
=head1 BUGS |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
Please report any bugs or feature requests on the bugtracker website |
685
|
|
|
|
|
|
|
L<https://github.com/robrwo/HTML-DeferableCSS/issues> |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
When submitting a bug or request, please include a test-file or a |
688
|
|
|
|
|
|
|
patch to an existing test-file that illustrates the bug or desired |
689
|
|
|
|
|
|
|
feature. |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
Please report any bugs in F<cssrelpreload.js> to |
692
|
|
|
|
|
|
|
L<https://github.com/filamentgroup/loadCSS/issues>. |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
=head1 AUTHOR |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
Robert Rothenberg <rrwo@cpan.org> |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
This module was developed from work for Science Photo Library |
699
|
|
|
|
|
|
|
L<https://www.sciencephoto.com>. |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
F<reset.css> comes from L<http://meyerweb.com/eric/tools/css/reset/>. |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
F<cssrelpreload.js> comes from L<https://github.com/filamentgroup/loadCSS/>. |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
This software is Copyright (c) 2020 by Robert Rothenberg. |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
This is free software, licensed under: |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
The MIT (X11) License |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
=cut |