sprockets 3.0.3 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +101 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +531 -276
  5. data/bin/sprockets +12 -7
  6. data/lib/rake/sprocketstask.rb +9 -4
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +41 -28
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +1 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  12. data/lib/sprockets/autoload/eco.rb +1 -0
  13. data/lib/sprockets/autoload/ejs.rb +1 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +1 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +1 -0
  18. data/lib/sprockets/autoload/yui.rb +1 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +5 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +61 -13
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +32 -7
  26. data/lib/sprockets/cache/memory_store.rb +28 -10
  27. data/lib/sprockets/cache/null_store.rb +8 -0
  28. data/lib/sprockets/cache.rb +43 -6
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +6 -11
  31. data/lib/sprockets/coffee_script_processor.rb +20 -6
  32. data/lib/sprockets/compressing.rb +62 -2
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +10 -9
  36. data/lib/sprockets/digest_utils.rb +103 -62
  37. data/lib/sprockets/directive_processor.rb +64 -36
  38. data/lib/sprockets/eco_processor.rb +4 -3
  39. data/lib/sprockets/ejs_processor.rb +4 -3
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +34 -21
  43. data/lib/sprockets/errors.rb +1 -0
  44. data/lib/sprockets/exporters/base.rb +71 -0
  45. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  46. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  47. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  48. data/lib/sprockets/exporting.rb +73 -0
  49. data/lib/sprockets/file_reader.rb +1 -0
  50. data/lib/sprockets/http_utils.rb +25 -7
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +11 -10
  53. data/lib/sprockets/loader.rb +244 -62
  54. data/lib/sprockets/manifest.rb +100 -46
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -42
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +3 -11
  59. data/lib/sprockets/path_digest_utils.rb +2 -1
  60. data/lib/sprockets/path_utils.rb +107 -22
  61. data/lib/sprockets/paths.rb +1 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +32 -52
  64. data/lib/sprockets/processor_utils.rb +38 -39
  65. data/lib/sprockets/resolve.rb +177 -97
  66. data/lib/sprockets/sass_cache_store.rb +1 -0
  67. data/lib/sprockets/sass_compressor.rb +21 -17
  68. data/lib/sprockets/sass_functions.rb +1 -0
  69. data/lib/sprockets/sass_importer.rb +1 -0
  70. data/lib/sprockets/sass_processor.rb +46 -18
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +77 -44
  74. data/lib/sprockets/source_map_processor.rb +66 -0
  75. data/lib/sprockets/source_map_utils.rb +483 -0
  76. data/lib/sprockets/transformers.rb +63 -35
  77. data/lib/sprockets/uglifier_compressor.rb +23 -20
  78. data/lib/sprockets/unloaded_asset.rb +139 -0
  79. data/lib/sprockets/uri_tar.rb +99 -0
  80. data/lib/sprockets/uri_utils.rb +14 -14
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +63 -71
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +5 -14
  85. data/lib/sprockets.rb +105 -33
  86. metadata +157 -27
  87. data/lib/sprockets/coffee_script_template.rb +0 -6
  88. data/lib/sprockets/eco_template.rb +0 -6
  89. data/lib/sprockets/ejs_template.rb +0 -6
  90. data/lib/sprockets/engines.rb +0 -81
  91. data/lib/sprockets/erb_template.rb +0 -6
  92. data/lib/sprockets/legacy.rb +0 -314
  93. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  94. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  95. 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
@@ -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
- if env['REQUEST_METHOD'] != 'GET'
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
- path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
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'][/^"(\w+)"$/, 1]
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'][/^"(\w+)"$/, 1]
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
- [ 200, headers(env, asset, asset.length), asset ]
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
- [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
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
- [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
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, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ]
176
+ [ 405, { "content-type" => "text/plain", "content-length" => "18" }, [ "Method Not Allowed" ] ]
143
177
  end
144
178
 
145
- def precondition_failed_response
146
- [ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
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, { "Content-Type" => "application/javascript", "Content-Length" => body.bytesize.to_s }, [ body ] ]
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, { "Content-Type" => "text/css; charset=utf-8", "Content-Length" => body.bytesize.to_s }, [ body ] ]
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["Cache-Control"] = "public"
229
- headers["ETag"] = %("#{etag}")
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["Cache-Control"] << ", max-age=31536000"
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["Cache-Control"] << ", must-revalidate"
239
- headers["Vary"] = "Accept-Encoding"
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["Content-Length"] = length.to_s
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["Content-Type"] = type
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-f]{7,128})\.[^.]+\z/, 1]
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