hanami-assets 1.3.5 → 2.1.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -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 +106 -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