hanami-assets 1.3.5 → 2.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +92 -314
  4. data/hanami-assets.gemspec +26 -33
  5. data/lib/hanami/assets/asset.rb +83 -0
  6. data/lib/hanami/assets/base_url.rb +64 -0
  7. data/lib/hanami/assets/config.rb +98 -0
  8. data/lib/hanami/assets/errors.rb +46 -0
  9. data/lib/hanami/assets/version.rb +2 -2
  10. data/lib/hanami/assets.rb +61 -143
  11. data/lib/hanami-assets.rb +3 -0
  12. metadata +33 -115
  13. data/lib/hanami/assets/bundler/asset.rb +0 -100
  14. data/lib/hanami/assets/bundler/compressor.rb +0 -63
  15. data/lib/hanami/assets/bundler/manifest_entry.rb +0 -64
  16. data/lib/hanami/assets/bundler.rb +0 -154
  17. data/lib/hanami/assets/cache.rb +0 -102
  18. data/lib/hanami/assets/compiler.rb +0 -287
  19. data/lib/hanami/assets/compilers/less.rb +0 -31
  20. data/lib/hanami/assets/compilers/sass.rb +0 -61
  21. data/lib/hanami/assets/compressors/abstract.rb +0 -119
  22. data/lib/hanami/assets/compressors/builtin_javascript.rb +0 -36
  23. data/lib/hanami/assets/compressors/builtin_stylesheet.rb +0 -57
  24. data/lib/hanami/assets/compressors/closure_javascript.rb +0 -25
  25. data/lib/hanami/assets/compressors/javascript.rb +0 -77
  26. data/lib/hanami/assets/compressors/jsmin.rb +0 -284
  27. data/lib/hanami/assets/compressors/null_compressor.rb +0 -19
  28. data/lib/hanami/assets/compressors/sass_stylesheet.rb +0 -36
  29. data/lib/hanami/assets/compressors/stylesheet.rb +0 -77
  30. data/lib/hanami/assets/compressors/uglifier_javascript.rb +0 -25
  31. data/lib/hanami/assets/compressors/yui_javascript.rb +0 -25
  32. data/lib/hanami/assets/compressors/yui_stylesheet.rb +0 -25
  33. data/lib/hanami/assets/config/global_sources.rb +0 -52
  34. data/lib/hanami/assets/config/manifest.rb +0 -142
  35. data/lib/hanami/assets/config/sources.rb +0 -80
  36. data/lib/hanami/assets/configuration.rb +0 -657
  37. data/lib/hanami/assets/helpers.rb +0 -945
  38. data/lib/hanami/assets/precompiler.rb +0 -97
