sprockets 3.0.0 → 4.0.2

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 +76 -0
  3. data/README.md +426 -404
  4. data/bin/sprockets +12 -7
  5. data/lib/rake/sprocketstask.rb +3 -2
  6. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  7. data/lib/sprockets/asset.rb +33 -24
  8. data/lib/sprockets/autoload/babel.rb +8 -0
  9. data/lib/sprockets/autoload/closure.rb +1 -0
  10. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  11. data/lib/sprockets/autoload/eco.rb +1 -0
  12. data/lib/sprockets/autoload/ejs.rb +1 -0
  13. data/lib/sprockets/autoload/jsminc.rb +8 -0
  14. data/lib/sprockets/autoload/sass.rb +1 -0
  15. data/lib/sprockets/autoload/sassc.rb +8 -0
  16. data/lib/sprockets/autoload/uglifier.rb +1 -0
  17. data/lib/sprockets/autoload/yui.rb +1 -0
  18. data/lib/sprockets/autoload/zopfli.rb +7 -0
  19. data/lib/sprockets/autoload.rb +5 -0
  20. data/lib/sprockets/babel_processor.rb +66 -0
  21. data/lib/sprockets/base.rb +61 -13
  22. data/lib/sprockets/bower.rb +5 -2
  23. data/lib/sprockets/bundle.rb +44 -4
  24. data/lib/sprockets/cache/file_store.rb +32 -7
  25. data/lib/sprockets/cache/memory_store.rb +9 -0
  26. data/lib/sprockets/cache/null_store.rb +8 -0
  27. data/lib/sprockets/cache.rb +42 -5
  28. data/lib/sprockets/cached_environment.rb +14 -19
  29. data/lib/sprockets/closure_compressor.rb +6 -11
  30. data/lib/sprockets/coffee_script_processor.rb +19 -5
  31. data/lib/sprockets/compressing.rb +62 -2
  32. data/lib/sprockets/configuration.rb +3 -7
  33. data/lib/sprockets/context.rb +98 -23
  34. data/lib/sprockets/dependencies.rb +9 -8
  35. data/lib/sprockets/digest_utils.rb +104 -60
  36. data/lib/sprockets/directive_processor.rb +45 -35
  37. data/lib/sprockets/eco_processor.rb +3 -2
  38. data/lib/sprockets/ejs_processor.rb +3 -2
  39. data/lib/sprockets/encoding_utils.rb +8 -4
  40. data/lib/sprockets/environment.rb +9 -4
  41. data/lib/sprockets/erb_processor.rb +28 -21
  42. data/lib/sprockets/errors.rb +1 -1
  43. data/lib/sprockets/exporters/base.rb +71 -0
  44. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  45. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  46. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  47. data/lib/sprockets/exporting.rb +73 -0
  48. data/lib/sprockets/file_reader.rb +1 -0
  49. data/lib/sprockets/http_utils.rb +26 -6
  50. data/lib/sprockets/jsminc_compressor.rb +32 -0
  51. data/lib/sprockets/jst_processor.rb +11 -10
  52. data/lib/sprockets/loader.rb +239 -70
  53. data/lib/sprockets/manifest.rb +97 -44
  54. data/lib/sprockets/manifest_utils.rb +9 -6
  55. data/lib/sprockets/mime.rb +8 -42
  56. data/lib/sprockets/npm.rb +52 -0
  57. data/lib/sprockets/path_dependency_utils.rb +3 -11
  58. data/lib/sprockets/path_digest_utils.rb +2 -1
  59. data/lib/sprockets/path_utils.rb +106 -21
  60. data/lib/sprockets/paths.rb +1 -0
  61. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  62. data/lib/sprockets/processing.rb +31 -51
  63. data/lib/sprockets/processor_utils.rb +81 -15
  64. data/lib/sprockets/resolve.rb +182 -95
  65. data/lib/sprockets/sass_cache_store.rb +1 -0
  66. data/lib/sprockets/sass_compressor.rb +21 -17
  67. data/lib/sprockets/sass_functions.rb +1 -0
  68. data/lib/sprockets/sass_importer.rb +1 -0
  69. data/lib/sprockets/sass_processor.rb +45 -17
  70. data/lib/sprockets/sassc_compressor.rb +56 -0
  71. data/lib/sprockets/sassc_processor.rb +297 -0
  72. data/lib/sprockets/server.rb +57 -34
  73. data/lib/sprockets/source_map_processor.rb +66 -0
  74. data/lib/sprockets/source_map_utils.rb +483 -0
  75. data/lib/sprockets/transformers.rb +63 -35
  76. data/lib/sprockets/uglifier_compressor.rb +23 -20
  77. data/lib/sprockets/unloaded_asset.rb +139 -0
  78. data/lib/sprockets/uri_tar.rb +99 -0
  79. data/lib/sprockets/uri_utils.rb +15 -14
  80. data/lib/sprockets/utils/gzip.rb +99 -0
  81. data/lib/sprockets/utils.rb +43 -59
  82. data/lib/sprockets/version.rb +2 -1
  83. data/lib/sprockets/yui_compressor.rb +5 -14
  84. data/lib/sprockets.rb +103 -33
  85. metadata +151 -22
  86. data/LICENSE +0 -21
  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,56 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/source_map_utils'
