sprockets 2.2.3 → 4.0.0

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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +68 -0
  3. data/README.md +482 -255
  4. data/bin/sprockets +20 -7
  5. data/lib/rake/sprocketstask.rb +28 -15
  6. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  7. data/lib/sprockets/asset.rb +142 -207
  8. data/lib/sprockets/autoload/babel.rb +8 -0
  9. data/lib/sprockets/autoload/closure.rb +8 -0
  10. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  11. data/lib/sprockets/autoload/eco.rb +8 -0
  12. data/lib/sprockets/autoload/ejs.rb +8 -0
  13. data/lib/sprockets/autoload/jsminc.rb +8 -0
  14. data/lib/sprockets/autoload/sass.rb +8 -0
  15. data/lib/sprockets/autoload/sassc.rb +8 -0
  16. data/lib/sprockets/autoload/uglifier.rb +8 -0
  17. data/lib/sprockets/autoload/yui.rb +8 -0
  18. data/lib/sprockets/autoload/zopfli.rb +7 -0
  19. data/lib/sprockets/autoload.rb +16 -0
  20. data/lib/sprockets/babel_processor.rb +66 -0
  21. data/lib/sprockets/base.rb +89 -249
  22. data/lib/sprockets/bower.rb +61 -0
  23. data/lib/sprockets/bundle.rb +105 -0
  24. data/lib/sprockets/cache/file_store.rb +190 -14
  25. data/lib/sprockets/cache/memory_store.rb +75 -0
  26. data/lib/sprockets/cache/null_store.rb +54 -0
  27. data/lib/sprockets/cache.rb +271 -0
  28. data/lib/sprockets/cached_environment.rb +64 -0
  29. data/lib/sprockets/closure_compressor.rb +48 -0
  30. data/lib/sprockets/coffee_script_processor.rb +39 -0
  31. data/lib/sprockets/compressing.rb +134 -0
  32. data/lib/sprockets/configuration.rb +79 -0
  33. data/lib/sprockets/context.rb +204 -135
  34. data/lib/sprockets/dependencies.rb +74 -0
  35. data/lib/sprockets/digest_utils.rb +200 -0
  36. data/lib/sprockets/directive_processor.rb +224 -216
  37. data/lib/sprockets/eco_processor.rb +33 -0
  38. data/lib/sprockets/ejs_processor.rb +32 -0
  39. data/lib/sprockets/encoding_utils.rb +262 -0
  40. data/lib/sprockets/environment.rb +23 -68
  41. data/lib/sprockets/erb_processor.rb +37 -0
  42. data/lib/sprockets/errors.rb +6 -13
  43. data/lib/sprockets/exporters/base.rb +72 -0
  44. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  45. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  46. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  47. data/lib/sprockets/exporting.rb +73 -0
  48. data/lib/sprockets/file_reader.rb +16 -0
  49. data/lib/sprockets/http_utils.rb +135 -0
  50. data/lib/sprockets/jsminc_compressor.rb +32 -0
  51. data/lib/sprockets/jst_processor.rb +36 -19
  52. data/lib/sprockets/loader.rb +343 -0
  53. data/lib/sprockets/manifest.rb +231 -96
  54. data/lib/sprockets/manifest_utils.rb +48 -0
  55. data/lib/sprockets/mime.rb +80 -32
  56. data/lib/sprockets/npm.rb +52 -0
  57. data/lib/sprockets/path_dependency_utils.rb +77 -0
  58. data/lib/sprockets/path_digest_utils.rb +48 -0
  59. data/lib/sprockets/path_utils.rb +367 -0
  60. data/lib/sprockets/paths.rb +82 -0
  61. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  62. data/lib/sprockets/processing.rb +140 -192
  63. data/lib/sprockets/processor_utils.rb +169 -0
  64. data/lib/sprockets/resolve.rb +295 -0
  65. data/lib/sprockets/sass_cache_store.rb +30 -0
  66. data/lib/sprockets/sass_compressor.rb +63 -0
  67. data/lib/sprockets/sass_functions.rb +3 -0
  68. data/lib/sprockets/sass_importer.rb +3 -0
  69. data/lib/sprockets/sass_processor.rb +313 -0
  70. data/lib/sprockets/sassc_compressor.rb +56 -0
  71. data/lib/sprockets/sassc_processor.rb +297 -0
  72. data/lib/sprockets/server.rb +138 -90
  73. data/lib/sprockets/source_map_processor.rb +66 -0
  74. data/lib/sprockets/source_map_utils.rb +483 -0
  75. data/lib/sprockets/transformers.rb +173 -0
  76. data/lib/sprockets/uglifier_compressor.rb +66 -0
  77. data/lib/sprockets/unloaded_asset.rb +139 -0
  78. data/lib/sprockets/uri_tar.rb +99 -0
  79. data/lib/sprockets/uri_utils.rb +191 -0
  80. data/lib/sprockets/utils/gzip.rb +99 -0
  81. data/lib/sprockets/utils.rb +186 -53
  82. data/lib/sprockets/version.rb +2 -1
  83. data/lib/sprockets/yui_compressor.rb +56 -0
  84. data/lib/sprockets.rb +217 -52
  85. metadata +250 -59
  86. data/LICENSE +0 -21
  87. data/lib/sprockets/asset_attributes.rb +0 -126
  88. data/lib/sprockets/bundled_asset.rb +0 -79
  89. data/lib/sprockets/caching.rb +0 -96
  90. data/lib/sprockets/charset_normalizer.rb +0 -41
  91. data/lib/sprockets/eco_template.rb +0 -38
  92. data/lib/sprockets/ejs_template.rb +0 -37
  93. data/lib/sprockets/engines.rb +0 -74
  94. data/lib/sprockets/index.rb +0 -99
  95. data/lib/sprockets/processed_asset.rb +0 -152
  96. data/lib/sprockets/processor.rb +0 -32
  97. data/lib/sprockets/safety_colons.rb +0 -28
  98. data/lib/sprockets/static_asset.rb +0 -57
  99. data/lib/sprockets/trail.rb +0 -90
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'sassc'
3
+
4
+ module Sprockets
5
+ module Autoload
6
+ SassC = ::SassC
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'uglifier'
3
+
4
+ module Sprockets
5
+ module Autoload
6
+ Uglifier = ::Uglifier
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'yui/compressor'
3
+
4
+ module Sprockets
5
+ module Autoload
6
+ YUI = ::YUI
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ require 'zopfli'
2
+
3
+ module Sprockets
4
+ module Autoload
5
+ Zopfli = ::Zopfli
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module Sprockets
3
+ module Autoload
4
+ autoload :Babel, 'sprockets/autoload/babel'
5
+ autoload :Closure, 'sprockets/autoload/closure'
6
+ autoload :CoffeeScript, 'sprockets/autoload/coffee_script'
7
+ autoload :Eco, 'sprockets/autoload/eco'
8
+ autoload :EJS, 'sprockets/autoload/ejs'
9
+ autoload :JSMinC, 'sprockets/autoload/jsminc'
10
+ autoload :Sass, 'sprockets/autoload/sass'
11
+ autoload :SassC, 'sprockets/autoload/sassc'
12
+ autoload :Uglifier, 'sprockets/autoload/uglifier'
13
+ autoload :YUI, 'sprockets/autoload/yui'
14
+ autoload :Zopfli, 'sprockets/autoload/zopfli'
15
+ end
16
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/path_utils'
4
+ require 'sprockets/source_map_utils'
5
+ require 'json'
6
+
7
+ module Sprockets
8
+ class BabelProcessor
9
+ VERSION = '1'
10
+
11
+ def self.instance
12
+ @instance ||= new
13
+ end
14
+
15
+ def self.call(input)
16
+ instance.call(input)
17
+ end
18
+
19
+ def self.cache_key
20
+ instance.cache_key
21
+ end
22
+
23
+ attr_reader :cache_key
24
+
25
+ def initialize(options = {})
26
+ @options = options.merge({
27
+ 'blacklist' => (options['blacklist'] || []) + ['useStrict'],
28
+ 'sourceMap' => true
29
+ }).freeze
30
+
31
+ @cache_key = [
32
+ self.class.name,
33
+ Autoload::Babel::Transpiler::VERSION,
34
+ Autoload::Babel::Source::VERSION,
35
+ VERSION,
36
+ @options
37
+ ].freeze
38
+ end
39
+
40
+ def call(input)
41
+ data = input[:data]
42
+
43
+ result = input[:cache].fetch(@cache_key + [input[:filename]] + [data]) do
44
+ opts = {
45
+ 'moduleRoot' => nil,
46
+ 'filename' => input[:filename],
47
+ 'filenameRelative' => PathUtils.split_subpath(input[:load_path], input[:filename]),
48
+ 'sourceFileName' => File.basename(input[:filename]),
49
+ 'sourceMapTarget' => input[:filename]
50
+ }.merge(@options)
51
+
52
+ if opts['moduleIds'] && opts['moduleRoot']
53
+ opts['moduleId'] ||= File.join(opts['moduleRoot'], input[:name])
54
+ elsif opts['moduleIds']
55
+ opts['moduleId'] ||= input[:name]
56
+ end
57
+ Autoload::Babel::Transpiler.transform(data, opts)
58
+ end
59
+
60
+ map = SourceMapUtils.format_source_map(result["map"], input)
61
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
62
+
63
+ { data: result['code'], map: map }
64
+ end
65
+ end
66
+ end
@@ -1,89 +1,41 @@
1
- require 'sprockets/asset_attributes'
2
- require 'sprockets/bundled_asset'
3
- require 'sprockets/caching'
4
- require 'sprockets/processed_asset'
5
- require 'sprockets/processing'
1
+ # frozen_string_literal: true
2
+ require 'sprockets/asset'
3
+ require 'sprockets/bower'
4
+ require 'sprockets/cache'
5
+ require 'sprockets/configuration'
6
+ require 'sprockets/digest_utils'
7
+ require 'sprockets/errors'
8
+ require 'sprockets/loader'
9
+ require 'sprockets/npm'
10
+ require 'sprockets/path_dependency_utils'
11
+ require 'sprockets/path_digest_utils'
12
+ require 'sprockets/path_utils'
13
+ require 'sprockets/resolve'
6
14
  require 'sprockets/server'