@@ -1,657 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pathname"
4
- require "json"
5
- require "hanami/utils/string"
6
- require "hanami/utils/class"
7
- require "hanami/utils/path_prefix"
8
- require "hanami/utils/basic_object"
9
- require "hanami/assets/config/manifest"
10
- require "hanami/assets/config/sources"
11
-
12
- module Hanami
13
- module Assets
14
- # Framework configuration
15
- #
16
- # @since 0.1.0
17
- class Configuration
18
- # @since 0.1.0
19
- # @api private
20
- DEFAULT_SCHEME = "http"
21
-
22
- # @since 0.1.0
23
- # @api private
24
- DEFAULT_HOST = "localhost"
25
-
26
- # @since 0.1.0
27
- # @api private
28
- DEFAULT_PORT = "2300"
29
-
30
- # @since 0.1.0
31
- # @api private
32
- DEFAULT_PUBLIC_DIRECTORY = "public"
33
-
34
- # @since 0.1.0
35
- # @api private
36
- DEFAULT_MANIFEST = "assets.json"
37
-
38
- # @since 0.1.0
39
- # @api private
40
- DEFAULT_PREFIX = "/assets"
41
-
42
- # @since 0.1.0
43
- # @api private
44
- URL_SEPARATOR = "/"
45
-
46
- # @since 0.1.0
47
- # @api private
48
- HTTP_SCHEME = "http"
49
-
50
- # @since 0.1.0
51
- # @api private
52
- HTTP_PORT = "80"
53
-
54
- # @since 0.1.0
55
- # @api private
56
- HTTPS_SCHEME = "https"
57
-
58
- # @since 0.1.0
59
- # @api private
60
- HTTPS_PORT = "443"
61
-
62
- # @since 0.3.0
63
- # @api private
64
- DEFAULT_SUBRESOURCE_INTEGRITY_ALGORITHM = :sha256
65
-
66
- # @since 0.3.0
67
- # @api private
68
- SUBRESOURCE_INTEGRITY_SEPARATOR = " "
69
-
70
- # Return a copy of the configuration of the framework instance associated
71
- # with the given class.
72
- #
73
- # When multiple instances of Hanami::Assets are used in the same
74
- # application, we want to make sure that a controller or an action will
75
- # receive the expected configuration.
76
- #
77
- # @param base [Class, Module] a controller or an action
78
- #
79
- # @return [Hanami::Assets::Configuration] the configuration associated
80
- # to the given class.
81
- #
82
- # @since 0.1.0
83
- # @api private
84
- def self.for(base)
85
- # TODO: this implementation is similar to Hanami::Controller::Configuration
86
- # consider to extract it into Hanami::Utils
87
- namespace = Utils::String.namespace(base)
88
- framework = Utils::Class.load("#{namespace}::Assets") || Utils::Class.load!("Hanami::Assets")
89
- framework.configuration
90
- end
91
-
92
- # @since 0.4.0
93
- # @api private
94
- attr_reader :public_manifest
95
-
96
- # Return a new instance
97
- #
98
- # @return [Hanami::Assets::Configuration] a new instance
99
- #
100
- # @since 0.1.0
101
- # @api private
102
- def initialize(&blk)
103
- reset!
104
- instance_eval(&blk) if block_given?
105
- end
106
-
107
- # Compile mode
108
- #
109
- # Determine if compile assets from sources to destination.
110
- # Usually this is turned off in production mode.
111
- #
112
- # @since 0.1.0
113
- def compile(value = nil)
114
- if value.nil?
115
- @compile
116
- else
117
- @compile = value
118
- end
119
- end
120
-
121
- # Fingerprint mode
122
- #
123
- # Determine if the helpers should generate the fingerprinted path for an asset.
124
- # Usually this is turned on in production mode.
125
- #
126
- # @since 0.1.0
127
- def fingerprint(value = nil)
128
- if value.nil?
129
- @fingerprint
130
- else
131
- @fingerprint = value
132
- end
133
- end
134
-
135
- # Support for nested path
136
- #
137
- # @since 1.3.1
138
- def nested(value = nil)
139
- if value.nil?
140
- @nested
141
- else
142
- @nested = !!value
143
- end
144
- end
145
-
146
- # Subresource integrity mode
147
- #
148
- # Determine if the helpers should generate the integrity attribute for an
149
- # asset. Usually this is turned on in production mode.
150
- #
151
- # @since 0.3.0
152
- def subresource_integrity(*values)
153
- if values.empty?
154
- @subresource_integrity
155
- elsif values.length == 1
156
- @subresource_integrity = values.first
157
- else
158
- @subresource_integrity = values
159
- end
160
- end
161
-
162
- # CDN mode
163
- #
164
- # Determine if the helpers should always generate absolute URL.
165
- # This is useful in production mode.
166
- #
167
- # @since 0.1.0
168
- def cdn(value = nil)
169
- if value.nil?
170
- @cdn
171
- else
172
- @cdn = !!value
173
- end
174
- end
175
-
176
- # JavaScript compressor
177
- #
178
- # Determine which compressor to use for JavaScript files during deploy.
179
- #
180
- # By default it's <tt>nil</tt>, that means it doesn't compress JavaScripts at deploy time.
181
- #
182
- # It accepts a <tt>Symbol</tt> or an object that respond to <tt>#compress(file)</tt>.
183
- #
184
- # The following symbols are accepted:
185
- #
186
- # * <tt>:builtin</tt> - Ruby based implementation of jsmin. It doesn't require any external gem.
187
- # * <tt>:yui</tt> - YUI Compressor, it depends on <tt>yui-compressor</tt> gem and it requires Java 1.4+
188
- # * <tt>:uglifier</tt> - UglifyJS, it depends on <tt>uglifier</tt> gem and it requires Node.js
189
- # * <tt>:closure</tt> - Google Closure Compiler, it depends on <tt>closure-compiler</tt> gem
190
- # and it requires Java
191
- #
192
- # @param value [Symbol,#compress] the compressor
193
- #
194
- # @since 0.1.0
195
- #
196
- # @see http://yui.github.io/yuicompressor
197
- # @see https://rubygems.org/gems/yui-compressor
198
- #
199
- # @see http://lisperator.net/uglifyjs
200
- # @see https://rubygems.org/gems/uglifier
201
- #
202
- # @see https://developers.google.com/closure/compiler
203
- # @see https://rubygems.org/gems/closure-compiler
204
- #
205
- # @example YUI Compressor
206
- # require 'hanami/assets'
207
- #
208
- # Hanami::Assets.configure do
209
- # # ...
210
- # javascript_compressor :yui
211
- # end.load!
212
- #
213
- # @example Custom Compressor
214
- # require 'hanami/assets'
215
- #
216
- # Hanami::Assets.configure do
217
- # # ...
218
- # javascript_compressor MyCustomJavascriptCompressor.new
219
- # end.load!
220
- def javascript_compressor(value = nil)
221
- if value.nil?
222
- @javascript_compressor
223
- else
224
- @javascript_compressor = value
225
- end
226
- end
227
-
228
- # Stylesheet compressor
229
- #
230
- # Determine which compressor to use for Stylesheet files during deploy.
231
- #
232
- # By default it's <tt>nil</tt>, that means it doesn't compress Stylesheets at deploy time.
233
- #
234
- # It accepts a <tt>Symbol</tt> or an object that respond to <tt>#compress(file)</tt>.
235
- #
236
- # The following symbols are accepted:
237
- #
238
- # * <tt>:builtin</tt> - Ruby based compressor. It doesn't require any external gem.
239
- # It's fast, but not an efficient compressor.
240
- # * <tt>:yui</tt> - YUI-Compressor, it depends on <tt>yui-compressor</tt> gem and requires Java 1.4+
241
- # * <tt>:sass</tt> - Sass, it depends on <tt>sassc</tt> gem
242
- #
243
- # @param value [Symbol,#compress] the compressor
244
- #
245
- # @since 0.1.0
246
- #
247
- # @see http://yui.github.io/yuicompressor
248
- # @see https://rubygems.org/gems/yui-compressor
249
- #
250
- # @see http://sass-lang.com
251
- # @see https://rubygems.org/gems/sassc
252
- #
253
- # @example YUI Compressor
254
- # require 'hanami/assets'
255
- #
256
- # Hanami::Assets.configure do
257
- # # ...
258
- # stylesheet_compressor :yui
259
- # end.load!
260
- #
261
- # @example Custom Compressor
262
- # require 'hanami/assets'
263
- #
264
- # Hanami::Assets.configure do
265
- # # ...
266
- # stylesheet_compressor MyCustomStylesheetCompressor.new
267
- # end.load!
268
- def stylesheet_compressor(value = nil)
269
- if value.nil?
270
- @stylesheet_compressor
271
- else
272
- @stylesheet_compressor = value
273
- end
274
- end
275
-
276
- # URL scheme for the application
277
- #
278
- # This is used to generate absolute URL from helpers.
279
- #
280
- # @since 0.1.0
281
- def scheme(value = nil)
282
- if value.nil?
283
- @scheme
284
- else
285
- @scheme = value
286
- end
287
- end
288
-
289
- # URL host for the application
290
- #
291
- # This is used to generate absolute URL from helpers.
292
- #
293
- # @since 0.1.0
294
- def host(value = nil)
295
- if value.nil?
296
- @host
297
- else
298
- @host = value
299
- end
300
- end
301
-
302
- # URL port for the application
303
- #
304
- # This is used to generate absolute URL from helpers.
305
- #
306
- # @since 0.1.0
307
- def port(value = nil)
308
- if value.nil?
309
- @port
310
- else
311
- @port = value.to_s
312
- end
313
- end
314
-
315
- # URL port for the application
316
- #
317
- # This is used to generate absolute or relative URL from helpers.
318
- #
319
- # @since 0.1.0
320
- def prefix(value = nil)
321
- if value.nil?
322
- @prefix
323
- else
324
- @prefix = Utils::PathPrefix.new(value)
325
- end
326
- end
327
-
328
- # Sources root
329
- #
330
- # @since 0.1.0
331
- def root(value = nil)
332
- if value.nil?
333
- @root
334
- else
335
- @root = Pathname.new(value).realpath
336
- sources.root = @root
337
- end
338
- end
339
-
340
- # Application public directory
341
- #
342
- # @since 0.1.0
343
- def public_directory(value = nil)
344
- if value.nil?
345
- @public_directory
346
- else
347
- @public_directory = Pathname.new(::File.expand_path(value))
348
- end
349
- end
350
-
351
- # Destination directory
352
- #
353
- # It's the combination of <tt>public_directory</tt> and <tt>prefix</tt>.
354
- #
355
- # @since 0.1.0
356
- # @api private
357
- def destination_directory
358
- @destination_directory ||= public_directory.join(*prefix.split(URL_SEPARATOR))
359
- end
360
-
361
- # Manifest path from public directory
362
- #
363
- # @since 0.1.0
364
- def manifest(value = nil)
365
- if value.nil?
366
- @manifest
367
- else
368
- @manifest = value.to_s
369
- end
370
- end
371
-
372
- # Absolute manifest path
373
- #
374
- # @since 0.1.0
375
- # @api private
376
- def manifest_path
377
- public_directory.join(manifest)
378
- end
379
-
380
- # Application's assets sources
381
- #
382
- # @since 0.1.0
383
- # @api private
384
- def sources
385
- @sources ||= Hanami::Assets::Config::Sources.new(root)
386
- end
387
-
388
- # Application's assets
389
- #
390
- # @since 0.1.0
391
- # @api private
392
- def files
393
- sources.files
394
- end
395
-
396
- # @since 0.3.0
397
- # @api private
398
- def source(file)
399
- pathname = Pathname.new(file)
400
- pathname.absolute? ? pathname : find(file)
401
- end
402
-
403
- # @since 1.3.0
404
- def base_directories
405
- @base_directories ||= %w[
406
- stylesheets
407
- javascripts
408
- images
409
- fonts
410
- ]
411
- end
412
-
413
- # Find a file from sources
414
- #
415
- # @since 0.1.0
416
- # @api private
417
- def find(file)
418
- @sources.find(file)
419
- end
420
-
421
- # Relative URL
422
- #
423
- # @since 0.1.0
424
- # @api private
425
- def asset_path(source)
426
- if cdn
427
- asset_url(source)
428
- else
429
- compile_path(source)
430
- end
431
- end
432
-
433
- # Absolute URL
434
- #
435
- # @since 0.1.0
436
- # @api private
437
- def asset_url(source)
438
- "#{@base_url}#{compile_path(source)}"
439
- end
440
-
441
- # Check if the given source is linked via Cross-Origin policy.
442
- # In other words, the given source, doesn't satisfy the Same-Origin policy.
443
- #
444
- # @see https://en.wikipedia.org/wiki/Same-origin_policy#Origin_determination_rules
445
- # @see https://en.wikipedia.org/wiki/Same-origin_policy#document.domain_property
446
- #
447
- # @since 1.2.0
448
- # @api private
449
- def crossorigin?(source)
450
- !source.start_with?(@base_url)
451
- end
452
-
453
- # An array of crypographically secure hashing algorithms to use for
454
- # generating asset subresource integrity checks
455
- #
456
- # @since 0.3.0
457
- def subresource_integrity_algorithms
458
- if @subresource_integrity == true
459
- [DEFAULT_SUBRESOURCE_INTEGRITY_ALGORITHM]
460
- else
461
- # Using Array() allows us to accept Array or Symbol, and '|| nil' lets
462
- # us return an empty array when @subresource_integrity is `false`
463
- Array(@subresource_integrity || nil)
464
- end
465
- end
466
-
467
- # Subresource integrity attribute
468
- #
469
- # @since 0.3.0
470
- # @api private
471
- def subresource_integrity_value(source)
472
- return unless subresource_integrity
473
-
474
- public_manifest.subresource_integrity_values(
475
- prefix.join(source)
476
- ).join(SUBRESOURCE_INTEGRITY_SEPARATOR)
477
- end
478
-
479
- # Load Javascript compressor
480
- #
481
- # @return [Hanami::Assets::Compressors::Javascript] a compressor
482
- #
483
- # @raise [Hanami::Assets::Compressors::UnknownCompressorError] when the
484
- # given name refers to an unknown compressor engine
485
- #
486
- # @since 0.1.0
487
- # @api private
488
- #
489
- # @see Hanami::Assets::Configuration#javascript_compressor
490
- # @see Hanami::Assets::Compressors::Javascript#for
491
- def js_compressor
492
- require "hanami/assets/compressors/javascript"
493
- Hanami::Assets::Compressors::Javascript.for(javascript_compressor)
494
- end
495
-
496
- # Load Stylesheet compressor
497
- #
498
- # @return [Hanami::Assets::Compressors::Stylesheet] a compressor
499
- #
500
- # @raise [Hanami::Assets::Compressors::UnknownCompressorError] when the
501
- # given name refers to an unknown compressor engine
502
- #
503
- # @since 0.1.0
504
- # @api private
505
- #
506
- # @see Hanami::Assets::Configuration#stylesheet_compressor
507
- # @see Hanami::Assets::Compressors::Stylesheet#for
508
- def css_compressor
509
- require "hanami/assets/compressors/stylesheet"
510
- Hanami::Assets::Compressors::Stylesheet.for(stylesheet_compressor)
511
- end
512
-
513
- # @since 0.1.0
514
- # @api private
515
- def duplicate
516
- Configuration.new.tap do |c|
517
- c.root = root
518
- c.scheme = scheme
519
- c.host = host
520
- c.port = port
521
- c.prefix = prefix
522
- c.subresource_integrity = subresource_integrity
523
- c.cdn = cdn
524
- c.compile = compile
525
- c.nested = nested
526
- c.public_directory = public_directory
527
- c.manifest = manifest
528
- c.sources = sources.dup
529
- c.javascript_compressor = javascript_compressor
530
- c.stylesheet_compressor = stylesheet_compressor
531
- end
532
- end
533
-
534
- # @since 0.1.0
535
- # @api private
536
- def reset!
537
- @scheme = DEFAULT_SCHEME
538
- @host = DEFAULT_HOST
539
- @port = DEFAULT_PORT
540
-
541
- @prefix = Utils::PathPrefix.new(DEFAULT_PREFIX)
542
- @subresource_integrity = false
543
- @cdn = false
544
- @fingerprint = false
545
- @compile = false
546
- @nested = false
547
- @base_url = nil
548
- @destination_directory = nil
549
- @public_manifest = Config::NullManifest.new(self)
550
-
551
- @javascript_compressor = nil
552
- @stylesheet_compressor = nil
553
-
554
- root Dir.pwd
555
- public_directory root.join(DEFAULT_PUBLIC_DIRECTORY)
556
- manifest DEFAULT_MANIFEST
557
- end
558
-
559
- # Load the configuration
560
- #
561
- # This MUST be executed before to accept the first HTTP request
562
- #
563
- # @since 0.1.0
564
- # @api private
565
- def load!
566
- if (fingerprint || subresource_integrity) && manifest_path.exist?
567
- @public_manifest = Config::Manifest.new(
568
- JSON.parse(manifest_path.read),
569
- manifest_path
570
- )
571
- end
572
-
573
- @base_url = URI::Generic.build(scheme: scheme, host: host, port: url_port).to_s
574
- end
575
-
576
- protected
577
-
578
- # @since 0.3.0
579
- # @api private
580
- attr_writer :subresource_integrity
581
-
582
- # @since 0.1.0
583
- # @api private
584
- attr_writer :cdn
585
-
586
- # @since 0.1.0
587
- # @api private
588
- attr_writer :compile
589
-
590
- # @since 1.3.1
591
- # @api private
592
- attr_writer :nested
593
-
594
- # @since 0.1.0
595
- # @api private
596
- attr_writer :scheme
597
-
598
- # @since 0.1.0
599
- # @api private
600
- attr_writer :host
601
-
602
- # @since 0.1.0
603
- # @api private
604
- attr_writer :port
605
-
606
- # @since 0.1.0
607
- # @api private
608
- attr_writer :prefix
609
-
610
- # @since 0.1.0
611
- # @api private
612
- attr_writer :root
613
-
614
- # @since 0.1.0
615
- # @api private
616
- attr_writer :public_directory
617
-
618
- # @since 0.1.0
619
- # @api private
620
- attr_writer :manifest
621
-
622
- # @since 0.1.0
623
- # @api private
624
- attr_writer :sources
625
-
626
- # @since 0.1.0
627
- # @api private
628
- attr_writer :javascript_compressor
629
-
630
- # @since 0.1.0
631
- # @api private
632
- attr_writer :stylesheet_compressor
633
-
634
- private
635
-
636
- # @since 0.1.0
637
- # @api private
638
- def compile_path(source)
639
- result = prefix.join(source)
640
- result = public_manifest.target(result) if fingerprint
641
- result.to_s
642
- end
643
-
644
- # @since 0.1.0
645
- # @api private
646
- #
647
- # rubocop:disable Style/MultilineTernaryOperator
648
- # rubocop:disable Style/TernaryParentheses
649
- def url_port
650
- ((scheme == HTTP_SCHEME && port == HTTP_PORT) ||
651
- (scheme == HTTPS_SCHEME && port == HTTPS_PORT)) ? nil : port.to_i
652
- end
653
- # rubocop:enable Style/TernaryParentheses
654
- # rubocop:enable Style/MultilineTernaryOperator
655
- end
656
- end
657
- end