4
+
5
+ module Sprockets
6
+ # Public: Sass CSS minifier.
7
+ #
8
+ # To accept the default options
9
+ #
10
+ # environment.register_bundle_processor 'text/css',
11
+ # Sprockets::SasscCompressor
12
+ #
13
+ # Or to pass options to the Sass::Engine class.
14
+ #
15
+ # environment.register_bundle_processor 'text/css',
16
+ # Sprockets::SasscCompressor.new({ ... })
17
+ #
18
+ class SasscCompressor
19
+ # Public: Return singleton instance with default options.
20
+ #
21
+ # Returns SasscCompressor object.
22
+ def self.instance
23
+ @instance ||= new
24
+ end
25
+
26
+ def self.call(input)
27
+ instance.call(input)
28
+ end
29
+
30
+ def initialize(options = {})
31
+ @options = {
32
+ syntax: :scss,
33
+ style: :compressed,
34
+ source_map_contents: false,
35
+ omit_source_map_url: true,
36
+ }.merge(options).freeze
37
+ end
38
+
39
+ def call(input)
40
+ # SassC requires the template to be modifiable
41
+ input_data = input[:data].frozen? ? input[:data].dup : input[:data]
42
+ engine = Autoload::SassC::Engine.new(input_data, @options.merge(filename: input[:filename], source_map_file: "#{input[:filename]}.map"))
43
+
44
+ css = engine.render.sub(/^\n^\/\*# sourceMappingURL=.*\*\/$/m, '')
45
+
46
+ begin
47
+ map = SourceMapUtils.format_source_map(JSON.parse(engine.source_map), input)
48
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
49
+ rescue SassC::NotRenderedError
50
+ map = input[:metadata][:map]
51
+ end
52
+
53
+ { data: css, map: map }
54
+ end
55
+ end
56
+ end
@@ -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 infomation 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,7 +28,7 @@ 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
 
@@ -32,6 +37,10 @@ module Sprockets
32
37
  # Extract the path from everything after the leading slash
33
38
  path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
34
39
 
40
+ unless path.valid_encoding?
41
+ return bad_request_response(env)
42
+ end
43
+
35
44
  # Strip fingerprint
36
45
  if fingerprint = path_fingerprint(path)
37
46
  path = path.sub("-#{fingerprint}", '')
@@ -39,32 +48,22 @@ module Sprockets
39
48
 
40
49
  # URLs containing a `".."` are rejected for security reasons.
41
50
  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
51
+ return forbidden_response(env)
56
52
  end
57
53
 
58
54
  if fingerprint
59
55
  if_match = fingerprint
60
56
  elsif env['HTTP_IF_MATCH']
61
- if_match = env['HTTP_IF_MATCH'][/^"(\w+)"$/, 1]
57
+ if_match = env['HTTP_IF_MATCH'][/"(\w+)"$/, 1]
62
58
  end
63
59
 
64
60
  if env['HTTP_IF_NONE_MATCH']
65
- if_none_match = env['HTTP_IF_NONE_MATCH'][/^"(\w+)"$/, 1]
61
+ if_none_match = env['HTTP_IF_NONE_MATCH'][/"(\w+)"$/, 1]
66
62
  end
67
63
 
64
+ # Look up the asset.
65
+ asset = find_asset(path)
66
+
68
67
  if asset.nil?
69
68
  status = :not_found
70
69
  elsif fingerprint && asset.etag != fingerprint
@@ -86,10 +85,10 @@ module Sprockets
86
85
  not_modified_response(env, if_none_match)
87
86
  when :not_found
88
87
  logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
89
- not_found_response
88
+ not_found_response(env)
90
89
  when :precondition_failed
91
90
  logger.info "#{msg} 412 Precondition Failed (#{time_elapsed.call}ms)"
92
- precondition_failed_response
91
+ precondition_failed_response(env)
93
92
  end
94
93
  rescue Exception => e
95
94
  logger.error "Error compiling asset #{path}:"
@@ -115,12 +114,20 @@ module Sprockets
115
114
  #
116
115
  # http://example.org/assets/../../../etc/passwd
117
116
  #
118
- path.include?("..") || absolute_path?(path)
117
+ path.include?("..") || absolute_path?(path) || path.include?("://")
118
+ end
119
+
120
+ def head_request?(env)
121
+ env['REQUEST_METHOD'] == 'HEAD'
119
122
  end
120
123
 
121
124
  # Returns a 200 OK response tuple
122
125
  def ok_response(asset, env)
123
- [ 200, headers(env, asset, asset.length), asset ]
126
+ if head_request?(env)
127
+ [ 200, headers(env, asset, 0), [] ]
128
+ else
129
+ [ 200, headers(env, asset, asset.length), asset ]
130
+ end
124
131
  end
125
132
 
126
133
  # Returns a 304 Not Modified response tuple
@@ -128,22 +135,43 @@ module Sprockets
128
135
  [ 304, cache_headers(env, etag), [] ]
129
136
  end
130
137
 
138
+ # Returns a 400 Forbidden response tuple
139
+ def bad_request_response(env)
140
+ if head_request?(env)
141
+ [ 400, { "Content-Type" => "text/plain", "Content-Length" => "0" }, [] ]
142
+ else
143
+ [ 400, { "Content-Type" => "text/plain", "Content-Length" => "11" }, [ "Bad Request" ] ]
144
+ end
145
+ end
146
+
131
147
  # Returns a 403 Forbidden response tuple
132
- def forbidden_response
133
- [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
148
+ def forbidden_response(env)
149
+ if head_request?(env)
150
+ [ 403, { "Content-Type" => "text/plain", "Content-Length" => "0" }, [] ]
151
+ else
152
+ [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
153
+ end
134
154
  end
135
155
 
136
156
  # 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" ] ]
157
+ def not_found_response(env)
158
+ if head_request?(env)
159
+ [ 404, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
160
+ else
161
+ [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
162
+ end
139
163
  end
140
164
 
141
165
  def method_not_allowed_response
142
166
  [ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ]
143
167
  end
144
168
 
145
- def precondition_failed_response
146
- [ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
169
+ def precondition_failed_response(env)
170
+ if head_request?(env)
171
+ [ 412, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
172
+ else
173
+ [ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
174
+ end
147
175
  end
148
176
 
149
177
  # Returns a JavaScript response that re-throws a Ruby exception
@@ -216,22 +244,17 @@ module Sprockets
216
244
  gsub('/', '\\\\002f ')
217
245
  end
218
246
 
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
247
  def cache_headers(env, etag)
225
248
  headers = {}
226
249
 
227
250
  # Set caching headers
228
- headers["Cache-Control"] = "public"
251
+ headers["Cache-Control"] = +"public"
229
252
  headers["ETag"] = %("#{etag}")
230
253
 
231
254
  # If the request url contains a fingerprint, set a long
232
255
  # expires on the response
233
256
  if path_fingerprint(env["PATH_INFO"])
234
- headers["Cache-Control"] << ", max-age=31536000"
257
+ headers["Cache-Control"] << ", max-age=31536000, immutable"
235
258
 
236
259
  # Otherwise set `must-revalidate` since the asset could be modified.
237
260
  else
@@ -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 aquire 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