7
- require 'sprockets/static_asset'
8
- require 'sprockets/trail'
9
- require 'pathname'
15
+ require 'sprockets/source_map_utils'
16
+ require 'sprockets/uri_tar'
10
17
 
11
18
  module Sprockets
12
- # `Base` class for `Environment` and `Index`.
13
- class Base
14
- include Caching, Processing, Server, Trail
15
-
16
- # Returns a `Digest` implementation class.
17
- #
18
- # Defaults to `Digest::MD5`.
19
- attr_reader :digest_class
20
-
21
- # Assign a `Digest` implementation class. This maybe any Ruby
22
- # `Digest::` implementation such as `Digest::MD5` or
23
- # `Digest::SHA1`.
24
- #
25
- # environment.digest_class = Digest::SHA1
26
- #
27
- def digest_class=(klass)
28
- expire_index!
29
- @digest_class = klass
30
- end
31
-
32
- # The `Environment#version` is a custom value used for manually
33
- # expiring all asset caches.
34
- #
35
- # Sprockets is able to track most file and directory changes and
36
- # will take care of expiring the cache for you. However, its
37
- # impossible to know when any custom helpers change that you mix
38
- # into the `Context`.
39
- #
40
- # It would be wise to increment this value anytime you make a
41
- # configuration change to the `Environment` object.
42
- attr_reader :version
43
19
 
