sprockets 2.2.3 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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