hanami-assets 1.3.5 → 2.1.0.beta2

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 +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