sprockets 2.12.5 → 3.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sprockets might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/LICENSE +2 -2
- data/README.md +61 -34
- data/lib/rake/sprocketstask.rb +5 -4
- data/lib/sprockets.rb +123 -85
- data/lib/sprockets/asset.rb +161 -200
- data/lib/sprockets/asset_uri.rb +64 -0
- data/lib/sprockets/base.rb +138 -373
- data/lib/sprockets/bower.rb +56 -0
- data/lib/sprockets/bundle.rb +32 -0
- data/lib/sprockets/cache.rb +220 -0
- data/lib/sprockets/cache/file_store.rb +145 -13
- data/lib/sprockets/cache/memory_store.rb +66 -0
- data/lib/sprockets/cache/null_store.rb +46 -0
- data/lib/sprockets/cached_environment.rb +103 -0
- data/lib/sprockets/closure_compressor.rb +30 -12
- data/lib/sprockets/coffee_script_template.rb +23 -0
- data/lib/sprockets/compressing.rb +20 -25
- data/lib/sprockets/configuration.rb +95 -0
- data/lib/sprockets/context.rb +68 -131
- data/lib/sprockets/directive_processor.rb +138 -179
- data/lib/sprockets/eco_template.rb +10 -19
- data/lib/sprockets/ejs_template.rb +10 -19
- data/lib/sprockets/encoding_utils.rb +246 -0
- data/lib/sprockets/engines.rb +40 -29
- data/lib/sprockets/environment.rb +10 -66
- data/lib/sprockets/erb_template.rb +23 -0
- data/lib/sprockets/errors.rb +5 -13
- data/lib/sprockets/http_utils.rb +97 -0
- data/lib/sprockets/jst_processor.rb +28 -15
- data/lib/sprockets/lazy_processor.rb +15 -0
- data/lib/sprockets/legacy.rb +23 -0
- data/lib/sprockets/legacy_proc_processor.rb +35 -0
- data/lib/sprockets/legacy_tilt_processor.rb +29 -0
- data/lib/sprockets/manifest.rb +128 -99
- data/lib/sprockets/mime.rb +114 -33
- data/lib/sprockets/path_utils.rb +179 -0
- data/lib/sprockets/paths.rb +13 -26
- data/lib/sprockets/processing.rb +198 -107
- data/lib/sprockets/resolve.rb +289 -0
- data/lib/sprockets/sass_compressor.rb +36 -17
- data/lib/sprockets/sass_template.rb +269 -46
- data/lib/sprockets/server.rb +113 -83
- data/lib/sprockets/transformers.rb +69 -0
- data/lib/sprockets/uglifier_compressor.rb +36 -15
- data/lib/sprockets/utils.rb +161 -44
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets/yui_compressor.rb +37 -12
- metadata +64 -106
- data/lib/sprockets/asset_attributes.rb +0 -137
- data/lib/sprockets/bundled_asset.rb +0 -78
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/index.rb +0 -100
- 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_cache_store.rb +0 -29
- data/lib/sprockets/sass_functions.rb +0 -70
- data/lib/sprockets/sass_importer.rb +0 -30
- data/lib/sprockets/scss_template.rb +0 -13
- data/lib/sprockets/static_asset.rb +0 -60
data/lib/sprockets/asset.rb
CHANGED
@@ -1,269 +1,230 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
3
|
|
4
4
|
module Sprockets
|
5
|
-
# `Asset` is the base class for `BundledAsset` and `StaticAsset`.
|
6
5
|
class Asset
|
7
|
-
|
8
|
-
def self.from_hash(environment, hash)
|
9
|
-
return unless hash.is_a?(Hash)
|
6
|
+
attr_reader :logical_path
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
asset.init_with(environment, hash)
|
25
|
-
asset
|
8
|
+
# Private: Intialize Asset wrapper from attributes Hash.
|
9
|
+
#
|
10
|
+
# Asset wrappers should not be initialized directly, only
|
11
|
+
# Environment#find_asset should vend them.
|
12
|
+
#
|
13
|
+
# attributes - Hash of ivars
|
14
|
+
#
|
15
|
+
# Returns Asset.
|
16
|
+
def initialize(environment, attributes = {})
|
17
|
+
@environment = environment
|
18
|
+
@attributes = attributes
|
19
|
+
attributes.each do |name, value|
|
20
|
+
instance_variable_set("@#{name}", value)
|
26
21
|
end
|
27
|
-
rescue UnserializeError
|
28
|
-
nil
|
29
22
|
end
|
30
23
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
raise ArgumentError, "Asset logical path has no extension: #{logical_path}" if File.extname(logical_path) == ""
|
37
|
-
|
38
|
-
@root = environment.root
|
39
|
-
@logical_path = logical_path.to_s
|
40
|
-
@pathname = Pathname.new(pathname)
|
41
|
-
@content_type = environment.content_type_of(pathname)
|
42
|
-
# drop precision to 1 second, same pattern followed elsewhere
|
43
|
-
@mtime = Time.at(environment.stat(pathname).mtime.to_i)
|
44
|
-
@length = environment.stat(pathname).size
|
45
|
-
@digest = environment.file_digest(pathname).hexdigest
|
24
|
+
# Internal: Return all internal instance variables as a hash.
|
25
|
+
#
|
26
|
+
# Returns a Hash.
|
27
|
+
def to_hash
|
28
|
+
@attributes
|
46
29
|
end
|
47
30
|
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if pathname = coder['pathname']
|
57
|
-
# Expand `$root` placeholder and wrapper string in a `Pathname`
|
58
|
-
@pathname = Pathname.new(expand_root_path(pathname))
|
59
|
-
end
|
31
|
+
# Public: Metadata accumulated from pipeline process.
|
32
|
+
#
|
33
|
+
# The API status of the keys is dependent on the pipeline processors
|
34
|
+
# itself. So some values maybe considered public and others internal.
|
35
|
+
# See the pipeline proccessor documentation itself.
|
36
|
+
#
|
37
|
+
# Returns Hash.
|
38
|
+
attr_reader :metadata
|
60
39
|
|
61
|
-
|
62
|
-
|
63
|
-
end
|
40
|
+
# Public: Returns String path of asset.
|
41
|
+
attr_reader :filename
|
64
42
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
43
|
+
# Deprecated: Use #filename instead.
|
44
|
+
#
|
45
|
+
# Returns Pathname.
|
46
|
+
def pathname
|
47
|
+
@pathname ||= Pathname.new(filename)
|
69
48
|
end
|
70
49
|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
50
|
+
# Internal: Unique asset object ID.
|
51
|
+
#
|
52
|
+
# Returns String SHA1 String.
|
53
|
+
attr_reader :id
|
54
|
+
|
55
|
+
# Public: Internal URI to lookup asset by.
|
56
|
+
#
|
57
|
+
# NOT a publically accessible URL.
|
58
|
+
#
|
59
|
+
# Returns URI.
|
60
|
+
attr_reader :uri
|
81
61
|
|
82
|
-
# Return logical path with digest spliced in.
|
62
|
+
# Public: Return logical path with digest spliced in.
|
83
63
|
#
|
84
64
|
# "foo/bar-37b51d194a7513e45b56f6524f2d51f2.js"
|
85
65
|
#
|
66
|
+
# Returns String.
|
86
67
|
def digest_path
|
87
68
|
logical_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
|
88
69
|
end
|
89
70
|
|
90
|
-
#
|
91
|
-
|
92
|
-
|
71
|
+
# Public: Returns String MIME type of asset. Returns nil if type is unknown.
|
72
|
+
attr_reader :content_type
|
73
|
+
|
74
|
+
# Public: Get all externally linked asset filenames from asset.
|
75
|
+
#
|
76
|
+
# All linked assets should be compiled anytime this asset is.
|
77
|
+
#
|
78
|
+
# Returns Set of String asset URIs.
|
79
|
+
def links
|
80
|
+
metadata[:links] || Set.new
|
93
81
|
end
|
94
82
|
|
95
|
-
#
|
83
|
+
# Public: Get all internally required assets that were concated into this
|
84
|
+
# asset.
|
85
|
+
#
|
86
|
+
# Returns Array of String asset URIs.
|
87
|
+
def included
|
88
|
+
metadata[:included]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Deprecated: Expand asset into an `Array` of parts.
|
96
92
|
#
|
97
93
|
# Appending all of an assets body parts together should give you
|
98
94
|
# the asset's contents as a whole.
|
99
95
|
#
|
100
96
|
# This allows you to link to individual files for debugging
|
101
97
|
# purposes.
|
98
|
+
#
|
99
|
+
# Use Asset#included instead. Keeping a full copy of the bundle's processed
|
100
|
+
# assets in memory (and in cache) is expensive and redundant. The common use
|
101
|
+
# case is to relink to the assets anyway.
|
102
|
+
#
|
103
|
+
# Returns Array of Assets.
|
102
104
|
def to_a
|
103
|
-
[
|
105
|
+
if metadata[:included]
|
106
|
+
metadata[:included].map { |uri| @environment.find_asset_by_uri(uri) }
|
107
|
+
else
|
108
|
+
[self]
|
109
|
+
end
|
104
110
|
end
|
105
111
|
|
106
|
-
#
|
107
|
-
|
108
|
-
|
112
|
+
# Deprecated: Get all required Assets.
|
113
|
+
#
|
114
|
+
# See Asset#to_a
|
115
|
+
#
|
116
|
+
# Returns Array of Assets.
|
117
|
+
def dependencies
|
118
|
+
to_a.reject { |a| a.filename.eql?(self.filename) }
|
119
|
+
end
|
120
|
+
|
121
|
+
# Public: Return `String` of concatenated source.
|
122
|
+
#
|
123
|
+
# Returns String.
|
124
|
+
def source
|
125
|
+
if defined? @source
|
126
|
+
@source
|
127
|
+
else
|
128
|
+
# File is read everytime to avoid memory bloat of large binary files
|
129
|
+
File.open(filename, 'rb') { |f| f.read }
|
130
|
+
end
|
109
131
|
end
|
110
132
|
|
111
|
-
#
|
133
|
+
# Public: Alias for #source.
|
134
|
+
#
|
135
|
+
# Returns String.
|
112
136
|
def to_s
|
113
137
|
source
|
114
138
|
end
|
115
139
|
|
116
|
-
#
|
117
|
-
#
|
118
|
-
|
119
|
-
|
140
|
+
# Public: HTTP encoding for Asset, "deflate", "gzip", etc.
|
141
|
+
#
|
142
|
+
# Note: This is not the Ruby Encoding of the source. See Asset#charset.
|
143
|
+
#
|
144
|
+
# Returns a String or nil if encoding is "identity".
|
145
|
+
def encoding
|
146
|
+
metadata[:encoding]
|
120
147
|
end
|
121
148
|
|
122
|
-
#
|
123
|
-
# digest to the inmemory model.
|
149
|
+
# Public: Get charset of source.
|
124
150
|
#
|
125
|
-
#
|
126
|
-
|
127
|
-
# Check current mtime and digest
|
128
|
-
dependency_fresh?(environment, self)
|
129
|
-
end
|
151
|
+
# Returns a String charset name or nil if binary.
|
152
|
+
attr_reader :charset
|
130
153
|
|
131
|
-
#
|
132
|
-
|
154
|
+
# Public: Returns Integer length of source.
|
155
|
+
attr_reader :length
|
156
|
+
alias_method :bytesize, :length
|
157
|
+
|
158
|
+
# Deprecated: Returns Time of the last time the source was modified.
|
159
|
+
#
|
160
|
+
# Time resolution is normalized to the nearest second.
|
133
161
|
#
|
134
|
-
#
|
135
|
-
def
|
136
|
-
|
162
|
+
# Returns Time.
|
163
|
+
def mtime
|
164
|
+
Time.at(@mtime)
|
137
165
|
end
|
138
166
|
|
139
|
-
#
|
140
|
-
|
141
|
-
# Gzip contents if filename has '.gz'
|
142
|
-
unless options.key?(:compress)
|
143
|
-
options[:compress] = File.extname(filename) == '.gz' && File.extname(logical_path) != '.gz'
|
144
|
-
end
|
167
|
+
# Public: Returns String hexdigest of source.
|
168
|
+
attr_reader :digest
|
145
169
|
|
170
|
+
# Pubic: ETag String of Asset.
|
171
|
+
alias_method :etag, :digest
|
172
|
+
|
173
|
+
# Public: Add enumerator to allow `Asset` instances to be used as Rack
|
174
|
+
# compatible body objects.
|
175
|
+
#
|
176
|
+
# block
|
177
|
+
# part - String body chunk
|
178
|
+
#
|
179
|
+
# Returns nothing.
|
180
|
+
def each
|
181
|
+
yield to_s
|
182
|
+
end
|
183
|
+
|
184
|
+
# Public: Save asset to disk.
|
185
|
+
#
|
186
|
+
# filename - String target
|
187
|
+
#
|
188
|
+
# Returns nothing.
|
189
|
+
def write_to(filename)
|
146
190
|
FileUtils.mkdir_p File.dirname(filename)
|
147
191
|
|
148
|
-
|
149
|
-
|
150
|
-
# Run contents through `Zlib`
|
151
|
-
gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
|
152
|
-
gz.mtime = mtime.to_i
|
153
|
-
gz.write to_s
|
154
|
-
gz.close
|
155
|
-
else
|
156
|
-
# Write out as is
|
157
|
-
f.write to_s
|
158
|
-
end
|
192
|
+
PathUtils.atomic_write(filename) do |f|
|
193
|
+
f.write source
|
159
194
|
end
|
160
195
|
|
161
|
-
# Atomic write
|
162
|
-
FileUtils.mv("#{filename}+", filename)
|
163
|
-
|
164
196
|
# Set mtime correctly
|
165
197
|
File.utime(mtime, mtime, filename)
|
166
198
|
|
167
199
|
nil
|
168
|
-
ensure
|
169
|
-
# Ensure tmp file gets cleaned up
|
170
|
-
FileUtils.rm("#{filename}+") if File.exist?("#{filename}+")
|
171
200
|
end
|
172
201
|
|
173
|
-
# Pretty inspect
|
202
|
+
# Public: Pretty inspect
|
203
|
+
#
|
204
|
+
# Returns String.
|
174
205
|
def inspect
|
175
|
-
"#<#{self.class}
|
176
|
-
"
|
177
|
-
"mtime=#{mtime.inspect}, " +
|
206
|
+
"#<#{self.class}:#{id} " +
|
207
|
+
"filename=#{filename.inspect}, " +
|
178
208
|
"digest=#{digest.inspect}" +
|
179
209
|
">"
|
180
210
|
end
|
181
211
|
|
212
|
+
# Public: Implements Object#hash so Assets can be used as a Hash key or
|
213
|
+
# in a Set.
|
214
|
+
#
|
215
|
+
# Returns Integer hash of the id.
|
182
216
|
def hash
|
183
|
-
|
217
|
+
id.hash
|
184
218
|
end
|
185
219
|
|
186
|
-
#
|
220
|
+
# Public: Compare assets.
|
221
|
+
#
|
222
|
+
# Assets are equal if they share the same path and digest.
|
223
|
+
#
|
224
|
+
# Returns true or false.
|
187
225
|
def eql?(other)
|
188
|
-
|
189
|
-
other.logical_path == self.logical_path &&
|
190
|
-
other.mtime.to_i == self.mtime.to_i &&
|
191
|
-
other.digest == self.digest
|
226
|
+
self.class == other.class && self.id == other.id
|
192
227
|
end
|
193
228
|
alias_method :==, :eql?
|
194
|
-
|
195
|
-
protected
|
196
|
-
# Internal: String paths that are marked as dependencies after processing.
|
197
|
-
#
|
198
|
-
# Default to an empty `Array`.
|
199
|
-
def dependency_paths
|
200
|
-
@dependency_paths ||= []
|
201
|
-
end
|
202
|
-
|
203
|
-
# Internal: `ProccessedAsset`s that are required after processing.
|
204
|
-
#
|
205
|
-
# Default to an empty `Array`.
|
206
|
-
def required_assets
|
207
|
-
@required_assets ||= []
|
208
|
-
end
|
209
|
-
|
210
|
-
# Get pathname with its root stripped.
|
211
|
-
def relative_pathname
|
212
|
-
@relative_pathname ||= Pathname.new(relativize_root_path(pathname))
|
213
|
-
end
|
214
|
-
|
215
|
-
# Replace `$root` placeholder with actual environment root.
|
216
|
-
def expand_root_path(path)
|
217
|
-
path.to_s.sub(/^\$root/, @root)
|
218
|
-
end
|
219
|
-
|
220
|
-
# Replace actual environment root with `$root` placeholder.
|
221
|
-
def relativize_root_path(path)
|
222
|
-
path.to_s.sub(/^#{Regexp.escape(@root)}/, '$root')
|
223
|
-
end
|
224
|
-
|
225
|
-
# Check if dependency is fresh.
|
226
|
-
#
|
227
|
-
# `dep` is a `Hash` with `path`, `mtime` and `hexdigest` keys.
|
228
|
-
#
|
229
|
-
# A `Hash` is used rather than other `Asset` object because we
|
230
|
-
# want to test non-asset files and directories.
|
231
|
-
def dependency_fresh?(environment, dep)
|
232
|
-
path, mtime, hexdigest = dep.pathname.to_s, dep.mtime, dep.digest
|
233
|
-
|
234
|
-
stat = environment.stat(path)
|
235
|
-
|
236
|
-
# If path no longer exists, its definitely stale.
|
237
|
-
if stat.nil?
|
238
|
-
return false
|
239
|
-
end
|
240
|
-
|
241
|
-
# Compare dependency mtime to the actual mtime. If the
|
242
|
-
# dependency mtime is newer than the actual mtime, the file
|
243
|
-
# hasn't changed since we created this `Asset` instance.
|
244
|
-
#
|
245
|
-
# However, if the mtime is newer it doesn't mean the asset is
|
246
|
-
# stale. Many deployment environments may recopy or recheckout
|
247
|
-
# assets on each deploy. In this case the mtime would be the
|
248
|
-
# time of deploy rather than modified time.
|
249
|
-
#
|
250
|
-
# Note: to_i is used in eql? and write_to we assume fidelity of 1 second
|
251
|
-
# if people save files more frequently than 1 second sprockets may
|
252
|
-
# not pick it up, by design
|
253
|
-
if mtime.to_i >= stat.mtime.to_i
|
254
|
-
return true
|
255
|
-
end
|
256
|
-
|
257
|
-
digest = environment.file_digest(path)
|
258
|
-
|
259
|
-
# If the mtime is newer, do a full digest comparsion. Return
|
260
|
-
# fresh if the digests match.
|
261
|
-
if hexdigest == digest.hexdigest
|
262
|
-
return true
|
263
|
-
end
|
264
|
-
|
265
|
-
# Otherwise, its stale.
|
266
|
-
false
|
267
|
-
end
|
268
229
|
end
|
269
230
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'sprockets/errors'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
module AssetURI
|
6
|
+
# Internal: Parse Asset URI.
|
7
|
+
#
|
8
|
+
# Examples
|
9
|
+
#
|
10
|
+
# parse("file:///tmp/js/application.coffee?type=application/javascript")
|
11
|
+
# # => "/tmp/js/application.coffee", {type: "application/javascript"}
|
12
|
+
#
|
13
|
+
# str - String asset URI
|
14
|
+
#
|
15
|
+
# Returns String path and Hash of symbolized parameters.
|
16
|
+
def self.parse(str)
|
17
|
+
uri = URI(str)
|
18
|
+
|
19
|
+
unless uri.scheme == 'file'
|
20
|
+
raise URI::InvalidURIError, "expected file:// scheme: #{str}"
|
21
|
+
end
|
22
|
+
|
23
|
+
path = URI::Generic::DEFAULT_PARSER.unescape(uri.path)
|
24
|
+
path.force_encoding(Encoding::UTF_8)
|
25
|
+
|
26
|
+
params = uri.query.to_s.split('&').reduce({}) do |h, p|
|
27
|
+
k, v = p.split('=', 2)
|
28
|
+
h.merge(k.to_sym => v || true)
|
29
|
+
end
|
30
|
+
|
31
|
+
return path, params
|
32
|
+
end
|
33
|
+
|
34
|
+
# Internal: Build Asset URI.
|
35
|
+
#
|
36
|
+
# Examples
|
37
|
+
#
|
38
|
+
# build("/tmp/js/application.coffee", type: "application/javascript")
|
39
|
+
# # => "file:///tmp/js/application.coffee?type=application/javascript"
|
40
|
+
#
|
41
|
+
# path - String file path
|
42
|
+
# params - Hash of optional parameters
|
43
|
+
#
|
44
|
+
# Returns String URI.
|
45
|
+
def self.build(path, params = {})
|
46
|
+
query = []
|
47
|
+
params.each do |key, value|
|
48
|
+
case value
|
49
|
+
when String
|
50
|
+
query << "#{key}=#{value}"
|
51
|
+
when TrueClass
|
52
|
+
query << "#{key}"
|
53
|
+
when FalseClass, NilClass
|
54
|
+
else
|
55
|
+
raise TypeError, "unexpected type: #{value.class}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
uri = "file://#{URI::Generic::DEFAULT_PARSER.escape(path)}"
|
60
|
+
uri << "?#{query.join('&')}" if query.any?
|
61
|
+
uri
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|