sprockets 3.0.0 → 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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +68 -0
- data/README.md +397 -408
- data/bin/sprockets +12 -7
- data/lib/rake/sprocketstask.rb +3 -2
- data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
- data/lib/sprockets/asset.rb +19 -23
- data/lib/sprockets/autoload/babel.rb +8 -0
- data/lib/sprockets/autoload/closure.rb +1 -0
- data/lib/sprockets/autoload/coffee_script.rb +1 -0
- data/lib/sprockets/autoload/eco.rb +1 -0
- data/lib/sprockets/autoload/ejs.rb +1 -0
- data/lib/sprockets/autoload/jsminc.rb +8 -0
- data/lib/sprockets/autoload/sass.rb +1 -0
- data/lib/sprockets/autoload/sassc.rb +8 -0
- data/lib/sprockets/autoload/uglifier.rb +1 -0
- data/lib/sprockets/autoload/yui.rb +1 -0
- data/lib/sprockets/autoload/zopfli.rb +7 -0
- data/lib/sprockets/autoload.rb +5 -0
- data/lib/sprockets/babel_processor.rb +66 -0
- data/lib/sprockets/base.rb +59 -11
- data/lib/sprockets/bower.rb +5 -2
- data/lib/sprockets/bundle.rb +44 -4
- data/lib/sprockets/cache/file_store.rb +32 -7
- data/lib/sprockets/cache/memory_store.rb +9 -0
- data/lib/sprockets/cache/null_store.rb +8 -0
- data/lib/sprockets/cache.rb +42 -5
- data/lib/sprockets/cached_environment.rb +14 -19
- data/lib/sprockets/closure_compressor.rb +6 -11
- data/lib/sprockets/coffee_script_processor.rb +19 -5
- data/lib/sprockets/compressing.rb +62 -2
- data/lib/sprockets/configuration.rb +3 -7
- data/lib/sprockets/context.rb +98 -23
- data/lib/sprockets/dependencies.rb +9 -8
- data/lib/sprockets/digest_utils.rb +104 -60
- data/lib/sprockets/directive_processor.rb +45 -35
- data/lib/sprockets/eco_processor.rb +3 -2
- data/lib/sprockets/ejs_processor.rb +3 -2
- data/lib/sprockets/encoding_utils.rb +8 -4
- data/lib/sprockets/environment.rb +9 -4
- data/lib/sprockets/erb_processor.rb +28 -21
- data/lib/sprockets/errors.rb +1 -1
- data/lib/sprockets/exporters/base.rb +72 -0
- data/lib/sprockets/exporters/file_exporter.rb +24 -0
- data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
- data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
- data/lib/sprockets/exporting.rb +73 -0
- data/lib/sprockets/file_reader.rb +1 -0
- data/lib/sprockets/http_utils.rb +26 -6
- data/lib/sprockets/jsminc_compressor.rb +32 -0
- data/lib/sprockets/jst_processor.rb +11 -10
- data/lib/sprockets/loader.rb +236 -69
- data/lib/sprockets/manifest.rb +97 -44
- data/lib/sprockets/manifest_utils.rb +9 -6
- data/lib/sprockets/mime.rb +8 -42
- data/lib/sprockets/npm.rb +52 -0
- data/lib/sprockets/path_dependency_utils.rb +3 -11
- data/lib/sprockets/path_digest_utils.rb +2 -1
- data/lib/sprockets/path_utils.rb +106 -21
- data/lib/sprockets/paths.rb +1 -0
- data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
- data/lib/sprockets/processing.rb +31 -51
- data/lib/sprockets/processor_utils.rb +81 -15
- data/lib/sprockets/resolve.rb +182 -95
- data/lib/sprockets/sass_cache_store.rb +1 -0
- data/lib/sprockets/sass_compressor.rb +21 -17
- data/lib/sprockets/sass_functions.rb +1 -0
- data/lib/sprockets/sass_importer.rb +1 -0
- data/lib/sprockets/sass_processor.rb +45 -17
- data/lib/sprockets/sassc_compressor.rb +56 -0
- data/lib/sprockets/sassc_processor.rb +297 -0
- data/lib/sprockets/server.rb +57 -34
- data/lib/sprockets/source_map_processor.rb +66 -0
- data/lib/sprockets/source_map_utils.rb +483 -0
- data/lib/sprockets/transformers.rb +63 -35
- data/lib/sprockets/uglifier_compressor.rb +23 -20
- data/lib/sprockets/unloaded_asset.rb +139 -0
- data/lib/sprockets/uri_tar.rb +99 -0
- data/lib/sprockets/uri_utils.rb +15 -14
- data/lib/sprockets/utils/gzip.rb +99 -0
- data/lib/sprockets/utils.rb +43 -59
- data/lib/sprockets/version.rb +2 -1
- data/lib/sprockets/yui_compressor.rb +5 -14
- data/lib/sprockets.rb +103 -33
- metadata +151 -22
- data/LICENSE +0 -21
- data/lib/sprockets/coffee_script_template.rb +0 -6
- data/lib/sprockets/eco_template.rb +0 -6
- data/lib/sprockets/ejs_template.rb +0 -6
- data/lib/sprockets/engines.rb +0 -81
- data/lib/sprockets/erb_template.rb +0 -6
- data/lib/sprockets/legacy.rb +0 -314
- data/lib/sprockets/legacy_proc_processor.rb +0 -35
- data/lib/sprockets/legacy_tilt_processor.rb +0 -29
- data/lib/sprockets/sass_template.rb +0 -7
data/lib/sprockets/loader.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'sprockets/asset'
|
2
3
|
require 'sprockets/digest_utils'
|
3
|
-
require 'sprockets/engines'
|
4
4
|
require 'sprockets/errors'
|
5
5
|
require 'sprockets/file_reader'
|
6
6
|
require 'sprockets/mime'
|
@@ -10,82 +10,161 @@ require 'sprockets/processor_utils'
|
|
10
10
|
require 'sprockets/resolve'
|
11
11
|
require 'sprockets/transformers'
|
12
12
|
require 'sprockets/uri_utils'
|
13
|
+
require 'sprockets/unloaded_asset'
|
13
14
|
|
14
15
|
module Sprockets
|
16
|
+
|
15
17
|
# The loader phase takes a asset URI location and returns a constructed Asset
|
16
18
|
# object.
|
17
19
|
module Loader
|
18
20
|
include DigestUtils, PathUtils, ProcessorUtils, URIUtils
|
19
|
-
include
|
21
|
+
include Mime, Processing, Resolve, Transformers
|
22
|
+
|
20
23
|
|
21
|
-
# Public: Load Asset by
|
24
|
+
# Public: Load Asset by Asset URI.
|
22
25
|
#
|
23
|
-
# uri -
|
26
|
+
# uri - A String containing complete URI to a file including schema
|
27
|
+
# and full path such as:
|
28
|
+
# "file:///Path/app/assets/js/app.js?type=application/javascript"
|
24
29
|
#
|
25
30
|
# Returns Asset.
|
26
31
|
def load(uri)
|
27
|
-
|
28
|
-
if params.key?(:id)
|
29
|
-
asset =
|
30
|
-
|
32
|
+
unloaded = UnloadedAsset.new(uri, self)
|
33
|
+
if unloaded.params.key?(:id)
|
34
|
+
unless asset = asset_from_cache(unloaded.asset_key)
|
35
|
+
id = unloaded.params.delete(:id)
|
36
|
+
uri_without_id = build_asset_uri(unloaded.filename, unloaded.params)
|
37
|
+
asset = load_from_unloaded(UnloadedAsset.new(uri_without_id, self))
|
38
|
+
if asset[:id] != id
|
39
|
+
@logger.warn "Sprockets load error: Tried to find #{uri}, but latest was id #{asset[:id]}"
|
40
|
+
end
|
31
41
|
end
|
32
42
|
else
|
33
|
-
asset = fetch_asset_from_dependency_cache(
|
43
|
+
asset = fetch_asset_from_dependency_cache(unloaded) do |paths|
|
44
|
+
# When asset is previously generated, its "dependencies" are stored in the cache.
|
45
|
+
# The presence of `paths` indicates dependencies were stored.
|
46
|
+
# We can check to see if the dependencies have not changed by "resolving" them and
|
47
|
+
# generating a digest key from the resolved entries. If this digest key has not
|
48
|
+
# changed, the asset will be pulled from cache.
|
49
|
+
#
|
50
|
+
# If this `paths` is present but the cache returns nothing then `fetch_asset_from_dependency_cache`
|
51
|
+
# will confusingly be called again with `paths` set to nil where the asset will be
|
52
|
+
# loaded from disk.
|
34
53
|
if paths
|
35
|
-
digest = digest(resolve_dependencies(paths))
|
36
|
-
if
|
37
|
-
|
54
|
+
digest = DigestUtils.digest(resolve_dependencies(paths))
|
55
|
+
if uri_from_cache = cache.get(unloaded.digest_key(digest), true)
|
56
|
+
asset_from_cache(UnloadedAsset.new(uri_from_cache, self).asset_key)
|
38
57
|
end
|
39
58
|
else
|
40
|
-
|
59
|
+
load_from_unloaded(unloaded)
|
41
60
|
end
|
42
61
|
end
|
43
62
|
end
|
44
|
-
Asset.new(
|
63
|
+
Asset.new(asset)
|
45
64
|
end
|
46
65
|
|
47
66
|
private
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
67
|
+
def compress_key_from_hash(hash, key)
|
68
|
+
return unless hash.key?(key)
|
69
|
+
value = hash[key].dup
|
70
|
+
return if !value
|
71
|
+
|
72
|
+
if block_given?
|
73
|
+
value.map! do |x|
|
74
|
+
if yield x
|
75
|
+
compress_from_root(x)
|
76
|
+
else
|
77
|
+
x
|
78
|
+
end
|
79
|
+
end
|
80
|
+
else
|
81
|
+
value.map! { |x| compress_from_root(x) }
|
52
82
|
end
|
83
|
+
hash[key] = value
|
84
|
+
end
|
53
85
|
|
54
|
-
uri = build_asset_uri(filename, params)
|
55
|
-
asset = load_asset_by_uri(uri, filename, params)
|
56
86
|
|
57
|
-
|
58
|
-
|
87
|
+
def expand_key_from_hash(hash, key)
|
88
|
+
return unless hash.key?(key)
|
89
|
+
value = hash[key].dup
|
90
|
+
return if !value
|
91
|
+
if block_given?
|
92
|
+
value.map! do |x|
|
93
|
+
if yield x
|
94
|
+
expand_from_root(x)
|
95
|
+
else
|
96
|
+
x
|
97
|
+
end
|
98
|
+
end
|
99
|
+
else
|
100
|
+
value.map! { |x| expand_from_root(x) }
|
59
101
|
end
|
102
|
+
hash[key] = value
|
103
|
+
end
|
104
|
+
|
105
|
+
# Internal: Load asset hash from cache
|
106
|
+
#
|
107
|
+
# key - A String containing lookup information for an asset
|
108
|
+
#
|
109
|
+
# This method converts all "compressed" paths to absolute paths.
|
110
|
+
# Returns a hash of values representing an asset
|
111
|
+
def asset_from_cache(key)
|
112
|
+
asset = cache.get(key, true)
|
113
|
+
if asset
|
114
|
+
asset[:uri] = expand_from_root(asset[:uri])
|
115
|
+
asset[:load_path] = expand_from_root(asset[:load_path])
|
116
|
+
asset[:filename] = expand_from_root(asset[:filename])
|
117
|
+
expand_key_from_hash(asset[:metadata], :included)
|
118
|
+
expand_key_from_hash(asset[:metadata], :links)
|
119
|
+
expand_key_from_hash(asset[:metadata], :stubbed)
|
120
|
+
expand_key_from_hash(asset[:metadata], :required)
|
121
|
+
expand_key_from_hash(asset[:metadata], :to_load)
|
122
|
+
expand_key_from_hash(asset[:metadata], :to_link)
|
123
|
+
expand_key_from_hash(asset[:metadata], :dependencies) { |uri| uri.start_with?("file-digest://") }
|
60
124
|
|
125
|
+
asset[:metadata].each_key do |k|
|
126
|
+
next unless k.match?(/_dependencies\z/) # rubocop:disable Performance/EndWith
|
127
|
+
expand_key_from_hash(asset[:metadata], k)
|
128
|
+
end
|
129
|
+
end
|
61
130
|
asset
|
62
131
|
end
|
63
132
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
133
|
+
# Internal: Loads an asset and saves it to cache
|
134
|
+
#
|
135
|
+
# unloaded - An UnloadedAsset
|
136
|
+
#
|
137
|
+
# This method is only called when the given unloaded asset could not be
|
138
|
+
# successfully pulled from cache.
|
139
|
+
def load_from_unloaded(unloaded)
|
140
|
+
unless file?(unloaded.filename)
|
141
|
+
raise FileNotFound, "could not find file: #{unloaded.filename}"
|
68
142
|
end
|
69
143
|
|
70
|
-
|
71
|
-
|
72
|
-
|
144
|
+
path_to_split =
|
145
|
+
if index_alias = unloaded.params[:index_alias]
|
146
|
+
expand_from_root index_alias
|
147
|
+
else
|
148
|
+
unloaded.filename
|
149
|
+
end
|
73
150
|
|
74
|
-
load_path, logical_path = paths_split(config[:paths],
|
151
|
+
load_path, logical_path = paths_split(config[:paths], path_to_split)
|
75
152
|
|
76
153
|
unless load_path
|
77
|
-
|
154
|
+
target = path_to_split
|
155
|
+
target += " (index alias of #{unloaded.filename})" if unloaded.params[:index_alias]
|
156
|
+
raise FileOutsidePaths, "#{target} is no longer under a load path: #{self.paths.join(', ')}"
|
78
157
|
end
|
79
158
|
|
80
|
-
|
81
|
-
logical_path =
|
159
|
+
extname, file_type = match_path_extname(logical_path, mime_exts)
|
160
|
+
logical_path = logical_path.chomp(extname)
|
82
161
|
name = logical_path
|
83
162
|
|
84
|
-
if pipeline = params[:pipeline]
|
163
|
+
if pipeline = unloaded.params[:pipeline]
|
85
164
|
logical_path += ".#{pipeline}"
|
86
165
|
end
|
87
166
|
|
88
|
-
if type = params[:type]
|
167
|
+
if type = unloaded.params[:type]
|
89
168
|
logical_path += config[:mime_types][type][:extensions].first
|
90
169
|
end
|
91
170
|
|
@@ -93,9 +172,9 @@ module Sprockets
|
|
93
172
|
raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
|
94
173
|
end
|
95
174
|
|
96
|
-
processors = processors_for(type, file_type,
|
175
|
+
processors = processors_for(type, file_type, pipeline)
|
97
176
|
|
98
|
-
processors_dep_uri = build_processors_uri(type, file_type,
|
177
|
+
processors_dep_uri = build_processors_uri(type, file_type, pipeline)
|
99
178
|
dependencies = config[:dependencies] + [processors_dep_uri]
|
100
179
|
|
101
180
|
# Read into memory and process if theres a processor pipeline
|
@@ -103,72 +182,160 @@ module Sprockets
|
|
103
182
|
result = call_processors(processors, {
|
104
183
|
environment: self,
|
105
184
|
cache: self.cache,
|
106
|
-
uri: uri,
|
107
|
-
filename: filename,
|
185
|
+
uri: unloaded.uri,
|
186
|
+
filename: unloaded.filename,
|
108
187
|
load_path: load_path,
|
109
188
|
name: name,
|
110
189
|
content_type: type,
|
111
|
-
metadata: {
|
190
|
+
metadata: {
|
191
|
+
dependencies: dependencies
|
192
|
+
}
|
112
193
|
})
|
194
|
+
validate_processor_result!(result)
|
113
195
|
source = result.delete(:data)
|
114
|
-
metadata = result
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
)
|
196
|
+
metadata = result
|
197
|
+
metadata[:charset] = source.encoding.name.downcase unless metadata.key?(:charset)
|
198
|
+
metadata[:digest] = digest(self.version + source)
|
199
|
+
metadata[:length] = source.bytesize
|
119
200
|
else
|
201
|
+
dependencies << build_file_digest_uri(unloaded.filename)
|
120
202
|
metadata = {
|
121
|
-
digest: file_digest(filename),
|
122
|
-
length: self.stat(filename).size,
|
203
|
+
digest: file_digest(unloaded.filename),
|
204
|
+
length: self.stat(unloaded.filename).size,
|
123
205
|
dependencies: dependencies
|
124
206
|
}
|
125
207
|
end
|
126
208
|
|
127
209
|
asset = {
|
128
|
-
uri: uri,
|
210
|
+
uri: unloaded.uri,
|
129
211
|
load_path: load_path,
|
130
|
-
filename: filename,
|
212
|
+
filename: unloaded.filename,
|
131
213
|
name: name,
|
132
214
|
logical_path: logical_path,
|
133
215
|
content_type: type,
|
134
216
|
source: source,
|
135
217
|
metadata: metadata,
|
136
|
-
|
137
|
-
dependencies_digest: digest(resolve_dependencies(metadata[:dependencies]))
|
218
|
+
dependencies_digest: DigestUtils.digest(resolve_dependencies(metadata[:dependencies]))
|
138
219
|
}
|
139
220
|
|
140
|
-
asset[:id] =
|
141
|
-
asset[:uri] = build_asset_uri(filename, params.merge(id: asset[:id]))
|
221
|
+
asset[:id] = hexdigest(asset)
|
222
|
+
asset[:uri] = build_asset_uri(unloaded.filename, unloaded.params.merge(id: asset[:id]))
|
142
223
|
|
143
|
-
|
144
|
-
asset
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
224
|
+
store_asset(asset, unloaded)
|
225
|
+
asset
|
226
|
+
end
|
227
|
+
|
228
|
+
# Internal: Save a given asset to the cache
|
229
|
+
#
|
230
|
+
# asset - A hash containing values of loaded asset
|
231
|
+
# unloaded - The UnloadedAsset used to lookup the `asset`
|
232
|
+
#
|
233
|
+
# This method converts all absolute paths to "compressed" paths
|
234
|
+
# which are relative if they're in the root.
|
235
|
+
def store_asset(asset, unloaded)
|
236
|
+
# Save the asset in the cache under the new URI
|
237
|
+
cached_asset = asset.dup
|
238
|
+
cached_asset[:uri] = compress_from_root(asset[:uri])
|
239
|
+
cached_asset[:filename] = compress_from_root(asset[:filename])
|
240
|
+
cached_asset[:load_path] = compress_from_root(asset[:load_path])
|
241
|
+
|
242
|
+
if cached_asset[:metadata]
|
243
|
+
# Deep dup to avoid modifying `asset`
|
244
|
+
cached_asset[:metadata] = cached_asset[:metadata].dup
|
245
|
+
compress_key_from_hash(cached_asset[:metadata], :included)
|
246
|
+
compress_key_from_hash(cached_asset[:metadata], :links)
|
247
|
+
compress_key_from_hash(cached_asset[:metadata], :stubbed)
|
248
|
+
compress_key_from_hash(cached_asset[:metadata], :required)
|
249
|
+
compress_key_from_hash(cached_asset[:metadata], :to_load)
|
250
|
+
compress_key_from_hash(cached_asset[:metadata], :to_link)
|
251
|
+
compress_key_from_hash(cached_asset[:metadata], :dependencies) { |uri| uri.start_with?("file-digest://") }
|
252
|
+
|
253
|
+
cached_asset[:metadata].each do |key, value|
|
254
|
+
next unless key.match?(/_dependencies\z/) # rubocop:disable Performance/EndWith
|
255
|
+
compress_key_from_hash(cached_asset[:metadata], key)
|
150
256
|
end
|
151
|
-
|
257
|
+
end
|
152
258
|
|
153
|
-
|
154
|
-
|
259
|
+
# Unloaded asset and stored_asset now have a different URI
|
260
|
+
stored_asset = UnloadedAsset.new(asset[:uri], self)
|
261
|
+
cache.set(stored_asset.asset_key, cached_asset, true)
|
155
262
|
|
156
|
-
asset
|
263
|
+
# Save the new relative path for the digest key of the unloaded asset
|
264
|
+
cache.set(unloaded.digest_key(asset[:dependencies_digest]), stored_asset.compressed_path, true)
|
157
265
|
end
|
158
266
|
|
159
|
-
def fetch_asset_from_dependency_cache(uri, filename, limit = 3)
|
160
|
-
key = "asset-uri-cache-dependencies:#{VERSION}:#{uri}:#{file_digest(filename)}"
|
161
|
-
history = cache.get(key) || []
|
162
267
|
|
268
|
+
# Internal: Resolve set of dependency URIs.
|
269
|
+
#
|
270
|
+
# uris - An Array of "dependencies" for example:
|
271
|
+
# ["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
|
272
|
+
# "file-digest:///Full/path/app/assets/stylesheets/application.css",
|
273
|
+
# "processors:type=text/css&file_type=text/css&pipeline=self",
|
274
|
+
# "file-digest:///Full/path/app/assets/stylesheets"]
|
275
|
+
#
|
276
|
+
# Returns back array of things that the given uri depends on
|
277
|
+
# For example the environment version, if you're using a different version of sprockets
|
278
|
+
# then the dependencies should be different, this is used only for generating cache key
|
279
|
+
# for example the "environment-version" may be resolved to "environment-1.0-3.2.0" for
|
280
|
+
# version "3.2.0" of sprockets.
|
281
|
+
#
|
282
|
+
# Any paths that are returned are converted to relative paths
|
283
|
+
#
|
284
|
+
# Returns array of resolved dependencies
|
285
|
+
def resolve_dependencies(uris)
|
286
|
+
uris.map { |uri| resolve_dependency(uri) }
|
287
|
+
end
|
288
|
+
|
289
|
+
# Internal: Retrieves an asset based on its digest
|
290
|
+
#
|
291
|
+
# unloaded - An UnloadedAsset
|
292
|
+
# limit - A Fixnum which sets the maximum number of versions of "histories"
|
293
|
+
# stored in the cache
|
294
|
+
#
|
295
|
+
# This method attempts to retrieve the last `limit` number of histories of an asset
|
296
|
+
# from the cache a "history" which is an array of unresolved "dependencies" that the asset needs
|
297
|
+
# to compile. In this case a dependency can refer to either an asset e.g. index.js
|
298
|
+
# may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
|
299
|
+
# compilation, such as the VERSION of Sprockets (i.e. the environment) and what "processors"
|
300
|
+
# are used.
|
301
|
+
#
|
302
|
+
# For example a history array may look something like this
|
303
|
+
#
|
304
|
+
# [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
|
305
|
+
# "file-digest:///Full/path/app/assets/stylesheets/application.css",
|
306
|
+
# "processors:type=text/css&file_digesttype=text/css&pipeline=self",
|
307
|
+
# "file-digest:///Full/path/app/assets/stylesheets"]]
|
308
|
+
#
|
309
|
+
# Where the first entry is a Set of dependencies for last generated version of that asset.
|
310
|
+
# Multiple versions are stored since Sprockets keeps the last `limit` number of assets
|
311
|
+
# generated present in the system.
|
312
|
+
#
|
313
|
+
# If a "history" of dependencies is present in the cache, each version of "history" will be
|
314
|
+
# yielded to the passed block which is responsible for loading the asset. If found, the existing
|
315
|
+
# history will be saved with the dependency that found a valid asset moved to the front.
|
316
|
+
#
|
317
|
+
# If no history is present, or if none of the histories could be resolved to a valid asset then,
|
318
|
+
# the block is yielded to and expected to return a valid asset.
|
319
|
+
# When this happens the dependencies for the returned asset are added to the "history", and older
|
320
|
+
# entries are removed if the "history" is above `limit`.
|
321
|
+
def fetch_asset_from_dependency_cache(unloaded, limit = 3)
|
322
|
+
key = unloaded.dependency_history_key
|
323
|
+
|
324
|
+
history = cache.get(key) || []
|
163
325
|
history.each_with_index do |deps, index|
|
164
|
-
|
326
|
+
expanded_deps = deps.map do |path|
|
327
|
+
path.start_with?("file-digest://") ? expand_from_root(path) : path
|
328
|
+
end
|
329
|
+
if asset = yield(expanded_deps)
|
165
330
|
cache.set(key, history.rotate!(index)) if index > 0
|
166
331
|
return asset
|
167
332
|
end
|
168
333
|
end
|
169
334
|
|
170
335
|
asset = yield
|
171
|
-
deps
|
336
|
+
deps = asset[:metadata][:dependencies].dup.map! do |uri|
|
337
|
+
uri.start_with?("file-digest://") ? compress_from_root(uri) : uri
|
338
|
+
end
|
172
339
|
cache.set(key, history.unshift(deps).take(limit))
|
173
340
|
asset
|
174
341
|
end
|
data/lib/sprockets/manifest.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'json'
|
2
3
|
require 'time'
|
4
|
+
|
5
|
+
require 'concurrent'
|
6
|
+
|
3
7
|
require 'sprockets/manifest_utils'
|
4
8
|
|
5
9
|
module Sprockets
|
@@ -48,14 +52,8 @@ module Sprockets
|
|
48
52
|
@directory ||= File.dirname(@filename) if @filename
|
49
53
|
|
50
54
|
# If directory is given w/o filename, pick a random manifest location
|
51
|
-
@rename_filename = nil
|
52
55
|
if @directory && @filename.nil?
|
53
|
-
@filename = find_directory_manifest(@directory)
|
54
|
-
|
55
|
-
# If legacy manifest name autodetected, mark to rename on save
|
56
|
-
if File.basename(@filename).start_with?("manifest")
|
57
|
-
@rename_filename = File.join(@directory, generate_manifest_path)
|
58
|
-
end
|
56
|
+
@filename = find_directory_manifest(@directory, logger)
|
59
57
|
end
|
60
58
|
|
61
59
|
unless @directory && @filename
|
@@ -121,31 +119,38 @@ module Sprockets
|
|
121
119
|
|
122
120
|
return to_enum(__method__, *args) unless block_given?
|
123
121
|
|
124
|
-
paths, filters = args.flatten.partition { |arg| self.class.simple_logical_path?(arg) }
|
125
|
-
filters = filters.map { |arg| self.class.compile_match_filter(arg) }
|
126
|
-
|
127
122
|
environment = self.environment.cached
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
if filters.any?
|
136
|
-
environment.logical_paths do |logical_path, filename|
|
137
|
-
if filters.any? { |f| f.call(logical_path, filename) }
|
138
|
-
environment.find_all_linked_assets(filename) do |asset|
|
139
|
-
yield asset
|
140
|
-
end
|
123
|
+
promises = args.flatten.map do |path|
|
124
|
+
Concurrent::Promise.execute(executor: executor) do
|
125
|
+
environment.find_all_linked_assets(path) do |asset|
|
126
|
+
yield asset
|
141
127
|
end
|
142
128
|
end
|
143
129
|
end
|
130
|
+
promises.each(&:wait!)
|
144
131
|
|
145
132
|
nil
|
146
133
|
end
|
147
134
|
|
148
|
-
#
|
135
|
+
# Public: Find the source of assets by paths.
|
136
|
+
#
|
137
|
+
# Returns Enumerator of assets file content.
|
138
|
+
def find_sources(*args)
|
139
|
+
return to_enum(__method__, *args) unless block_given?
|
140
|
+
|
141
|
+
if environment
|
142
|
+
find(*args).each do |asset|
|
143
|
+
yield asset.source
|
144
|
+
end
|
145
|
+
else
|
146
|
+
args.each do |path|
|
147
|
+
asset = assets[path]
|
148
|
+
yield File.binread(File.join(dir, asset)) if asset
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Compile asset to directory. The asset is written to a
|
149
154
|
# fingerprinted filename like
|
150
155
|
# `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
|
151
156
|
# also inserted into the manifest file.
|
@@ -157,29 +162,46 @@ module Sprockets
|
|
157
162
|
raise Error, "manifest requires environment for compilation"
|
158
163
|
end
|
159
164
|
|
160
|
-
filenames
|
165
|
+
filenames = []
|
166
|
+
concurrent_exporters = []
|
161
167
|
|
168
|
+
assets_to_export = Concurrent::Array.new
|
162
169
|
find(*args) do |asset|
|
170
|
+
assets_to_export << asset
|
171
|
+
end
|
172
|
+
|
173
|
+
assets_to_export.each do |asset|
|
174
|
+
mtime = Time.now.iso8601
|
163
175
|
files[asset.digest_path] = {
|
164
176
|
'logical_path' => asset.logical_path,
|
165
|
-
'mtime' =>
|
177
|
+
'mtime' => mtime,
|
166
178
|
'size' => asset.bytesize,
|
167
179
|
'digest' => asset.hexdigest,
|
168
|
-
|
180
|
+
|
181
|
+
# Deprecated: Remove beta integrity attribute in next release.
|
182
|
+
# Callers should DigestUtils.hexdigest_integrity_uri to compute the
|
183
|
+
# digest themselves.
|
184
|
+
'integrity' => DigestUtils.hexdigest_integrity_uri(asset.hexdigest)
|
169
185
|
}
|
170
186
|
assets[asset.logical_path] = asset.digest_path
|
171
187
|
|
172
|
-
|
188
|
+
filenames << asset.filename
|
173
189
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
logger.info "Writing #{target}"
|
178
|
-
asset.write_to target
|
179
|
-
end
|
190
|
+
promise = nil
|
191
|
+
exporters_for_asset(asset) do |exporter|
|
192
|
+
next if exporter.skip?(logger)
|
180
193
|
|
181
|
-
|
194
|
+
if promise.nil?
|
195
|
+
promise = Concurrent::Promise.new(executor: executor) { exporter.call }
|
196
|
+
concurrent_exporters << promise.execute
|
197
|
+
else
|
198
|
+
concurrent_exporters << promise.then { exporter.call }
|
199
|
+
end
|
200
|
+
end
|
182
201
|
end
|
202
|
+
|
203
|
+
# make sure all exporters have finished before returning the main thread
|
204
|
+
concurrent_exporters.each(&:wait!)
|
183
205
|
save
|
184
206
|
|
185
207
|
filenames
|
@@ -192,6 +214,7 @@ module Sprockets
|
|
192
214
|
#
|
193
215
|
def remove(filename)
|
194
216
|
path = File.join(dir, filename)
|
217
|
+
gzip = "#{path}.gz"
|
195
218
|
logical_path = files[filename]['logical_path']
|
196
219
|
|
197
220
|
if assets[logical_path] == filename
|
@@ -200,6 +223,7 @@ module Sprockets
|
|
200
223
|
|
201
224
|
files.delete(filename)
|
202
225
|
FileUtils.rm(path) if File.exist?(path)
|
226
|
+
FileUtils.rm(gzip) if File.exist?(gzip)
|
203
227
|
|
204
228
|
save
|
205
229
|
|
@@ -230,9 +254,9 @@ module Sprockets
|
|
230
254
|
# Sort by timestamp
|
231
255
|
Time.parse(attrs['mtime'])
|
232
256
|
}.reverse.each_with_index.drop_while { |(_, attrs), index|
|
233
|
-
|
257
|
+
_age = [0, Time.now - Time.parse(attrs['mtime'])].max
|
234
258
|
# Keep if under age or within the count limit
|
235
|
-
|
259
|
+
_age < age || index < count
|
236
260
|
}.each { |(path, _), _|
|
237
261
|
# Remove old assets
|
238
262
|
remove(path)
|
@@ -244,18 +268,13 @@ module Sprockets
|
|
244
268
|
def clobber
|
245
269
|
FileUtils.rm_r(directory) if File.exist?(directory)
|
246
270
|
logger.info "Removed #{directory}"
|
271
|
+
# if we have an environment clear the cache too
|
272
|
+
environment.cache.clear if environment
|
247
273
|
nil
|
248
274
|
end
|
249
275
|
|
250
276
|
# Persist manfiest back to FS
|
251
277
|
def save
|
252
|
-
if @rename_filename
|
253
|
-
logger.info "Renaming #{@filename} to #{@rename_filename}"
|
254
|
-
FileUtils.mv(@filename, @rename_filename)
|
255
|
-
@filename = @rename_filename
|
256
|
-
@rename_filename = nil
|
257
|
-
end
|
258
|
-
|
259
278
|
data = json_encode(@data)
|
260
279
|
FileUtils.mkdir_p File.dirname(@filename)
|
261
280
|
PathUtils.atomic_write(@filename) do |f|
|
@@ -264,6 +283,36 @@ module Sprockets
|
|
264
283
|
end
|
265
284
|
|
266
285
|
private
|
286
|
+
|
287
|
+
# Given an asset, finds all exporters that
|
288
|
+
# match its mime-type.
|
289
|
+
#
|
290
|
+
# Will yield each expoter to the passed in block.
|
291
|
+
#
|
292
|
+
# array = []
|
293
|
+
# puts asset.content_type # => "application/javascript"
|
294
|
+
# exporters_for_asset(asset) do |exporter|
|
295
|
+
# array << exporter
|
296
|
+
# end
|
297
|
+
# # puts array => [Exporters::FileExporter, Exporters::ZlibExporter]
|
298
|
+
def exporters_for_asset(asset)
|
299
|
+
exporters = [Exporters::FileExporter]
|
300
|
+
|
301
|
+
environment.exporters.each do |mime_type, exporter_list|
|
302
|
+
next unless asset.content_type
|
303
|
+
next unless environment.match_mime_type? asset.content_type, mime_type
|
304
|
+
exporter_list.each do |exporter|
|
305
|
+
exporters << exporter
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
exporters.uniq!
|
310
|
+
|
311
|
+
exporters.each do |exporter|
|
312
|
+
yield exporter.new(asset: asset, environment: environment, directory: dir)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
267
316
|
def json_decode(obj)
|
268
317
|
JSON.parse(obj, create_additions: false)
|
269
318
|
end
|
@@ -281,5 +330,9 @@ module Sprockets
|
|
281
330
|
logger
|
282
331
|
end
|
283
332
|
end
|
333
|
+
|
334
|
+
def executor
|
335
|
+
@executor ||= environment.export_concurrent ? :fast : :immediate
|
336
|
+
end
|
284
337
|
end
|
285
338
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'securerandom'
|
3
|
+
require 'logger'
|
2
4
|
|
3
5
|
module Sprockets
|
4
6
|
# Public: Manifest utilities.
|
@@ -6,7 +8,6 @@ module Sprockets
|
|
6
8
|
extend self
|
7
9
|
|
8
10
|
MANIFEST_RE = /^\.sprockets-manifest-[0-9a-f]{32}.json$/
|
9
|
-
LEGACY_MANIFEST_RE = /^manifest(-[0-9a-f]{32})?.json$/
|
10
11
|
|
11
12
|
# Public: Generate a new random manifest path.
|
12
13
|
#
|
@@ -33,12 +34,14 @@ module Sprockets
|
|
33
34
|
# # => "/app/public/assets/.sprockets-manifest-abc123.json"
|
34
35
|
#
|
35
36
|
# Returns String filename.
|
36
|
-
def find_directory_manifest(dirname)
|
37
|
+
def find_directory_manifest(dirname, logger = Logger.new($stderr))
|
37
38
|
entries = File.directory?(dirname) ? Dir.entries(dirname) : []
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
manifest_entries = entries.select { |e| e =~ MANIFEST_RE }
|
40
|
+
if manifest_entries.length > 1
|
41
|
+
manifest_entries.sort!
|
42
|
+
logger.warn("Found multiple manifests: #{manifest_entries}. Choosing the first alphabetically: #{manifest_entries.first}")
|
43
|
+
end
|
44
|
+
entry = manifest_entries.first || generate_manifest_path
|
42
45
|
File.join(dirname, entry)
|
43
46
|
end
|
44
47
|
end
|