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,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pathname"
4
-
5
- module Hanami
6
- module Assets
7
- # Store assets references when compile mode is on.
8
- #
9
- # This is especially useful in development mode, where we want to compile
10
- # only the assets that were changed from last browser refresh.
11
- #
12
- # @since 0.1.0
13
- # @api private
14
- class Cache
15
- # File cache entry
16
- #
17
- # @since 0.3.0
18
- # @api private
19
- class File
20
- # @since 0.3.0
21
- # @api private
22
- def initialize(file, mtime: nil, dependencies: nil)
23
- @file = file.is_a?(String) ? Pathname.new(file) : file
24
- @mtime = mtime || @file.mtime.utc.to_i
25
-
26
- @dependencies = (dependencies || []).map { |d| self.class.new(d) }
27
- end
28
-
29
- # @since 0.3.0
30
- # @api private
31
- def modified?(other)
32
- file = other.is_a?(self.class) ? other : self.class.new(other)
33
-
34
- if dependencies?
35
- modified_dependencies?(file) ||
36
- mtime <= file.mtime
37
- else
38
- mtime < file.mtime
39
- end
40
- end
41
-
42
- protected
43
-
44
- # @since 0.3.0
45
- # @api private
46
- attr_reader :mtime
47
-
48
- # @since 0.3.0
49
- # @api private
50
- attr_reader :dependencies
51
-
52
- # @since 0.3.0
53
- # @api private
54
- def modified_dependencies?(other)
55
- dependencies.all? { |dep| dep.mtime > other.mtime }
56
- end
57
-
58
- # @since 0.3.0
59
- # @api private
60
- def dependencies?
61
- dependencies.any?
62
- end
63
- end
64
-
65
- # Return a new instance
66
- #
67
- # @return [Hanami::Assets::Cache] a new instance
68
- def initialize
69
- @data = Hash.new { |h, k| h[k] = File.new(k, mtime: 0) }
70
- @mutex = Mutex.new
71
- end
72
-
73
- # Check if the given file was modified
74
- #
75
- # @param file [String,Pathname] the file path
76
- #
77
- # @return [TrueClass,FalseClass] the result of the check
78
- #
79
- # @since 0.3.0
80
- # @api private
81
- def modified?(file)
82
- @mutex.synchronize do
83
- @data[file.to_s].modified?(file)
84
- end
85
- end
86
-
87
- # Store the given file reference
88
- #
89
- # @param file [String,Pathname] the file path
90
- #
91
- # @return [TrueClass,FalseClass] the result of the check
92
- #
93
- # @since 0.1.0
94
- # @api private
95
- def store(file, dependencies = nil)
96
- @mutex.synchronize do
97
- @data[file.to_s] = File.new(file, dependencies: dependencies)
98
- end
99
- end
100
- end
101
- end
102
- end
@@ -1,287 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
- require "find"
5
- require "hanami/utils/class_attribute"
6
-
7
- module Hanami
8
- module Assets
9
- # Missing Asset error
10
- class MissingAsset < Error
11
- def initialize(name, sources)
12
- sources = sources.map(&:to_s).join(", ")
13
- super("Missing asset: `#{name}' (sources: #{sources})")
14
- end
15
- end
16
-
17
- # Unknown Asset Engine error
18
- class UnknownAssetEngine < Error
19
- def initialize(source)
20
- super("No asset engine registered for `#{::File.basename(source)}'")
21
- end
22
- end
23
-
24
- # Assets compiler
25
- #
26
- # It compiles assets that needs to be preprocessed (eg. Sass or ES6) into
27
- # the destination directory.
28
- #
29
- # Vanilla javascripts or stylesheets are just copied over.
30
- #
31
- # @since 0.1.0
32
- # @api private
33
- class Compiler
34
- # @since 0.1.0
35
- # @api private
36
- DEFAULT_PERMISSIONS = 0o644
37
-
38
- # @since 0.1.0
39
- # @api private
40
- COMPILE_PATTERN = "*.*.*" # Example hello.js.es6
41
-
42
- # @since 0.1.0
43
- # @api private
44
- EXTENSIONS = {".js" => true, ".css" => true, ".map" => true}.freeze
45
-
46
- include Utils::ClassAttribute
47
-
48
- # @since 0.3.0
49
- # @api private
50
- class_attribute :subclasses
51
- self.subclasses = Set.new
52
-
53
- # @since 0.3.0
54
- # @api private
55
- def self.inherited(subclass)
56
- super
57
- subclasses.add(subclass)
58
- end
59
-
60
- # Compile the given asset
61
- #
62
- # @param configuration [Hanami::Assets::Configuration] the application
63
- # configuration associated with the given asset
64
- #
65
- # @param name [String] the asset path
66
- #
67
- # @since 0.1.0
68
- # @api private
69
- def self.compile(configuration, name)
70
- return unless configuration.compile
71
-
72
- require "tilt"
73
- require "hanami/assets/cache"
74
- require "hanami/assets/compilers/sass"
75
- require "hanami/assets/compilers/less"
76
- fabricate(configuration, name).compile
77
- end
78
-
79
- # @since 0.3.0
80
- # @api private
81
- def self.fabricate(configuration, name)
82
- source = configuration.source(name)
83
- engine = (subclasses + [self]).find do |klass|
84
- klass.eligible?(source)
85
- end
86
-
87
- engine.new(configuration, name)
88
- end
89
-
90
- # @since 0.3.0
91
- # @api private
92
- def self.eligible?(_name)
93
- true
94
- end
95
-
96
- # Assets cache
97
- #
98
- # @since 0.1.0
99
- # @api private
100
- #
101
- # @see Hanami::Assets::Cache
102
- def self.cache
103
- @@cache ||= Assets::Cache.new # rubocop:disable Style/ClassVars
104
- end
105
-
106
- # Return a new instance
107
- #
108
- # @param configuration [Hanami::Assets::Configuration] the application
109
- # configuration associated with the given asset
110
- #
111
- # @param name [String] the asset path
112
- #
113
- # @return [Hanami::Assets::Compiler] a new instance
114
- #
115
- # @since 0.1.0
116
- # @api private
117
- def initialize(configuration, name)
118
- @configuration = configuration
119
- @name = Pathname.new(name)
120
- end
121
-
122
- # Compile the asset
123
- #
124
- # @raise [Hanami::Assets::MissingAsset] if the asset can't be found in
125
- # sources
126
- #
127
- # @since 0.1.0
128
- # @api private
129
- def compile
130
- raise MissingAsset.new(@name, @configuration.sources) unless exist?
131
- return unless modified?
132
-
133
- if compile?
134
- compile!
135
- else
136
- copy!
137
- end
138
-
139
- cache!
140
- end
141
-
142
- private
143
-
144
- # @since 0.1.0
145
- # @api private
146
- def source
147
- @source ||= @configuration.source(@name)
148
- end
149
-
150
- # @since 0.1.0
151
- # @api private
152
- def destination
153
- @destination ||= @configuration.destination_directory.join(destination_name)
154
- end
155
-
156
- # @since 1.3.0
157
- # @api private
158
- def relative_destination_name(name: @name, add_prefix: true)
159
- result = name.to_s
160
- base_dir = @configuration.base_directories.detect { |dir| result.start_with?(dir) }
161
- if base_dir
162
- prefix = @configuration.prefix
163
- base_dir = prefix.join(base_dir) if add_prefix
164
- result = name.relative_path_from(Pathname.new(base_dir))
165
- end
166
- result
167
- end
168
-
169
- # @since 1.3.0
170
- # @api private
171
- def absolute_destination_name
172
- result = ::File.basename(@name)
173
- @configuration.sources.each do |source|
174
- if @name.to_s.start_with?(source.to_s)
175
- result = @name.relative_path_from(source)
176
- break
177
- end
178
- end
179
- relative_destination_name(name: Pathname.new(result), add_prefix: false)
180
- end
181
-
182
- # @since 1.3.0
183
- # @api private
184
- def destination_name
185
- result = destination_path
186
-
187
- if compile?
188
- result.scan(/\A[[[:alnum:]][\-\_]]*\.[[\w]]*/).first || result
189
- else
190
- result
191
- end
192
- end
193
-
194
- # @since 1.3.1
195
- # @api private
196
- def destination_path
197
- if @configuration.nested
198
- @name.relative? ? relative_destination_name : absolute_destination_name
199
- else
200
- ::File.basename(@name)
201
- end.to_s
202
- end
203
-
204
- # @since 0.1.0
205
- # @api private
206
- def exist?
207
- !source.nil? &&
208
- source.exist?
209
- end
210
-
211
- # @since 0.3.0
212
- # @api private
213
- def modified?
214
- !destination.exist? ||
215
- cache.modified?(source)
216
- end
217
-
218
- # @since 0.1.0
219
- # @api private
220
- def compile?
221
- @compile ||= ::File.fnmatch(COMPILE_PATTERN, ::File.basename(source.to_s)) &&
222
- !EXTENSIONS[::File.extname(source.to_s)]
223
- end
224
-
225
- # @since 0.1.0
226
- # @api private
227
- def compile!
228
- write { renderer.render }
229
- rescue RuntimeError
230
- raise UnknownAssetEngine.new(source)
231
- end
232
-
233
- # @since 0.1.0
234
- # @api private
235
- def copy!
236
- write { source.binread }
237
- end
238
-
239
- # @since 0.1.0
240
- # @api private
241
- def cache!
242
- cache.store(source, dependencies)
243
- end
244
-
245
- # @since 0.1.0
246
- # @api private
247
- def write
248
- destination.dirname.mkpath
249
- destination.open(File::WRONLY | File::TRUNC | File::CREAT | File::BINARY, DEFAULT_PERMISSIONS) do |file|
250
- file.write(yield)
251
- end
252
- end
253
-
254
- # @since 0.1.0
255
- # @api private
256
- def cache
257
- self.class.cache
258
- end
259
-
260
- # @since 0.3.0
261
- # @api private
262
- def renderer
263
- Tilt.new(source)
264
- end
265
-
266
- # @since 0.3.0
267
- # @api private
268
- def dependencies
269
- nil
270
- end
271
-
272
- # @since 0.3.0
273
- # @api private
274
- def load_paths
275
- result = []
276
-
277
- @configuration.sources.each do |source|
278
- Find.find(source) do |path|
279
- result << path if File.directory?(path)
280
- end
281
- end
282
-
283
- result
284
- end
285
- end
286
- end
287
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hanami
4
- module Assets
5
- module Compilers
6
- # LESS compiler
7
- #
8
- # @since 0.3.0
9
- # @api private
10
- class Less < Compiler
11
- # @since 0.3.0
12
- # @api private
13
- EXTENSIONS = /\.(less)\z/.freeze
14
-
15
- # @since 0.3.0
16
- # @api private
17
- def self.eligible?(name)
18
- name.to_s =~ EXTENSIONS
19
- end
20
-
21
- private
22
-
23
- # @since 0.3.0
24
- # @api private
25
- def renderer
26
- Tilt.new(source, nil, paths: load_paths)
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hanami
4
- module Assets
5
- module Compilers
6
- # Sass/SCSS Compiler
7
- #
8
- # @since 0.3.0
9
- # @api private
10
- class Sass < Compiler
11
- # @since 0.3.0
12
- # @api private
13
- EXTENSIONS = /\.(sass|scss)\z/.freeze
14
-
15
- # @since 0.3.0
16
- # @api private
17
- def self.eligible?(name)
18
- name.to_s =~ EXTENSIONS
19
- end
20
-
21
- # @since 1.3.3
22
- # @api private
23
- def initialize(*)
24
- super
25
- require "sassc"
26
- end
27
-
28
- private
29
-
30
- # @since 0.3.0
31
- # @api private
32
- def renderer
33
- @renderer ||=
34
- ::SassC::Engine.new(
35
- source.read,
36
- syntax: target_syntax,
37
- load_paths: load_paths
38
- )
39
- end
40
-
41
- # @since 0.3.0
42
- # @api private
43
- def dependencies
44
- renderer.dependencies.map(&:filename)
45
- rescue source::NotRenderedError
46
- []
47
- end
48
-
49
- # @since 1.3.2
50
- # @api private
51
- def target_syntax
52
- if source.extname =~ /sass\z/.freeze
53
- :sass
54
- else
55
- :scss
56
- end
57
- end
58
- end
59
- end
60
- end
61
- end
@@ -1,119 +0,0 @@
1
- require 'hanami/utils/string'
2
- require 'hanami/utils/class'
3
-
4
- module Hanami
5
- module Assets
6
- module Compressors
7
- # Unknown compressor error
8
- #
9
- # It's raised when trying to load an unknown compressor.
10
- #
11
- # @since 0.1.0
12
- # @api private
13
- #
14
- # @see Hanami::Assets::Configuration#javascript_compressor
15
- # @see Hanami::Assets::Configuration#stylesheet_compressor
16
- # @see Hanami::Assets::Compressors::Abstract#for
17
- class UnknownCompressorError < Error
18
- # @since 0.1.0
19
- # @api private
20
- def initialize(type, engine_name)
21
- super("Unknown #{ type } compressor: :#{ engine_name }")
22
- end
23
- end
24
-
25
- # Abstract base class for compressors.
26
- #
27
- # Don't use this class directly, but please use subclasses instead.
28
- #
29
- # @since 0.1.0
30
- # @api private
31
- class Abstract
32
- # Compress the given asset
33
- #
34
- # @param filename [String, Pathname] the absolute path to the asset
35
- #
36
- # @return [String] the compressed asset
37
- #
38
- # @since 0.1.0
39
- # @api private
40
- def compress(filename)
41
- compressor.compress(
42
- read(filename)
43
- )
44
- end
45
-
46
- protected
47
- # @since 0.1.0
48
- # @api private
49
- attr_reader :compressor
50
-
51
- # Read the contents of given filename
52
- #
53
- # @param filename [String, Pathname] the absolute path to the asset
54
- #
55
- # @return [String] the contents of asset
56
- #
57
- # @since 0.1.0
58
- # @api private
59
- def read(filename)
60
- ::File.read(filename)
61
- end
62
-
63
- private
64
-
65
- # Factory for compressors.
66
- #
67
- # It loads a compressor for the given name.
68
- #
69
- # @abstract Please use this method from the subclasses
70
- #
71
- # @param engine_name [Symbol,String,NilClass,#compress] the name of the
72
- # engine to load or an instance of an engine
73
- #
74
- # @return [Hanami::Assets::Compressors::Abstract] returns a concrete
75
- # implementation of a compressor
76
- #
77
- # @raise [Hanami::Assets::Compressors::UnknownCompressorError] when the
78
- # given name refers to an unknown compressor engine
79
- #
80
- # @since 0.1.0
81
- # @api private
82
- def self.for(engine_name)
83
- case engine_name
84
- when Symbol, String
85
- load_engine(name, engine_name)
86
- when nil
87
- require 'hanami/assets/compressors/null_compressor'
88
- NullCompressor.new
89
- else
90
- engine_name
91
- end
92
- end
93
-
94
- # Load the compressor for the given type and engine name.
95
- #
96
- # @param type [String] asset type (eg. "Javascript" or "Stylesheet")
97
- # @param engine_name [Symbol,String] the name of the engine to load (eg. `:yui`)
98
- #
99
- # @return [Hanami::Assets::Compress::Abstract] returns a concrete
100
- # implementation of a compressor
101
- #
102
- # @since 0.1.0
103
- # @api private
104
- def self.load_engine(type, engine_name)
105
- type = Utils::String.demodulize(type)
106
-
107
- require "hanami/assets/compressors/#{ engine_name }_#{ Utils::String.underscore(type) }"
108
- Utils::Class.load!("#{ Utils::String.classify(engine_name) }#{ type }", Hanami::Assets::Compressors).new
109
- rescue LoadError
110
- raise UnknownCompressorError.new(type, engine_name)
111
- end
112
-
113
- class << self
114
- private :for, :load_engine
115
- end
116
- end
117
- end
118
- end
119
- end
@@ -1,36 +0,0 @@
1
- require 'hanami/assets/compressors/javascript'
2
- require_relative './jsmin'
3
-
4
- module Hanami
5
- module Assets
6
- module Compressors
7
- # Builtin compressor for stylesheet
8
- #
9
- # This is a port of jsmin
10
- # Copyright (c) 2002 Douglas Crockford (www.crockford.com)
11
- #
12
- # This Ruby port was implemented by Ryan Grove (@rgrove) as work for
13
- # <tt>jsmin</tt> gem.
14
- #
15
- # Copyright (c) 2008-2012 Ryan Grove
16
- #
17
- # @since 0.1.0
18
- # @api private
19
- #
20
- # @see https://github.com/sbecker/asset_packager
21
- class BuiltinJavascript < Javascript
22
- def initialize
23
- @compressor = JSMin
24
- end
25
-
26
- # @since 0.1.0
27
- # @api private
28
- def compress(filename)
29
- compressor.minify(
30
- read(filename)
31
- )
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,57 +0,0 @@
1
- require 'hanami/assets/compressors/stylesheet'
2
-
3
- module Hanami
4
- module Assets
5
- module Compressors
6
- # Builtin compressor for stylesheet
7
- #
8
- # This is a basic algorithm based on Scott Becker (@sbecker) work on
9
- # <tt>asset_packager</tt> gem.
10
- #
11
- # Copyright (c) 2006-2008 Scott Becker
12
- #
13
- # @since 0.1.0
14
- # @api private
15
- #
16
- # @see https://github.com/sbecker/asset_packager
17
- class BuiltinStylesheet < Stylesheet
18
- # @since 0.1.0
19
- # @api private
20
- SPACE_REPLACEMENT = " ".freeze
21
-
22
- # @since 0.1.0
23
- # @api private
24
- COMMENTS_REPLACEMENT = "".freeze
25
-
26
- # @since 0.1.0
27
- # @api private
28
- LINE_BREAKS_REPLACEMENT = "}\n".freeze
29
-
30
- # @since 0.1.0
31
- # @api private
32
- LAST_BREAK_REPLACEMENT = "".freeze
33
-
34
- # @since 0.1.0
35
- # @api private
36
- INSIDE_LEFT_BRACKET_REPLACEMENT = " {".freeze
37
-
38
- # @since 0.1.0
39
- # @api private
40
- INSIDE_RIGHT_BRACKET_REPLACEMENT = "}".freeze
41
-
42
- # @since 0.1.0
43
- # @api private
44
- def compress(filename)
45
- result = read(filename)
46
- result.gsub!(/\s+/, SPACE_REPLACEMENT) # collapse space
47
- result.gsub!(/\/\*(.*?)\*\/ /, COMMENTS_REPLACEMENT) # remove comments - caution, might want to remove this if using css hacks
48
- result.gsub!(/\} /, LINE_BREAKS_REPLACEMENT) # add line breaks
49
- result.gsub!(/\n$/, LAST_BREAK_REPLACEMENT) # remove last break
50
- result.gsub!(/ \{ /, INSIDE_LEFT_BRACKET_REPLACEMENT) # trim inside brackets
51
- result.gsub!(/; \}/, INSIDE_RIGHT_BRACKET_REPLACEMENT) # trim inside brackets
52
- result
53
- end
54
- end
55
- end
56
- end
57
- end