sprockets 2.6.0 → 4.2.2
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 +7 -0
- data/CHANGELOG.md +118 -0
- data/{LICENSE → MIT-LICENSE} +2 -2
- data/README.md +541 -289
- data/bin/sprockets +20 -7
- data/lib/rake/sprocketstask.rb +34 -17
- data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
- data/lib/sprockets/asset.rb +158 -210
- data/lib/sprockets/autoload/babel.rb +8 -0
- data/lib/sprockets/autoload/closure.rb +8 -0
- data/lib/sprockets/autoload/coffee_script.rb +8 -0
- data/lib/sprockets/autoload/eco.rb +8 -0
- data/lib/sprockets/autoload/ejs.rb +8 -0
- data/lib/sprockets/autoload/jsminc.rb +8 -0
- data/lib/sprockets/autoload/sass.rb +8 -0
- data/lib/sprockets/autoload/sassc.rb +8 -0
- data/lib/sprockets/autoload/uglifier.rb +8 -0
- data/lib/sprockets/autoload/yui.rb +8 -0
- data/lib/sprockets/autoload/zopfli.rb +7 -0
- data/lib/sprockets/autoload.rb +16 -0
- data/lib/sprockets/babel_processor.rb +66 -0
- data/lib/sprockets/base.rb +89 -378
- data/lib/sprockets/bower.rb +61 -0
- data/lib/sprockets/bundle.rb +105 -0
- data/lib/sprockets/cache/file_store.rb +190 -14
- data/lib/sprockets/cache/memory_store.rb +84 -0
- data/lib/sprockets/cache/null_store.rb +54 -0
- data/lib/sprockets/cache.rb +271 -0
- data/lib/sprockets/cached_environment.rb +64 -0
- data/lib/sprockets/closure_compressor.rb +48 -0
- data/lib/sprockets/coffee_script_processor.rb +39 -0
- data/lib/sprockets/compressing.rb +134 -0
- data/lib/sprockets/configuration.rb +79 -0
- data/lib/sprockets/context.rb +166 -150
- data/lib/sprockets/dependencies.rb +74 -0
- data/lib/sprockets/digest_utils.rb +197 -0
- data/lib/sprockets/directive_processor.rb +241 -215
- data/lib/sprockets/eco_processor.rb +33 -0
- data/lib/sprockets/ejs_processor.rb +32 -0
- data/lib/sprockets/encoding_utils.rb +261 -0
- data/lib/sprockets/environment.rb +23 -64
- data/lib/sprockets/erb_processor.rb +43 -0
- data/lib/sprockets/errors.rb +5 -13
- data/lib/sprockets/exporters/base.rb +71 -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 +16 -0
- data/lib/sprockets/http_utils.rb +135 -0
- data/lib/sprockets/jsminc_compressor.rb +32 -0
- data/lib/sprockets/jst_processor.rb +36 -19
- data/lib/sprockets/loader.rb +347 -0
- data/lib/sprockets/manifest.rb +228 -112
- data/lib/sprockets/manifest_utils.rb +48 -0
- data/lib/sprockets/mime.rb +78 -31
- data/lib/sprockets/npm.rb +52 -0
- data/lib/sprockets/path_dependency_utils.rb +77 -0
- data/lib/sprockets/path_digest_utils.rb +48 -0
- data/lib/sprockets/path_utils.rb +367 -0
- data/lib/sprockets/paths.rb +43 -19
- data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
- data/lib/sprockets/processing.rb +146 -164
- data/lib/sprockets/processor_utils.rb +170 -0
- data/lib/sprockets/resolve.rb +295 -0
- data/lib/sprockets/sass_cache_store.rb +20 -15
- data/lib/sprockets/sass_compressor.rb +55 -10
- data/lib/sprockets/sass_functions.rb +3 -70
- data/lib/sprockets/sass_importer.rb +3 -29
- data/lib/sprockets/sass_processor.rb +313 -0
- data/lib/sprockets/sassc_compressor.rb +56 -0
- data/lib/sprockets/sassc_processor.rb +297 -0
- data/lib/sprockets/server.rb +159 -91
- data/lib/sprockets/source_map_processor.rb +66 -0
- data/lib/sprockets/source_map_utils.rb +483 -0
- data/lib/sprockets/transformers.rb +173 -0
- data/lib/sprockets/uglifier_compressor.rb +66 -0
- data/lib/sprockets/unloaded_asset.rb +139 -0
- data/lib/sprockets/uri_tar.rb +99 -0
- data/lib/sprockets/uri_utils.rb +194 -0
- data/lib/sprockets/utils/gzip.rb +99 -0
- data/lib/sprockets/utils.rb +193 -52
- data/lib/sprockets/version.rb +2 -1
- data/lib/sprockets/yui_compressor.rb +56 -0
- data/lib/sprockets.rb +217 -75
- metadata +272 -117
- data/lib/sprockets/asset_attributes.rb +0 -131
- data/lib/sprockets/bundled_asset.rb +0 -80
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/eco_template.rb +0 -38
- data/lib/sprockets/ejs_template.rb +0 -37
- data/lib/sprockets/engines.rb +0 -74
- data/lib/sprockets/index.rb +0 -99
- data/lib/sprockets/processed_asset.rb +0 -152
- data/lib/sprockets/processor.rb +0 -32
- data/lib/sprockets/safety_colons.rb +0 -28
- data/lib/sprockets/sass_template.rb +0 -60
- data/lib/sprockets/scss_template.rb +0 -13
- data/lib/sprockets/static_asset.rb +0 -58
data/lib/sprockets/manifest.rb
CHANGED
|
@@ -1,51 +1,85 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'json'
|
|
2
3
|
require 'time'
|
|
3
4
|
|
|
5
|
+
require 'concurrent'
|
|
6
|
+
|
|
7
|
+
require 'sprockets/manifest_utils'
|
|
8
|
+
|
|
4
9
|
module Sprockets
|
|
5
|
-
# The Manifest logs the contents of assets compiled to a single
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
10
|
+
# The Manifest logs the contents of assets compiled to a single directory. It
|
|
11
|
+
# records basic attributes about the asset for fast lookup without having to
|
|
12
|
+
# compile. A pointer from each logical path indicates which fingerprinted
|
|
13
|
+
# asset is the current one.
|
|
9
14
|
#
|
|
10
|
-
# The JSON is part of the public API and should be considered
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
15
|
+
# The JSON is part of the public API and should be considered stable. This
|
|
16
|
+
# should make it easy to read from other programming languages and processes
|
|
17
|
+
# that don't have sprockets loaded. See `#assets` and `#files` for more
|
|
18
|
+
# information about the structure.
|
|
14
19
|
class Manifest
|
|
15
|
-
|
|
20
|
+
include ManifestUtils
|
|
21
|
+
|
|
22
|
+
attr_reader :environment
|
|
16
23
|
|
|
17
|
-
# Create new Manifest associated with an `environment`. `
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
24
|
+
# Create new Manifest associated with an `environment`. `filename` is a full
|
|
25
|
+
# path to the manifest json file. The file may or may not already exist. The
|
|
26
|
+
# dirname of the `filename` will be used to write compiled assets to.
|
|
27
|
+
# Otherwise, if the path is a directory, the filename will default a random
|
|
28
|
+
# ".sprockets-manifest-*.json" file in that directory.
|
|
22
29
|
#
|
|
23
30
|
# Manifest.new(environment, "./public/assets/manifest.json")
|
|
24
31
|
#
|
|
25
|
-
def initialize(
|
|
26
|
-
|
|
32
|
+
def initialize(*args)
|
|
33
|
+
if args.first.is_a?(Base) || args.first.nil?
|
|
34
|
+
@environment = args.shift
|
|
35
|
+
end
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
@directory, @filename = args[0], args[1]
|
|
38
|
+
|
|
39
|
+
# Whether the manifest file is using the old manifest-*.json naming convention
|
|
40
|
+
@legacy_manifest = false
|
|
41
|
+
|
|
42
|
+
# Expand paths
|
|
43
|
+
@directory = File.expand_path(@directory) if @directory
|
|
44
|
+
@filename = File.expand_path(@filename) if @filename
|
|
45
|
+
|
|
46
|
+
# If filename is given as the second arg
|
|
47
|
+
if @directory && File.extname(@directory) != ""
|
|
48
|
+
@directory, @filename = nil, @directory
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Default dir to the directory of the filename
|
|
52
|
+
@directory ||= File.dirname(@filename) if @filename
|
|
53
|
+
|
|
54
|
+
# If directory is given w/o filename, pick a random manifest location
|
|
55
|
+
if @directory && @filename.nil?
|
|
56
|
+
@filename = find_directory_manifest(@directory, logger)
|
|
34
57
|
end
|
|
35
58
|
|
|
36
|
-
|
|
59
|
+
unless @directory && @filename
|
|
60
|
+
raise ArgumentError, "manifest requires output filename"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
data = {}
|
|
37
64
|
|
|
38
65
|
begin
|
|
39
|
-
if File.exist?(@
|
|
40
|
-
data = json_decode(File.read(@
|
|
66
|
+
if File.exist?(@filename)
|
|
67
|
+
data = json_decode(File.read(@filename))
|
|
41
68
|
end
|
|
42
|
-
rescue
|
|
43
|
-
logger.error "#{@
|
|
69
|
+
rescue JSON::ParserError => e
|
|
70
|
+
logger.error "#{@filename} is invalid: #{e.class} #{e.message}"
|
|
44
71
|
end
|
|
45
72
|
|
|
46
|
-
@data = data
|
|
73
|
+
@data = data
|
|
47
74
|
end
|
|
48
75
|
|
|
76
|
+
# Returns String path to manifest.json file.
|
|
77
|
+
attr_reader :filename
|
|
78
|
+
alias_method :path, :filename
|
|
79
|
+
|
|
80
|
+
attr_reader :directory
|
|
81
|
+
alias_method :dir, :directory
|
|
82
|
+
|
|
49
83
|
# Returns internal assets mapping. Keys are logical paths which
|
|
50
84
|
# map to the latest fingerprinted filename.
|
|
51
85
|
#
|
|
@@ -75,7 +109,49 @@ module Sprockets
|
|
|
75
109
|
@data['files'] ||= {}
|
|
76
110
|
end
|
|
77
111
|
|
|
78
|
-
#
|
|
112
|
+
# Public: Find all assets matching pattern set in environment.
|
|
113
|
+
#
|
|
114
|
+
# Returns Enumerator of Assets.
|
|
115
|
+
def find(*args, &block)
|
|
116
|
+
unless environment
|
|
117
|
+
raise Error, "manifest requires environment for compilation"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
return to_enum(__method__, *args) unless block_given?
|
|
121
|
+
|
|
122
|
+
environment = self.environment.cached
|
|
123
|
+
promises = args.flatten.map do |path|
|
|
124
|
+
Concurrent::Promise.execute(executor: executor) do
|
|
125
|
+
environment.find_all_linked_assets(path).to_a
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
promises.each do |promise|
|
|
130
|
+
promise.value!.each(&block)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Public: Find the source of assets by paths.
|
|
137
|
+
#
|
|
138
|
+
# Returns Enumerator of assets file content.
|
|
139
|
+
def find_sources(*args)
|
|
140
|
+
return to_enum(__method__, *args) unless block_given?
|
|
141
|
+
|
|
142
|
+
if environment
|
|
143
|
+
find(*args).each do |asset|
|
|
144
|
+
yield asset.source
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
args.each do |path|
|
|
148
|
+
asset = assets[path]
|
|
149
|
+
yield File.binread(File.join(dir, asset)) if asset
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Compile asset to directory. The asset is written to a
|
|
79
155
|
# fingerprinted filename like
|
|
80
156
|
# `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
|
|
81
157
|
# also inserted into the manifest file.
|
|
@@ -83,32 +159,53 @@ module Sprockets
|
|
|
83
159
|
# compile("application.js")
|
|
84
160
|
#
|
|
85
161
|
def compile(*args)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
162
|
+
unless environment
|
|
163
|
+
raise Error, "manifest requires environment for compilation"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
filenames = []
|
|
167
|
+
concurrent_exporters = []
|
|
168
|
+
|
|
169
|
+
assets_to_export = Concurrent::Array.new
|
|
170
|
+
find(*args) do |asset|
|
|
171
|
+
assets_to_export << asset
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
assets_to_export.each do |asset|
|
|
175
|
+
mtime = Time.now.iso8601
|
|
176
|
+
files[asset.digest_path] = {
|
|
177
|
+
'logical_path' => asset.logical_path,
|
|
178
|
+
'mtime' => mtime,
|
|
179
|
+
'size' => asset.bytesize,
|
|
180
|
+
'digest' => asset.hexdigest,
|
|
181
|
+
|
|
182
|
+
# Deprecated: Remove beta integrity attribute in next release.
|
|
183
|
+
# Callers should DigestUtils.hexdigest_integrity_uri to compute the
|
|
184
|
+
# digest themselves.
|
|
185
|
+
'integrity' => DigestUtils.hexdigest_integrity_uri(asset.hexdigest)
|
|
186
|
+
}
|
|
187
|
+
assets[asset.logical_path] = asset.digest_path
|
|
188
|
+
|
|
189
|
+
filenames << asset.filename
|
|
190
|
+
|
|
191
|
+
promise = nil
|
|
192
|
+
exporters_for_asset(asset) do |exporter|
|
|
193
|
+
next if exporter.skip?(logger)
|
|
194
|
+
|
|
195
|
+
if promise.nil?
|
|
196
|
+
promise = Concurrent::Promise.new(executor: executor) { exporter.call }
|
|
197
|
+
concurrent_exporters << promise.execute
|
|
103
198
|
else
|
|
104
|
-
|
|
105
|
-
asset.write_to target
|
|
199
|
+
concurrent_exporters << promise.then { exporter.call }
|
|
106
200
|
end
|
|
107
|
-
|
|
108
|
-
save
|
|
109
|
-
asset
|
|
110
201
|
end
|
|
111
202
|
end
|
|
203
|
+
|
|
204
|
+
# make sure all exporters have finished before returning the main thread
|
|
205
|
+
concurrent_exporters.each(&:wait!)
|
|
206
|
+
save
|
|
207
|
+
|
|
208
|
+
filenames
|
|
112
209
|
end
|
|
113
210
|
|
|
114
211
|
# Removes file from directory and from manifest. `filename` must
|
|
@@ -118,6 +215,7 @@ module Sprockets
|
|
|
118
215
|
#
|
|
119
216
|
def remove(filename)
|
|
120
217
|
path = File.join(dir, filename)
|
|
218
|
+
gzip = "#{path}.gz"
|
|
121
219
|
logical_path = files[filename]['logical_path']
|
|
122
220
|
|
|
123
221
|
if assets[logical_path] == filename
|
|
@@ -126,6 +224,7 @@ module Sprockets
|
|
|
126
224
|
|
|
127
225
|
files.delete(filename)
|
|
128
226
|
FileUtils.rm(path) if File.exist?(path)
|
|
227
|
+
FileUtils.rm(gzip) if File.exist?(gzip)
|
|
129
228
|
|
|
130
229
|
save
|
|
131
230
|
|
|
@@ -135,89 +234,106 @@ module Sprockets
|
|
|
135
234
|
end
|
|
136
235
|
|
|
137
236
|
# Cleanup old assets in the compile directory. By default it will
|
|
138
|
-
# keep the latest version
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
237
|
+
# keep the latest version, 2 backups and any created within the past hour.
|
|
238
|
+
#
|
|
239
|
+
# Examples
|
|
240
|
+
#
|
|
241
|
+
# To force only 1 backup to be kept, set count=1 and age=0.
|
|
242
|
+
#
|
|
243
|
+
# To only keep files created within the last 10 minutes, set count=0 and
|
|
244
|
+
# age=600.
|
|
245
|
+
#
|
|
246
|
+
def clean(count = 2, age = 3600)
|
|
247
|
+
asset_versions = files.group_by { |_, attrs| attrs['logical_path'] }
|
|
143
248
|
|
|
144
|
-
|
|
145
|
-
|
|
249
|
+
asset_versions.each do |logical_path, versions|
|
|
250
|
+
current = assets[logical_path]
|
|
146
251
|
|
|
147
|
-
|
|
148
|
-
|
|
252
|
+
versions.reject { |path, _|
|
|
253
|
+
path == current
|
|
254
|
+
}.sort_by { |_, attrs|
|
|
255
|
+
# Sort by timestamp
|
|
256
|
+
Time.parse(attrs['mtime'])
|
|
257
|
+
}.reverse.each_with_index.drop_while { |(_, attrs), index|
|
|
258
|
+
_age = [0, Time.now - Time.parse(attrs['mtime'])].max
|
|
259
|
+
# Keep if under age or within the count limit
|
|
260
|
+
_age < age || index < count
|
|
261
|
+
}.each { |(path, _), _|
|
|
262
|
+
# Remove old assets
|
|
263
|
+
remove(path)
|
|
264
|
+
}
|
|
149
265
|
end
|
|
150
266
|
end
|
|
151
267
|
|
|
152
268
|
# Wipe directive
|
|
153
269
|
def clobber
|
|
154
|
-
FileUtils.rm_r(
|
|
155
|
-
logger.info "Removed #{
|
|
270
|
+
FileUtils.rm_r(directory) if File.exist?(directory)
|
|
271
|
+
logger.info "Removed #{directory}"
|
|
272
|
+
# if we have an environment clear the cache too
|
|
273
|
+
environment.cache.clear if environment
|
|
156
274
|
nil
|
|
157
275
|
end
|
|
158
276
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
# Matching logical paths
|
|
166
|
-
attrs['logical_path'] == logical_path &&
|
|
167
|
-
# Excluding whatever asset is the current
|
|
168
|
-
assets[logical_path] != filename
|
|
169
|
-
}.sort_by { |filename, attrs|
|
|
170
|
-
# Sort by timestamp
|
|
171
|
-
Time.parse(attrs['mtime'])
|
|
172
|
-
}.reverse
|
|
277
|
+
# Persist manifest back to FS
|
|
278
|
+
def save
|
|
279
|
+
data = json_encode(@data)
|
|
280
|
+
FileUtils.mkdir_p File.dirname(@filename)
|
|
281
|
+
PathUtils.atomic_write(@filename) do |f|
|
|
282
|
+
f.write(data)
|
|
173
283
|
end
|
|
284
|
+
end
|
|
174
285
|
|
|
175
|
-
|
|
176
|
-
def find_asset(logical_path)
|
|
177
|
-
asset = nil
|
|
178
|
-
ms = benchmark do
|
|
179
|
-
asset = environment.find_asset(logical_path)
|
|
180
|
-
end
|
|
181
|
-
logger.debug "Compiled #{logical_path} (#{ms}ms)"
|
|
182
|
-
asset
|
|
183
|
-
end
|
|
286
|
+
private
|
|
184
287
|
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
288
|
+
# Given an asset, finds all exporters that
|
|
289
|
+
# match its mime-type.
|
|
290
|
+
#
|
|
291
|
+
# Will yield each expoter to the passed in block.
|
|
292
|
+
#
|
|
293
|
+
# array = []
|
|
294
|
+
# puts asset.content_type # => "application/javascript"
|
|
295
|
+
# exporters_for_asset(asset) do |exporter|
|
|
296
|
+
# array << exporter
|
|
297
|
+
# end
|
|
298
|
+
# # puts array => [Exporters::FileExporter, Exporters::ZlibExporter]
|
|
299
|
+
def exporters_for_asset(asset)
|
|
300
|
+
exporters = [Exporters::FileExporter]
|
|
192
301
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
302
|
+
environment.exporters.each do |mime_type, exporter_list|
|
|
303
|
+
next unless asset.content_type
|
|
304
|
+
next unless environment.match_mime_type? asset.content_type, mime_type
|
|
305
|
+
exporter_list.each do |exporter|
|
|
306
|
+
exporters << exporter
|
|
307
|
+
end
|
|
198
308
|
end
|
|
199
309
|
|
|
200
|
-
|
|
201
|
-
MultiJson.dump(obj)
|
|
202
|
-
end
|
|
203
|
-
else
|
|
204
|
-
def json_decode(obj)
|
|
205
|
-
MultiJson.decode(obj)
|
|
206
|
-
end
|
|
310
|
+
exporters.uniq!
|
|
207
311
|
|
|
208
|
-
|
|
209
|
-
|
|
312
|
+
exporters.each do |exporter|
|
|
313
|
+
yield exporter.new(asset: asset, environment: environment, directory: dir)
|
|
210
314
|
end
|
|
211
315
|
end
|
|
212
316
|
|
|
317
|
+
def json_decode(obj)
|
|
318
|
+
JSON.parse(obj, create_additions: false)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def json_encode(obj)
|
|
322
|
+
JSON.generate(obj)
|
|
323
|
+
end
|
|
324
|
+
|
|
213
325
|
def logger
|
|
214
|
-
environment
|
|
326
|
+
if environment
|
|
327
|
+
environment.logger
|
|
328
|
+
else
|
|
329
|
+
logger = Logger.new($stderr)
|
|
330
|
+
logger.level = Logger::FATAL
|
|
331
|
+
logger
|
|
332
|
+
end
|
|
215
333
|
end
|
|
216
334
|
|
|
217
|
-
def
|
|
218
|
-
|
|
219
|
-
yield
|
|
220
|
-
((Time.now.to_f - start_time) * 1000).to_i
|
|
335
|
+
def executor
|
|
336
|
+
@executor ||= environment.export_concurrent ? :fast : :immediate
|
|
221
337
|
end
|
|
222
338
|
end
|
|
223
339
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'securerandom'
|
|
3
|
+
require 'logger'
|
|
4
|
+
|
|
5
|
+
module Sprockets
|
|
6
|
+
# Public: Manifest utilities.
|
|
7
|
+
module ManifestUtils
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
MANIFEST_RE = /^\.sprockets-manifest-[0-9a-f]{32}.json$/
|
|
11
|
+
|
|
12
|
+
# Public: Generate a new random manifest path.
|
|
13
|
+
#
|
|
14
|
+
# Manifests are not intended to be accessed publicly, but typically live
|
|
15
|
+
# alongside public assets for convenience. To avoid being served, the
|
|
16
|
+
# filename is prefixed with a "." which is usually hidden by web servers
|
|
17
|
+
# like Apache. To help in other environments that may not control this,
|
|
18
|
+
# a random hex string is appended to the filename to prevent people from
|
|
19
|
+
# guessing the location. If directory indexes are enabled on the server,
|
|
20
|
+
# all bets are off.
|
|
21
|
+
#
|
|
22
|
+
# Return String path.
|
|
23
|
+
def generate_manifest_path
|
|
24
|
+
".sprockets-manifest-#{SecureRandom.hex(16)}.json"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Public: Find or pick a new manifest filename for target build directory.
|
|
28
|
+
#
|
|
29
|
+
# dirname - String dirname
|
|
30
|
+
#
|
|
31
|
+
# Examples
|
|
32
|
+
#
|
|
33
|
+
# find_directory_manifest("/app/public/assets")
|
|
34
|
+
# # => "/app/public/assets/.sprockets-manifest-abc123.json"
|
|
35
|
+
#
|
|
36
|
+
# Returns String filename.
|
|
37
|
+
def find_directory_manifest(dirname, logger = Logger.new($stderr))
|
|
38
|
+
entries = File.directory?(dirname) ? Dir.entries(dirname) : []
|
|
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
|
|
45
|
+
File.join(dirname, entry)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/sprockets/mime.rb
CHANGED
|
@@ -1,48 +1,95 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'sprockets/encoding_utils'
|
|
3
|
+
require 'sprockets/http_utils'
|
|
4
|
+
require 'sprockets/utils'
|
|
2
5
|
|
|
3
6
|
module Sprockets
|
|
4
7
|
module Mime
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
include HTTPUtils, Utils
|
|
9
|
+
|
|
10
|
+
# Public: Mapping of MIME type Strings to properties Hash.
|
|
7
11
|
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
# key - MIME Type String
|
|
13
|
+
# value - Hash
|
|
14
|
+
# extensions - Array of extnames
|
|
15
|
+
# charset - Default Encoding or function to detect encoding
|
|
16
|
+
#
|
|
17
|
+
# Returns Hash.
|
|
18
|
+
def mime_types
|
|
19
|
+
config[:mime_types]
|
|
16
20
|
end
|
|
17
21
|
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
# Internal: Mapping of MIME extension Strings to MIME type Strings.
|
|
23
|
+
#
|
|
24
|
+
# Used for internal fast lookup purposes.
|
|
25
|
+
#
|
|
26
|
+
# Examples:
|
|
27
|
+
#
|
|
28
|
+
# mime_exts['.js'] #=> 'application/javascript'
|
|
29
|
+
#
|
|
30
|
+
# key - MIME extension String
|
|
31
|
+
# value - MIME Type String
|
|
32
|
+
#
|
|
33
|
+
# Returns Hash.
|
|
34
|
+
def mime_exts
|
|
35
|
+
config[:mime_exts]
|
|
21
36
|
end
|
|
22
37
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
# Public: Register a new mime type.
|
|
39
|
+
#
|
|
40
|
+
# mime_type - String MIME Type
|
|
41
|
+
# extensions - Array of String extnames
|
|
42
|
+
# charset - Proc/Method that detects the charset of a file.
|
|
43
|
+
# See EncodingUtils.
|
|
44
|
+
#
|
|
45
|
+
# Returns nothing.
|
|
46
|
+
def register_mime_type(mime_type, extensions: [], charset: nil)
|
|
47
|
+
extnames = Array(extensions)
|
|
48
|
+
|
|
49
|
+
charset ||= :default if mime_type.start_with?('text/')
|
|
50
|
+
charset = EncodingUtils::CHARSET_DETECT[charset] if charset.is_a?(Symbol)
|
|
51
|
+
|
|
52
|
+
self.config = hash_reassoc(config, :mime_exts) do |mime_exts|
|
|
53
|
+
extnames.each do |extname|
|
|
54
|
+
mime_exts[extname] = mime_type
|
|
55
|
+
end
|
|
56
|
+
mime_exts
|
|
26
57
|
end
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
58
|
+
|
|
59
|
+
self.config = hash_reassoc(config, :mime_types) do |mime_types|
|
|
60
|
+
type = { extensions: extnames }
|
|
61
|
+
type[:charset] = charset if charset
|
|
62
|
+
mime_types.merge(mime_type => type)
|
|
30
63
|
end
|
|
31
64
|
end
|
|
32
65
|
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
66
|
+
# Internal: Get detecter function for MIME type.
|
|
67
|
+
#
|
|
68
|
+
# mime_type - String MIME type
|
|
69
|
+
#
|
|
70
|
+
# Returns Proc detector or nil if none is available.
|
|
71
|
+
def mime_type_charset_detecter(mime_type)
|
|
72
|
+
if type = config[:mime_types][mime_type]
|
|
73
|
+
if detect = type[:charset]
|
|
74
|
+
return detect
|
|
75
|
+
end
|
|
76
|
+
end
|
|
37
77
|
end
|
|
38
78
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
79
|
+
# Public: Read file on disk with MIME type specific encoding.
|
|
80
|
+
#
|
|
81
|
+
# filename - String path
|
|
82
|
+
# content_type - String MIME type
|
|
83
|
+
#
|
|
84
|
+
# Returns String file contents transcoded to UTF-8 or in its external
|
|
85
|
+
# encoding.
|
|
86
|
+
def read_file(filename, content_type = nil)
|
|
87
|
+
data = File.binread(filename)
|
|
88
|
+
|
|
89
|
+
if detect = mime_type_charset_detecter(content_type)
|
|
90
|
+
detect.call(data).encode(Encoding::UTF_8, universal_newline: true)
|
|
91
|
+
else
|
|
92
|
+
data
|
|
46
93
|
end
|
|
47
94
|
end
|
|
48
95
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Sprockets
|
|
5
|
+
module Npm
|
|
6
|
+
# Internal: Override resolve_alternates to install package.json behavior.
|
|
7
|
+
#
|
|
8
|
+
# load_path - String environment path
|
|
9
|
+
# logical_path - String path relative to base
|
|
10
|
+
#
|
|
11
|
+
# Returns candidate filenames.
|
|
12
|
+
def resolve_alternates(load_path, logical_path)
|
|
13
|
+
candidates, deps = super
|
|
14
|
+
|
|
15
|
+
dirname = File.join(load_path, logical_path)
|
|
16
|
+
|
|
17
|
+
if directory?(dirname)
|
|
18
|
+
filename = File.join(dirname, 'package.json')
|
|
19
|
+
|
|
20
|
+
if self.file?(filename)
|
|
21
|
+
deps << build_file_digest_uri(filename)
|
|
22
|
+
read_package_directives(dirname, filename) do |path|
|
|
23
|
+
if file?(path)
|
|
24
|
+
candidates << path
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return candidates, deps
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Internal: Read package.json's main and style directives.
|
|
34
|
+
#
|
|
35
|
+
# dirname - String path to component directory.
|
|
36
|
+
# filename - String path to package.json.
|
|
37
|
+
#
|
|
38
|
+
# Returns nothing.
|
|
39
|
+
def read_package_directives(dirname, filename)
|
|
40
|
+
package = JSON.parse(File.read(filename), create_additions: false)
|
|
41
|
+
|
|
42
|
+
case package['main']
|
|
43
|
+
when String
|
|
44
|
+
yield File.expand_path(package['main'], dirname)
|
|
45
|
+
when nil
|
|
46
|
+
yield File.expand_path('index.js', dirname)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
yield File.expand_path(package['style'], dirname) if package['style']
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|