44
- # Assign an environment version.
45
- #
46
- # environment.version = '2.0'
47
- #
48
- def version=(version)
49
- expire_index!
50
- @version = version
51
- end
52
-
53
- # Returns a `Digest` instance for the `Environment`.
54
- #
55
- # This value serves two purposes. If two `Environment`s have the
56
- # same digest value they can be treated as equal. This is more
57
- # useful for comparing environment states between processes rather
58
- # than in the same. Two equal `Environment`s can share the same
59
- # cached assets.
60
- #
61
- # The value also provides a seed digest for all `Asset`
62
- # digests. Any change in the environment digest will affect all of
63
- # its assets.
64
- def digest
65
- # Compute the initial digest using the implementation class. The
66
- # Sprockets release version and custom environment version are
67
- # mixed in. So any new releases will affect all your assets.
68
- @digest ||= digest_class.new.update(VERSION).update(version.to_s)
69
-
70
- # Returned a dupped copy so the caller can safely mutate it with `.update`
71
- @digest.dup
20
+ class DoubleLinkError < Sprockets::Error
21
+ def initialize(parent_filename:, logical_path:, last_filename:, filename:)
22
+ super <<~MSG
23
+ Multiple files with the same output path cannot be linked (#{logical_path.inspect})
24
+ In #{parent_filename.inspect} these files were linked:
25
+ - #{last_filename}
26
+ - #{filename}
27
+ MSG
72
28
  end
29
+ end
73
30
 
74
- # Get and set `Logger` instance.
75
- attr_accessor :logger
76
-
77
- # Get `Context` class.
78
- #
79
- # This class maybe mutated and mixed in with custom helpers.
80
- #
81
- # environment.context_class.instance_eval do
82
- # include MyHelpers
83
- # def asset_url; end
84
- # end
85
- #
86
- attr_reader :context_class
31
+ # `Base` class for `Environment` and `CachedEnvironment`.
32
+ class Base
33
+ include PathUtils, PathDependencyUtils, PathDigestUtils, DigestUtils, SourceMapUtils
34
+ include Configuration
35
+ include Server
36
+ include Resolve, Loader
37
+ include Bower
38
+ include Npm
87
39
 
88
40
  # Get persistent cache store
89
41
  attr_reader :cache
@@ -94,79 +46,69 @@ module Sprockets
94
46
  # setters. Either `get(key)`/`set(key, value)`,
95
47
  # `[key]`/`[key]=value`, `read(key)`/`write(key, value)`.
96
48
  def cache=(cache)
97
- expire_index!
98
- @cache = cache
49
+ @cache = Cache.new(cache, logger)
99
50
  end
100
51
 
101
- # Return an `Index`. Must be implemented by the subclass.
102
- def index
52
+ # Return an `CachedEnvironment`. Must be implemented by the subclass.
53
+ def cached
103
54
  raise NotImplementedError
104
55
  end
56
+ alias_method :index, :cached
105
57
 
106
- if defined? Encoding.default_external
107
- # Define `default_external_encoding` accessor on 1.9.
108
- # Defaults to UTF-8.
109
- attr_accessor :default_external_encoding
110
- end
111
-
112
- # Works like `Dir.entries`.
58
+ # Internal: Compute digest for path.
113
59
  #
114
- # Subclasses may cache this method.
115
- def entries(pathname)
116
- trail.entries(pathname)
117
- end
118
-
119
- # Works like `File.stat`.
120
- #
121
- # Subclasses may cache this method.
122
- def stat(path)
123
- trail.stat(path)
124
- end
125
-
126
- # Read and compute digest of filename.
60
+ # path - String filename or directory path.
127
61
  #
128
- # Subclasses may cache this method.
62
+ # Returns a String digest or nil.
129
63
  def file_digest(path)
130
64
  if stat = self.stat(path)
131
- # If its a file, digest the contents
132
- if stat.file?
133
- digest.file(path.to_s)
134
-
135
- # If its a directive, digest the list of filenames
136
- elsif stat.directory?
137
- contents = self.entries(path).join(',')
138
- digest.update(contents)
65
+ # Caveat: Digests are cached by the path's current mtime. Its possible
66
+ # for a files contents to have changed and its mtime to have been
67
+ # negligently reset thus appearing as if the file hasn't changed on
68
+ # disk. Also, the mtime is only read to the nearest second. It's
69
+ # also possible the file was updated more than once in a given second.
70
+ key = UnloadedAsset.new(path, self).file_digest_key(stat.mtime.to_i)
71
+ cache.fetch(key) do
72
+ self.stat_digest(path, stat)
139
73
  end
140
74
  end
141
75
  end
142
76
 
143
- # Internal. Return a `AssetAttributes` for `path`.
144
- def attributes_for(path)
145
- AssetAttributes.new(self, path)
77
+ # Find asset by logical path or expanded path.
78
+ def find_asset(*args, **options)
79
+ uri, _ = resolve(*args, **options)
80
+ if uri
81
+ load(uri)
82
+ end
146
83
  end
147
84
 
148
- # Internal. Return content type of `path`.
149
- def content_type_of(path)
150
- attributes_for(path).content_type
151
- end
85
+ def find_all_linked_assets(*args)
86
+ return to_enum(__method__, *args) unless block_given?
152
87
 
153
- # Find asset by logical path or expanded path.
154
- def find_asset(path, options = {})
155
- logical_path = path
156
- pathname = Pathname.new(path)
88
+ parent_asset = asset = find_asset(*args)
89
+ return unless asset
90
+
91
+ yield asset
92
+ stack = asset.links.to_a
93
+ linked_paths = {}
94
+
95
+ while uri = stack.shift
96
+ yield asset = load(uri)
157
97
 
158
- if pathname.absolute?
159
- return unless stat(pathname)
160
- logical_path = attributes_for(pathname).logical_path
161
- else
162
- begin
163
- pathname = resolve(logical_path)
164
- rescue FileNotFound
165
- return nil
98
+ last_filename = linked_paths[asset.logical_path]
99
+ if last_filename && last_filename != asset.filename
100
+ raise DoubleLinkError.new(
101
+ parent_filename: parent_asset.filename,
102
+ last_filename: last_filename,
103
+ logical_path: asset.logical_path,
104
+ filename: asset.filename
105
+ )
166
106
  end
107
+ linked_paths[asset.logical_path] = asset.filename
108
+ stack = asset.links.to_a + stack
167
109
  end
168
110
 
169
- build_asset(logical_path, pathname, options)
111
+ nil
170
112
  end
171
113
 
172
114
  # Preferred `find_asset` shorthand.
@@ -177,131 +119,29 @@ module Sprockets
177
119
  find_asset(*args)
178
120
  end
179
121
 
180
- def each_entry(root, &block)
181
- return to_enum(__method__, root) unless block_given?
182
- root = Pathname.new(root) unless root.is_a?(Pathname)
183
-
184
- paths = []
185
- entries(root).sort.each do |filename|
186
- path = root.join(filename)
187
- paths << path
188
-
189
- if stat(path).directory?
190
- each_entry(path) do |subpath|
191
- paths << subpath
192
- end
193
- end
194
- end
195
-
196
- paths.sort_by(&:to_s).each(&block)
197
-
198
- nil
199
- end
200
-
201
- def each_file
202
- return to_enum(__method__) unless block_given?
203
- paths.each do |root|
204
- each_entry(root) do |path|
205
- if !stat(path).directory?
206
- yield path
207
- end
208
- end
209
- end
210
- nil
211
- end
212
-
213
- def each_logical_path(*args)
214
- return to_enum(__method__, *args) unless block_given?
215
- filters = args.flatten
216
- files = {}
217
- each_file do |filename|
218
- if logical_path = logical_path_for_filename(filename, filters)
219
- yield logical_path unless files[logical_path]
220
- files[logical_path] = true
221
- end
122
+ # Find asset by logical path or expanded path.
123
+ #
124
+ # If the asset is not found an error will be raised.
125
+ def find_asset!(*args)
126
+ uri, _ = resolve!(*args)
127
+ if uri
128
+ load(uri)
222
129
  end
223
- nil
224
130
  end
225
131
 
226
132
  # Pretty inspect
227
133
  def inspect
228
134
  "#<#{self.class}:0x#{object_id.to_s(16)} " +
229
135
  "root=#{root.to_s.inspect}, " +
230
- "paths=#{paths.inspect}, " +
231
- "digest=#{digest.to_s.inspect}" +
232
- ">"
136
+ "paths=#{paths.inspect}>"
233
137
  end
234
138
 
235
- protected
236
- # Clear index after mutating state. Must be implemented by the subclass.
237
- def expire_index!
238
- raise NotImplementedError
239
- end
240
-
241
- def build_asset(logical_path, pathname, options)
242
- pathname = Pathname.new(pathname)
243
-
244
- # If there are any processors to run on the pathname, use
245
- # `BundledAsset`. Otherwise use `StaticAsset` and treat is as binary.
246
- if attributes_for(pathname).processors.any?
247
- if options[:bundle] == false
248
- circular_call_protection(pathname.to_s) do
249
- ProcessedAsset.new(index, logical_path, pathname)
250
- end
251
- else
252
- BundledAsset.new(index, logical_path, pathname)
253
- end
254
- else
255
- StaticAsset.new(index, logical_path, pathname)
256
- end
257
- end
258
-
259
- def cache_key_for(path, options)
260
- "#{path}:#{options[:bundle] ? '1' : '0'}"
261
- end
262
-
263
- def circular_call_protection(path)
264
- reset = Thread.current[:sprockets_circular_calls].nil?
265
- calls = Thread.current[:sprockets_circular_calls] ||= Set.new
266
- if calls.include?(path)
267
- raise CircularDependencyError, "#{path} has already been required"
268
- end
269
- calls << path
270
- yield
271
- ensure
272
- Thread.current[:sprockets_circular_calls] = nil if reset
273
- end
274
-
275
- def logical_path_for_filename(filename, filters)
276
- logical_path = attributes_for(filename).logical_path.to_s
277
-
278
- if matches_filter(filters, logical_path)
279
- return logical_path
280
- end
281
-
282
- # If filename is an index file, retest with alias
283
- if File.basename(logical_path)[/[^\.]+/, 0] == 'index'
284
- path = logical_path.sub(/\/index\./, '.')
285
- if matches_filter(filters, path)
286
- return path
287
- end
288
- end
289
-
290
- nil
291
- end
292
-
293
- def matches_filter(filters, filename)
294
- return true if filters.empty?
139
+ def compress_from_root(uri)
140
+ URITar.new(uri, self).compress
141
+ end
295
142
 
296
- filters.any? do |filter|
297
- if filter.is_a?(Regexp)
298
- filter.match(filename)
299
- elsif filter.respond_to?(:call)
300
- filter.call(filename)
301
- else
302
- File.fnmatch(filter.to_s, filename)
303
- end
304
- end
305
- end
143
+ def expand_from_root(uri)
144
+ URITar.new(uri, self).expand
145
+ end
306
146
  end
307
147
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+
4
+ module Sprockets
5
+ module Bower
6
+ # Internal: All supported bower.json files.
7
+ #
8
+ # https://github.com/bower/json/blob/0.4.0/lib/json.js#L7
9
+ POSSIBLE_BOWER_JSONS = ['bower.json', 'component.json', '.bower.json']
10
+
11
+ # Internal: Override resolve_alternates to install bower.json behavior.
12
+ #
13
+ # load_path - String environment path
14
+ # logical_path - String path relative to base
15
+ #
16
+ # Returns candiate filenames.
17
+ def resolve_alternates(load_path, logical_path)
18
+ candidates, deps = super
19
+
20
+ # bower.json can only be nested one level deep
21
+ if !logical_path.index('/'.freeze)
22
+ dirname = File.join(load_path, logical_path)
23
+
24
+ if directory?(dirname)
25
+ filenames = POSSIBLE_BOWER_JSONS.map { |basename| File.join(dirname, basename) }
26
+ filename = filenames.detect { |fn| self.file?(fn) }
27
+
28
+ if filename
29
+ deps << build_file_digest_uri(filename)
30
+ read_bower_main(dirname, filename) do |path|
31
+ if file?(path)
32
+ candidates << path
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ return candidates, deps
40
+ end
41
+
42
+ # Internal: Read bower.json's main directive.
43
+ #
44
+ # dirname - String path to component directory.
45
+ # filename - String path to bower.json.
46
+ #
47
+ # Returns nothing.
48
+ def read_bower_main(dirname, filename)
49
+ bower = JSON.parse(File.read(filename), create_additions: false)
50
+
51
+ case bower['main']
52
+ when String
53
+ yield File.expand_path(bower['main'], dirname)
54
+ when Array
55
+ bower['main'].each do |name|
56
+ yield File.expand_path(name, dirname)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+ require 'sprockets/utils'
4
+ require 'sprockets/uri_utils'
5
+
6
+ module Sprockets
7
+ # Internal: Bundle processor takes a single file asset and prepends all the
8
+ # `:required` URIs to the contents.
9
+ #
10
+ # Uses pipeline metadata:
11
+ #
12
+ # :required - Ordered Set of asset URIs to prepend
13
+ # :stubbed - Set of asset URIs to substract from the required set.
14
+ #
15
+ # Also see DirectiveProcessor.
16
+ class Bundle
17
+ def self.call(input)
18
+ env = input[:environment]
19
+ type = input[:content_type]
20
+ input[:links] ||= Set.new
21
+ dependencies = Set.new(input[:metadata][:dependencies])
22
+
23
+ processed_uri, deps = env.resolve(input[:filename], accept: type, pipeline: :self)
24
+ dependencies.merge(deps)
25
+
26
+ # DirectiveProcessor (and any other transformers called here with pipeline=self)
27
+ primary_asset = env.load(processed_uri)
28
+ to_load = primary_asset.metadata.delete(:to_load) || Set.new
29
+ to_link = primary_asset.metadata.delete(:to_link) || Set.new
30
+
31
+ to_load.each do |uri|
32
+ loaded_asset = env.load(uri)
33
+ dependencies.merge(loaded_asset.metadata[:dependencies])
34
+ if to_link.include?(uri)
35
+ primary_metadata = primary_asset.metadata
36
+ input[:links] << loaded_asset.uri
37
+ primary_metadata[:links] << loaded_asset.uri
38
+ end
39
+ end
40
+
41
+ find_required = proc { |uri| env.load(uri).metadata[:required] }
42
+ required = Utils.dfs(processed_uri, &find_required)
43
+ stubbed = Utils.dfs(env.load(processed_uri).metadata[:stubbed], &find_required)
44
+ required.subtract(stubbed)
45
+ dedup(required)
46
+ assets = required.map { |uri| env.load(uri) }
47
+
48
+ (required + stubbed).each do |uri|
49
+ dependencies.merge(env.load(uri).metadata[:dependencies])
50
+ end
51
+
52
+ reducers = Hash[env.match_mime_type_keys(env.config[:bundle_reducers], type).flat_map(&:to_a)]
53
+ process_bundle_reducers(input, assets, reducers).merge(dependencies: dependencies, included: assets.map(&:uri))
54
+ end
55
+
56
+ # Internal: Removes uri from required if it's already included as an alias.
57
+ #
58
+ # required - Set of required uris
59
+ #
60
+ # Returns deduped set of uris
61
+ def self.dedup(required)
62
+ dupes = required.reduce([]) do |r, uri|
63
+ path, params = URIUtils.parse_asset_uri(uri)
64
+ if (params.delete(:index_alias))
65
+ r << URIUtils.build_asset_uri(path, params)
66
+ end
67
+ r
68
+ end
69
+ required.subtract(dupes)
70
+ end
71
+
72
+ # Internal: Run bundle reducers on set of Assets producing a reduced
73
+ # metadata Hash.
74
+ #
75
+ # filename - String bundle filename
76
+ # assets - Array of Assets
77
+ # reducers - Array of [initial, reducer_proc] pairs
78
+ #
79
+ # Returns reduced asset metadata Hash.
80
+ def self.process_bundle_reducers(input, assets, reducers)
81
+ initial = {}
82
+ reducers.each do |k, (v, _)|
83
+ if v.respond_to?(:call)
84
+ initial[k] = v.call(input)
85
+ elsif !v.nil?
86
+ initial[k] = v
87
+ end
88
+ end
89
+
90
+ assets.reduce(initial) do |h, asset|
91
+ reducers.each do |k, (_, block)|
92
+ value = k == :data ? asset.source : asset.metadata[k]
93
+ if h.key?(k)
94
+ if !value.nil?
95
+ h[k] = block.call(h[k], value)
96
+ end
97
+ else
98
+ h[k] = value
99
+ end
100
+ end
101
+ h
102
+ end
103
+ end
104
+ end
105
+ end