sprockets 3.0.3 → 4.2.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 +101 -0
- data/{LICENSE → MIT-LICENSE} +2 -2
- data/README.md +531 -276
- data/bin/sprockets +12 -7
- data/lib/rake/sprocketstask.rb +9 -4
- data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
- data/lib/sprockets/asset.rb +41 -28
- 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 +61 -13
- data/lib/sprockets/bower.rb +6 -3
- data/lib/sprockets/bundle.rb +41 -5
- data/lib/sprockets/cache/file_store.rb +32 -7
- data/lib/sprockets/cache/memory_store.rb +28 -10
- data/lib/sprockets/cache/null_store.rb +8 -0
- data/lib/sprockets/cache.rb +43 -6
- data/lib/sprockets/cached_environment.rb +15 -20
- data/lib/sprockets/closure_compressor.rb +6 -11
- data/lib/sprockets/coffee_script_processor.rb +20 -6
- data/lib/sprockets/compressing.rb +62 -2
- data/lib/sprockets/configuration.rb +5 -9
- data/lib/sprockets/context.rb +99 -25
- data/lib/sprockets/dependencies.rb +10 -9
- data/lib/sprockets/digest_utils.rb +103 -62
- data/lib/sprockets/directive_processor.rb +64 -36
- data/lib/sprockets/eco_processor.rb +4 -3
- data/lib/sprockets/ejs_processor.rb +4 -3
- data/lib/sprockets/encoding_utils.rb +1 -0
- data/lib/sprockets/environment.rb +9 -4
- data/lib/sprockets/erb_processor.rb +34 -21
- data/lib/sprockets/errors.rb +1 -0
- 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 +1 -0
- data/lib/sprockets/http_utils.rb +25 -7
- data/lib/sprockets/jsminc_compressor.rb +32 -0
- data/lib/sprockets/jst_processor.rb +11 -10
- data/lib/sprockets/loader.rb +244 -62
- data/lib/sprockets/manifest.rb +100 -46
- 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 +107 -22
- data/lib/sprockets/paths.rb +1 -0
- data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
- data/lib/sprockets/processing.rb +32 -52
- data/lib/sprockets/processor_utils.rb +38 -39
- data/lib/sprockets/resolve.rb +177 -97
- 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 +46 -18
- data/lib/sprockets/sassc_compressor.rb +56 -0
- data/lib/sprockets/sassc_processor.rb +297 -0
- data/lib/sprockets/server.rb +77 -44
- 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 +14 -14
- data/lib/sprockets/utils/gzip.rb +99 -0
- data/lib/sprockets/utils.rb +63 -71
- data/lib/sprockets/version.rb +2 -1
- data/lib/sprockets/yui_compressor.rb +5 -14
- data/lib/sprockets.rb +105 -33
- metadata +157 -27
- 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
@@ -0,0 +1,297 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'sprockets/autoload'
|
4
|
+
require 'sprockets/source_map_utils'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Sprockets
|
8
|
+
# Processor engine class for the SASS/SCSS compiler. Depends on the `sassc` gem.
|
9
|
+
#
|
10
|
+
# For more information see:
|
11
|
+
#
|
12
|
+
# https://github.com/sass/sassc-ruby
|
13
|
+
# https://github.com/sass/sassc-rails
|
14
|
+
#
|
15
|
+
class SasscProcessor
|
16
|
+
|
17
|
+
# Internal: Defines default sass syntax to use. Exposed so the ScsscProcessor
|
18
|
+
# may override it.
|
19
|
+
def self.syntax
|
20
|
+
:sass
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Return singleton instance with default options.
|
24
|
+
#
|
25
|
+
# Returns SasscProcessor object.
|
26
|
+
def self.instance
|
27
|
+
@instance ||= new
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.call(input)
|
31
|
+
instance.call(input)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.cache_key
|
35
|
+
instance.cache_key
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :cache_key
|
39
|
+
|
40
|
+
def initialize(options = {}, &block)
|
41
|
+
@cache_version = options[:cache_version]
|
42
|
+
@cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::SassC::VERSION}:#{@cache_version}".freeze
|
43
|
+
@importer_class = options[:importer]
|
44
|
+
@sass_config = options[:sass_config] || {}
|
45
|
+
@functions = Module.new do
|
46
|
+
include Functions
|
47
|
+
include options[:functions] if options[:functions]
|
48
|
+
class_eval(&block) if block_given?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def call(input)
|
53
|
+
context = input[:environment].context_class.new(input)
|
54
|
+
|
55
|
+
options = engine_options(input, context)
|
56
|
+
engine = Autoload::SassC::Engine.new(input[:data], options)
|
57
|
+
|
58
|
+
css = Utils.module_include(Autoload::SassC::Script::Functions, @functions) do
|
59
|
+
engine.render.sub(/^\n^\/\*# sourceMappingURL=.*\*\/$/m, '')
|
60
|
+
end
|
61
|
+
|
62
|
+
begin
|
63
|
+
map = SourceMapUtils.format_source_map(JSON.parse(engine.source_map), input)
|
64
|
+
map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
|
65
|
+
|
66
|
+
engine.dependencies.each do |dependency|
|
67
|
+
context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.filename)
|
68
|
+
end
|
69
|
+
rescue SassC::NotRenderedError
|
70
|
+
map = input[:metadata][:map]
|
71
|
+
end
|
72
|
+
|
73
|
+
context.metadata.merge(data: css, map: map)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def merge_options(options)
|
79
|
+
defaults = @sass_config.dup
|
80
|
+
|
81
|
+
if load_paths = defaults.delete(:load_paths)
|
82
|
+
options[:load_paths] += load_paths
|
83
|
+
end
|
84
|
+
|
85
|
+
options.merge!(defaults)
|
86
|
+
options
|
87
|
+
end
|
88
|
+
|
89
|
+
# Public: Functions injected into Sass context during Sprockets evaluation.
|
90
|
+
#
|
91
|
+
# This module may be extended to add global functionality to all Sprockets
|
92
|
+
# Sass environments. Though, scoping your functions to just your environment
|
93
|
+
# is preferred.
|
94
|
+
#
|
95
|
+
# module Sprockets::SasscProcessor::Functions
|
96
|
+
# def asset_path(path, options = {})
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
module Functions
|
101
|
+
# Public: Generate a url for asset path.
|
102
|
+
#
|
103
|
+
# Default implementation is deprecated. Currently defaults to
|
104
|
+
# Context#asset_path.
|
105
|
+
#
|
106
|
+
# Will raise NotImplementedError in the future. Users should provide their
|
107
|
+
# own base implementation.
|
108
|
+
#
|
109
|
+
# Returns a SassC::Script::Value::String.
|
110
|
+
def asset_path(path, options = {})
|
111
|
+
path = path.value
|
112
|
+
|
113
|
+
path, _, query, fragment = URI.split(path)[5..8]
|
114
|
+
path = sprockets_context.asset_path(path, options)
|
115
|
+
query = "?#{query}" if query
|
116
|
+
fragment = "##{fragment}" if fragment
|
117
|
+
|
118
|
+
Autoload::SassC::Script::Value::String.new("#{path}#{query}#{fragment}", :string)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Public: Generate a asset url() link.
|
122
|
+
#
|
123
|
+
# path - SassC::Script::Value::String URL path
|
124
|
+
#
|
125
|
+
# Returns a SassC::Script::Value::String.
|
126
|
+
def asset_url(path, options = {})
|
127
|
+
Autoload::SassC::Script::Value::String.new("url(#{asset_path(path, options).value})")
|
128
|
+
end
|
129
|
+
|
130
|
+
# Public: Generate url for image path.
|
131
|
+
#
|
132
|
+
# path - SassC::Script::Value::String URL path
|
133
|
+
#
|
134
|
+
# Returns a SassC::Script::Value::String.
|
135
|
+
def image_path(path)
|
136
|
+
asset_path(path, type: :image)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Public: Generate a image url() link.
|
140
|
+
#
|
141
|
+
# path - SassC::Script::Value::String URL path
|
142
|
+
#
|
143
|
+
# Returns a SassC::Script::Value::String.
|
144
|
+
def image_url(path)
|
145
|
+
asset_url(path, type: :image)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Public: Generate url for video path.
|
149
|
+
#
|
150
|
+
# path - SassC::Script::Value::String URL path
|
151
|
+
#
|
152
|
+
# Returns a SassC::Script::Value::String.
|
153
|
+
def video_path(path)
|
154
|
+
asset_path(path, type: :video)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Public: Generate a video url() link.
|
158
|
+
#
|
159
|
+
# path - SassC::Script::Value::String URL path
|
160
|
+
#
|
161
|
+
# Returns a SassC::Script::Value::String.
|
162
|
+
def video_url(path)
|
163
|
+
asset_url(path, type: :video)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Public: Generate url for audio path.
|
167
|
+
#
|
168
|
+
# path - SassC::Script::Value::String URL path
|
169
|
+
#
|
170
|
+
# Returns a SassC::Script::Value::String.
|
171
|
+
def audio_path(path)
|
172
|
+
asset_path(path, type: :audio)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Public: Generate a audio url() link.
|
176
|
+
#
|
177
|
+
# path - SassC::Script::Value::String URL path
|
178
|
+
#
|
179
|
+
# Returns a SassC::Script::Value::String.
|
180
|
+
def audio_url(path)
|
181
|
+
asset_url(path, type: :audio)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Public: Generate url for font path.
|
185
|
+
#
|
186
|
+
# path - SassC::Script::Value::String URL path
|
187
|
+
#
|
188
|
+
# Returns a SassC::Script::Value::String.
|
189
|
+
def font_path(path)
|
190
|
+
asset_path(path, type: :font)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Public: Generate a font url() link.
|
194
|
+
#
|
195
|
+
# path - SassC::Script::Value::String URL path
|
196
|
+
#
|
197
|
+
# Returns a SassC::Script::Value::String.
|
198
|
+
def font_url(path)
|
199
|
+
asset_url(path, type: :font)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Public: Generate url for javascript path.
|
203
|
+
#
|
204
|
+
# path - SassC::Script::Value::String URL path
|
205
|
+
#
|
206
|
+
# Returns a SassC::Script::Value::String.
|
207
|
+
def javascript_path(path)
|
208
|
+
asset_path(path, type: :javascript)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Public: Generate a javascript url() link.
|
212
|
+
#
|
213
|
+
# path - SassC::Script::Value::String URL path
|
214
|
+
#
|
215
|
+
# Returns a SassC::Script::Value::String.
|
216
|
+
def javascript_url(path)
|
217
|
+
asset_url(path, type: :javascript)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Public: Generate url for stylesheet path.
|
221
|
+
#
|
222
|
+
# path - SassC::Script::Value::String URL path
|
223
|
+
#
|
224
|
+
# Returns a SassC::Script::Value::String.
|
225
|
+
def stylesheet_path(path)
|
226
|
+
asset_path(path, type: :stylesheet)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Public: Generate a stylesheet url() link.
|
230
|
+
#
|
231
|
+
# path - SassC::Script::Value::String URL path
|
232
|
+
#
|
233
|
+
# Returns a SassC::Script::Value::String.
|
234
|
+
def stylesheet_url(path)
|
235
|
+
asset_url(path, type: :stylesheet)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Public: Generate a data URI for asset path.
|
239
|
+
#
|
240
|
+
# path - SassC::Script::Value::String logical asset path
|
241
|
+
#
|
242
|
+
# Returns a SassC::Script::Value::String.
|
243
|
+
def asset_data_url(path)
|
244
|
+
url = sprockets_context.asset_data_uri(path.value)
|
245
|
+
Autoload::SassC::Script::Value::String.new("url(" + url + ")")
|
246
|
+
end
|
247
|
+
|
248
|
+
protected
|
249
|
+
# Public: The Environment.
|
250
|
+
#
|
251
|
+
# Returns Sprockets::Environment.
|
252
|
+
def sprockets_environment
|
253
|
+
options[:sprockets][:environment]
|
254
|
+
end
|
255
|
+
|
256
|
+
# Public: Mutatable set of dependencies.
|
257
|
+
#
|
258
|
+
# Returns a Set.
|
259
|
+
def sprockets_dependencies
|
260
|
+
options[:sprockets][:dependencies]
|
261
|
+
end
|
262
|
+
|
263
|
+
# Deprecated: Get the Context instance. Use APIs on
|
264
|
+
# sprockets_environment or sprockets_dependencies directly.
|
265
|
+
#
|
266
|
+
# Returns a Context instance.
|
267
|
+
def sprockets_context
|
268
|
+
options[:sprockets][:context]
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
def engine_options(input, context)
|
274
|
+
merge_options({
|
275
|
+
filename: input[:filename],
|
276
|
+
syntax: self.class.syntax,
|
277
|
+
load_paths: input[:environment].paths,
|
278
|
+
importer: @importer_class,
|
279
|
+
source_map_contents: false,
|
280
|
+
source_map_file: "#{input[:filename]}.map",
|
281
|
+
omit_source_map_url: true,
|
282
|
+
sprockets: {
|
283
|
+
context: context,
|
284
|
+
environment: input[:environment],
|
285
|
+
dependencies: context.metadata[:dependencies]
|
286
|
+
}
|
287
|
+
})
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
class ScsscProcessor < SasscProcessor
|
293
|
+
def self.syntax
|
294
|
+
:scss
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
data/lib/sprockets/server.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'set'
|
1
3
|
require 'time'
|
2
4
|
require 'rack/utils'
|
3
5
|
|
@@ -6,6 +8,9 @@ module Sprockets
|
|
6
8
|
# `CachedEnvironment` that provides a Rack compatible `call`
|
7
9
|
# interface and url generation helpers.
|
8
10
|
module Server
|
11
|
+
# Supported HTTP request methods.
|
12
|
+
ALLOWED_REQUEST_METHODS = ['GET', 'HEAD'].to_set.freeze
|
13
|
+
|
9
14
|
# `call` implements the Rack 1.x specification which accepts an
|
10
15
|
# `env` Hash and returns a three item tuple with the status code,
|
11
16
|
# headers, and body.
|
@@ -23,14 +28,19 @@ module Sprockets
|
|
23
28
|
start_time = Time.now.to_f
|
24
29
|
time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
|
25
30
|
|
26
|
-
|
31
|
+
unless ALLOWED_REQUEST_METHODS.include? env['REQUEST_METHOD']
|
27
32
|
return method_not_allowed_response
|
28
33
|
end
|
29
34
|
|
30
35
|
msg = "Served asset #{env['PATH_INFO']} -"
|
31
36
|
|
32
37
|
# Extract the path from everything after the leading slash
|
33
|
-
|
38
|
+
full_path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
|
39
|
+
path = full_path
|
40
|
+
|
41
|
+
unless path.valid_encoding?
|
42
|
+
return bad_request_response(env)
|
43
|
+
end
|
34
44
|
|
35
45
|
# Strip fingerprint
|
36
46
|
if fingerprint = path_fingerprint(path)
|
@@ -39,30 +49,29 @@ module Sprockets
|
|
39
49
|
|
40
50
|
# URLs containing a `".."` are rejected for security reasons.
|
41
51
|
if forbidden_request?(path)
|
42
|
-
return forbidden_response
|
43
|
-
end
|
44
|
-
|
45
|
-
# Look up the asset.
|
46
|
-
options = {}
|
47
|
-
options[:pipeline] = :self if body_only?(env)
|
48
|
-
|
49
|
-
asset = find_asset(path, options)
|
50
|
-
|
51
|
-
# 2.x/3.x compatibility hack. Just ignore fingerprints on ?body=1 requests.
|
52
|
-
# 3.x/4.x prefers strong validation of fingerprint to body contents, but
|
53
|
-
# 2.x just ignored it.
|
54
|
-
if asset && parse_asset_uri(asset.uri)[1][:pipeline] == "self"
|
55
|
-
fingerprint = nil
|
52
|
+
return forbidden_response(env)
|
56
53
|
end
|
57
54
|
|
58
55
|
if fingerprint
|
59
56
|
if_match = fingerprint
|
60
57
|
elsif env['HTTP_IF_MATCH']
|
61
|
-
if_match = env['HTTP_IF_MATCH'][
|
58
|
+
if_match = env['HTTP_IF_MATCH'][/"(\w+)"$/, 1]
|
62
59
|
end
|
63
60
|
|
64
61
|
if env['HTTP_IF_NONE_MATCH']
|
65
|
-
if_none_match = env['HTTP_IF_NONE_MATCH'][
|
62
|
+
if_none_match = env['HTTP_IF_NONE_MATCH'][/"(\w+)"$/, 1]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Look up the asset.
|
66
|
+
asset = find_asset(path)
|
67
|
+
|
68
|
+
# Fallback to looking up the asset with the full path.
|
69
|
+
# This will make assets that are hashed with webpack or
|
70
|
+
# other js bundlers work consistently between production
|
71
|
+
# and development pipelines.
|
72
|
+
if asset.nil? && (asset = find_asset(full_path))
|
73
|
+
if_match = asset.etag if fingerprint
|
74
|
+
fingerprint = asset.etag
|
66
75
|
end
|
67
76
|
|
68
77
|
if asset.nil?
|
@@ -86,10 +95,10 @@ module Sprockets
|
|
86
95
|
not_modified_response(env, if_none_match)
|
87
96
|
when :not_found
|
88
97
|
logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
|
89
|
-
not_found_response
|
98
|
+
not_found_response(env)
|
90
99
|
when :precondition_failed
|
91
100
|
logger.info "#{msg} 412 Precondition Failed (#{time_elapsed.call}ms)"
|
92
|
-
precondition_failed_response
|
101
|
+
precondition_failed_response(env)
|
93
102
|
end
|
94
103
|
rescue Exception => e
|
95
104
|
logger.error "Error compiling asset #{path}:"
|
@@ -115,12 +124,20 @@ module Sprockets
|
|
115
124
|
#
|
116
125
|
# http://example.org/assets/../../../etc/passwd
|
117
126
|
#
|
118
|
-
path.include?("..") || absolute_path?(path)
|
127
|
+
path.include?("..") || absolute_path?(path) || path.include?("://")
|
128
|
+
end
|
129
|
+
|
130
|
+
def head_request?(env)
|
131
|
+
env['REQUEST_METHOD'] == 'HEAD'
|
119
132
|
end
|
120
133
|
|
121
134
|
# Returns a 200 OK response tuple
|
122
135
|
def ok_response(asset, env)
|
123
|
-
|
136
|
+
if head_request?(env)
|
137
|
+
[ 200, headers(env, asset, 0), [] ]
|
138
|
+
else
|
139
|
+
[ 200, headers(env, asset, asset.length), asset ]
|
140
|
+
end
|
124
141
|
end
|
125
142
|
|
126
143
|
# Returns a 304 Not Modified response tuple
|
@@ -128,22 +145,43 @@ module Sprockets
|
|
128
145
|
[ 304, cache_headers(env, etag), [] ]
|
129
146
|
end
|
130
147
|
|
148
|
+
# Returns a 400 Forbidden response tuple
|
149
|
+
def bad_request_response(env)
|
150
|
+
if head_request?(env)
|
151
|
+
[ 400, { "content-type" => "text/plain", "content-length" => "0" }, [] ]
|
152
|
+
else
|
153
|
+
[ 400, { "content-type" => "text/plain", "content-length" => "11" }, [ "Bad Request" ] ]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
131
157
|
# Returns a 403 Forbidden response tuple
|
132
|
-
def forbidden_response
|
133
|
-
|
158
|
+
def forbidden_response(env)
|
159
|
+
if head_request?(env)
|
160
|
+
[ 403, { "content-type" => "text/plain", "content-length" => "0" }, [] ]
|
161
|
+
else
|
162
|
+
[ 403, { "content-type" => "text/plain", "content-length" => "9" }, [ "Forbidden" ] ]
|
163
|
+
end
|
134
164
|
end
|
135
165
|
|
136
166
|
# Returns a 404 Not Found response tuple
|
137
|
-
def not_found_response
|
138
|
-
|
167
|
+
def not_found_response(env)
|
168
|
+
if head_request?(env)
|
169
|
+
[ 404, { "content-type" => "text/plain", "content-length" => "0", "x-cascade" => "pass" }, [] ]
|
170
|
+
else
|
171
|
+
[ 404, { "content-type" => "text/plain", "content-length" => "9", "x-cascade" => "pass" }, [ "Not found" ] ]
|
172
|
+
end
|
139
173
|
end
|
140
174
|
|
141
175
|
def method_not_allowed_response
|
142
|
-
[ 405, { "
|
176
|
+
[ 405, { "content-type" => "text/plain", "content-length" => "18" }, [ "Method Not Allowed" ] ]
|
143
177
|
end
|
144
178
|
|
145
|
-
def precondition_failed_response
|
146
|
-
|
179
|
+
def precondition_failed_response(env)
|
180
|
+
if head_request?(env)
|
181
|
+
[ 412, { "content-type" => "text/plain", "content-length" => "0", "x-cascade" => "pass" }, [] ]
|
182
|
+
else
|
183
|
+
[ 412, { "content-type" => "text/plain", "content-length" => "19", "x-cascade" => "pass" }, [ "Precondition Failed" ] ]
|
184
|
+
end
|
147
185
|
end
|
148
186
|
|
149
187
|
# Returns a JavaScript response that re-throws a Ruby exception
|
@@ -151,7 +189,7 @@ module Sprockets
|
|
151
189
|
def javascript_exception_response(exception)
|
152
190
|
err = "#{exception.class.name}: #{exception.message}\n (in #{exception.backtrace[0]})"
|
153
191
|
body = "throw Error(#{err.inspect})"
|
154
|
-
[ 200, { "
|
192
|
+
[ 200, { "content-type" => "application/javascript", "content-length" => body.bytesize.to_s }, [ body ] ]
|
155
193
|
end
|
156
194
|
|
157
195
|
# Returns a CSS response that hides all elements on the page and
|
@@ -204,7 +242,7 @@ module Sprockets
|
|
204
242
|
}
|
205
243
|
CSS
|
206
244
|
|
207
|
-
[ 200, { "
|
245
|
+
[ 200, { "content-type" => "text/css; charset=utf-8", "content-length" => body.bytesize.to_s }, [ body ] ]
|
208
246
|
end
|
209
247
|
|
210
248
|
# Escape special characters for use inside a CSS content("...") string
|
@@ -216,27 +254,22 @@ module Sprockets
|
|
216
254
|
gsub('/', '\\\\002f ')
|
217
255
|
end
|
218
256
|
|
219
|
-
# Test if `?body=1` or `body=true` query param is set
|
220
|
-
def body_only?(env)
|
221
|
-
env["QUERY_STRING"].to_s =~ /body=(1|t)/
|
222
|
-
end
|
223
|
-
|
224
257
|
def cache_headers(env, etag)
|
225
258
|
headers = {}
|
226
259
|
|
227
260
|
# Set caching headers
|
228
|
-
headers["
|
229
|
-
headers["
|
261
|
+
headers["cache-control"] = +"public"
|
262
|
+
headers["etag"] = %("#{etag}")
|
230
263
|
|
231
264
|
# If the request url contains a fingerprint, set a long
|
232
265
|
# expires on the response
|
233
266
|
if path_fingerprint(env["PATH_INFO"])
|
234
|
-
headers["
|
267
|
+
headers["cache-control"] << ", max-age=31536000, immutable"
|
235
268
|
|
236
269
|
# Otherwise set `must-revalidate` since the asset could be modified.
|
237
270
|
else
|
238
|
-
headers["
|
239
|
-
headers["
|
271
|
+
headers["cache-control"] << ", must-revalidate"
|
272
|
+
headers["vary"] = "Accept-Encoding"
|
240
273
|
end
|
241
274
|
|
242
275
|
headers
|
@@ -246,7 +279,7 @@ module Sprockets
|
|
246
279
|
headers = {}
|
247
280
|
|
248
281
|
# Set content length header
|
249
|
-
headers["
|
282
|
+
headers["content-length"] = length.to_s
|
250
283
|
|
251
284
|
# Set content type header
|
252
285
|
if type = asset.content_type
|
@@ -254,7 +287,7 @@ module Sprockets
|
|
254
287
|
if type.start_with?("text/") && asset.charset
|
255
288
|
type += "; charset=#{asset.charset}"
|
256
289
|
end
|
257
|
-
headers["
|
290
|
+
headers["content-type"] = type
|
258
291
|
end
|
259
292
|
|
260
293
|
headers.merge(cache_headers(env, asset.etag))
|
@@ -266,7 +299,7 @@ module Sprockets
|
|
266
299
|
# # => "0aa2105d29558f3eb790d411d7d8fb66"
|
267
300
|
#
|
268
301
|
def path_fingerprint(path)
|
269
|
-
path[/-([0-9a-
|
302
|
+
path[/-([0-9a-zA-Z]{7,128})\.[^.]+\z/, 1]
|
270
303
|
end
|
271
304
|
end
|
272
305
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
|
6
|
+
# The purpose of this class is to generate a source map file
|
7
|
+
# that can be read and understood by browsers.
|
8
|
+
#
|
9
|
+
# When a file is passed in it will have a `application/js-sourcemap+json`
|
10
|
+
# or `application/css-sourcemap+json` mime type. The filename will be
|
11
|
+
# match the original asset. The original asset is loaded. As it
|
12
|
+
# gets processed by Sprockets it will acquire all information
|
13
|
+
# needed to build a source map file in the `asset.to_hash[:metadata][:map]`
|
14
|
+
# key.
|
15
|
+
#
|
16
|
+
# The output is an asset with a properly formatted source map file:
|
17
|
+
#
|
18
|
+
# {
|
19
|
+
# "version": 3,
|
20
|
+
# "sources": ["foo.js"],
|
21
|
+
# "names": [ ],
|
22
|
+
# "mappings": "AAAA,GAAIA"
|
23
|
+
# }
|
24
|
+
#
|
25
|
+
class SourceMapProcessor
|
26
|
+
def self.call(input)
|
27
|
+
links = Set.new(input[:metadata][:links])
|
28
|
+
env = input[:environment]
|
29
|
+
|
30
|
+
uri, _ = env.resolve!(input[:filename], accept: self.original_content_type(input[:content_type]))
|
31
|
+
asset = env.load(uri)
|
32
|
+
map = asset.metadata[:map]
|
33
|
+
|
34
|
+
# TODO: Because of the default piplene hack we have to apply dependencies
|
35
|
+
# from compiled asset to the source map, otherwise the source map cache
|
36
|
+
# will never detect the changes from directives
|
37
|
+
dependencies = Set.new(input[:metadata][:dependencies])
|
38
|
+
dependencies.merge(asset.metadata[:dependencies])
|
39
|
+
|
40
|
+
map["file"] = PathUtils.split_subpath(input[:load_path], input[:filename])
|
41
|
+
sources = map["sections"] ? map["sections"].map { |s| s["map"]["sources"] }.flatten : map["sources"]
|
42
|
+
|
43
|
+
sources.each do |source|
|
44
|
+
source = PathUtils.join(File.dirname(map["file"]), source)
|
45
|
+
uri, _ = env.resolve!(source)
|
46
|
+
links << uri
|
47
|
+
end
|
48
|
+
|
49
|
+
json = JSON.generate(map)
|
50
|
+
|
51
|
+
{ data: json, links: links, dependencies: dependencies }
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.original_content_type(source_map_content_type, error_when_not_found: true)
|
55
|
+
case source_map_content_type
|
56
|
+
when "application/js-sourcemap+json"
|
57
|
+
"application/javascript"
|
58
|
+
when "application/css-sourcemap+json"
|
59
|
+
"text/css"
|
60
|
+
else
|
61
|
+
fail(source_map_content_type) if error_when_not_found
|
62
|
+
source_map_content_type
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|