sprockets 3.7.3 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -261
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +527 -320
  5. data/bin/sprockets +11 -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 +39 -27
  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 +49 -12
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +25 -3
  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 +37 -2
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +1 -0
  31. data/lib/sprockets/coffee_script_processor.rb +19 -5
  32. data/lib/sprockets/compressing.rb +43 -3
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +2 -1
  36. data/lib/sprockets/digest_utils.rb +35 -18
  37. data/lib/sprockets/directive_processor.rb +64 -38
  38. data/lib/sprockets/eco_processor.rb +2 -1
  39. data/lib/sprockets/ejs_processor.rb +2 -1
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +33 -32
  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 +91 -69
  54. data/lib/sprockets/manifest.rb +67 -64
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -62
  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 +88 -8
  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 -62
  64. data/lib/sprockets/processor_utils.rb +28 -38
  65. data/lib/sprockets/resolve.rb +177 -93
  66. data/lib/sprockets/sass_cache_store.rb +2 -6
  67. data/lib/sprockets/sass_compressor.rb +13 -1
  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 +31 -10
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +63 -40
  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 +21 -11
  78. data/lib/sprockets/unloaded_asset.rb +13 -11
  79. data/lib/sprockets/uri_tar.rb +1 -0
  80. data/lib/sprockets/uri_utils.rb +12 -12
  81. data/lib/sprockets/utils/gzip.rb +46 -14
  82. data/lib/sprockets/utils.rb +64 -90
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +1 -0
  85. data/lib/sprockets.rb +102 -39
  86. metadata +138 -46
  87. data/lib/sprockets/coffee_script_template.rb +0 -17
  88. data/lib/sprockets/deprecation.rb +0 -90
  89. data/lib/sprockets/eco_template.rb +0 -17
  90. data/lib/sprockets/ejs_template.rb +0 -17
  91. data/lib/sprockets/engines.rb +0 -92
  92. data/lib/sprockets/erb_template.rb +0 -11
  93. data/lib/sprockets/legacy.rb +0 -330
  94. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  95. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  96. data/lib/sprockets/sass_template.rb +0 -19
@@ -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,11 +1,26 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
1
3
  require 'time'
2
- require 'rack/utils'
4
+ require 'rack'
3
5
 
4
6
  module Sprockets
5
7
  # `Server` is a concern mixed into `Environment` and
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
+
14
+ # :stopdoc:
15
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
16
+ X_CASCADE = "X-Cascade"
17
+ VARY = "Vary"
18
+ else
19
+ X_CASCADE = "x-cascade"
20
+ VARY = "vary"
21
+ end
22
+ # :startdoc:
23
+
9
24
  # `call` implements the Rack 1.x specification which accepts an
10
25
  # `env` Hash and returns a three item tuple with the status code,
11
26
  # headers, and body.
@@ -23,14 +38,19 @@ module Sprockets
23
38
  start_time = Time.now.to_f
24
39
  time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
25
40
 
26
- if !['GET', 'HEAD'].include?(env['REQUEST_METHOD'])
41
+ unless ALLOWED_REQUEST_METHODS.include? env['REQUEST_METHOD']
27
42
  return method_not_allowed_response
28
43
  end
29
44
 
30
45
  msg = "Served asset #{env['PATH_INFO']} -"
31
46
 
32
47
  # Extract the path from everything after the leading slash
33
- path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
48
+ full_path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
49
+ path = full_path
50
+
51
+ unless path.valid_encoding?
52
+ return bad_request_response(env)
53
+ end
34
54
 
35
55
  # Strip fingerprint
36
56
  if fingerprint = path_fingerprint(path)
@@ -42,27 +62,26 @@ module Sprockets
42
62
  return forbidden_response(env)
43
63
  end
44
64
 
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
56
- end
57
-
58
65
  if fingerprint
59
66
  if_match = fingerprint
60
67
  elsif env['HTTP_IF_MATCH']
61
- if_match = env['HTTP_IF_MATCH'][/^"(\w+)"$/, 1]
68
+ if_match = env['HTTP_IF_MATCH'][/"(\w+)"$/, 1]
62
69
  end
63
70
 
64
71
  if env['HTTP_IF_NONE_MATCH']
65
- if_none_match = env['HTTP_IF_NONE_MATCH'][/^"(\w+)"$/, 1]
72
+ if_none_match = env['HTTP_IF_NONE_MATCH'][/"(\w+)"$/, 1]
73
+ end
74
+
75
+ # Look up the asset.
76
+ asset = find_asset(path)
77
+
78
+ # Fallback to looking up the asset with the full path.
79
+ # This will make assets that are hashed with webpack or
80
+ # other js bundlers work consistently between production
81
+ # and development pipelines.
82
+ if asset.nil? && (asset = find_asset(full_path))
83
+ if_match = asset.etag if fingerprint
84
+ fingerprint = asset.etag
66
85
  end
67
86
 
68
87
  if asset.nil?
@@ -136,33 +155,42 @@ module Sprockets
136
155
  [ 304, cache_headers(env, etag), [] ]
137
156
  end
138
157
 
158
+ # Returns a 400 Forbidden response tuple
159
+ def bad_request_response(env)
160
+ if head_request?(env)
161
+ [ 400, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "0" }, [] ]
162
+ else
163
+ [ 400, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "11" }, [ "Bad Request" ] ]
164
+ end
165
+ end
166
+
139
167
  # Returns a 403 Forbidden response tuple
140
168
  def forbidden_response(env)
141
169
  if head_request?(env)
142
- [ 403, { "Content-Type" => "text/plain", "Content-Length" => "0" }, [] ]
170
+ [ 403, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "0" }, [] ]
143
171
  else
144
- [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
172
+ [ 403, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "9" }, [ "Forbidden" ] ]
145
173
  end
146
174
  end
147
175
 
148
176
  # Returns a 404 Not Found response tuple
149
177
  def not_found_response(env)
150
178
  if head_request?(env)
151
- [ 404, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
179
+ [ 404, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "0", X_CASCADE => "pass" }, [] ]
152
180
  else
153
- [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
181
+ [ 404, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "9", X_CASCADE => "pass" }, [ "Not found" ] ]
154
182
  end
155
183
  end
156
184
 
157
185
  def method_not_allowed_response
158
- [ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ]
186
+ [ 405, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "18" }, [ "Method Not Allowed" ] ]
159
187
  end
160
188
 
161
189
  def precondition_failed_response(env)
162
190
  if head_request?(env)
163
- [ 412, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
191
+ [ 412, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "0", X_CASCADE => "pass" }, [] ]
164
192
  else
165
- [ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
193
+ [ 412, { Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "19", X_CASCADE => "pass" }, [ "Precondition Failed" ] ]
166
194
  end
167
195
  end
168
196
 
@@ -171,7 +199,7 @@ module Sprockets
171
199
  def javascript_exception_response(exception)
172
200
  err = "#{exception.class.name}: #{exception.message}\n (in #{exception.backtrace[0]})"
173
201
  body = "throw Error(#{err.inspect})"
174
- [ 200, { "Content-Type" => "application/javascript", "Content-Length" => body.bytesize.to_s }, [ body ] ]
202
+ [ 200, { Rack::CONTENT_TYPE => "application/javascript", Rack::CONTENT_LENGTH => body.bytesize.to_s }, [ body ] ]
175
203
  end
176
204
 
177
205
  # Returns a CSS response that hides all elements on the page and
@@ -224,7 +252,7 @@ module Sprockets
224
252
  }
225
253
  CSS
226
254
 
227
- [ 200, { "Content-Type" => "text/css; charset=utf-8", "Content-Length" => body.bytesize.to_s }, [ body ] ]
255
+ [ 200, { Rack::CONTENT_TYPE => "text/css; charset=utf-8", Rack::CONTENT_LENGTH => body.bytesize.to_s }, [ body ] ]
228
256
  end
229
257
 
230
258
  # Escape special characters for use inside a CSS content("...") string
@@ -236,27 +264,22 @@ module Sprockets
236
264
  gsub('/', '\\\\002f ')
237
265
  end
238
266
 
239
- # Test if `?body=1` or `body=true` query param is set
240
- def body_only?(env)
241
- env["QUERY_STRING"].to_s =~ /body=(1|t)/
242
- end
243
-
244
267
  def cache_headers(env, etag)
245
268
  headers = {}
246
269
 
247
270
  # Set caching headers
248
- headers["Cache-Control"] = "public"
249
- headers["ETag"] = %("#{etag}")
271
+ headers[Rack::CACHE_CONTROL] = +"public"
272
+ headers[Rack::ETAG] = %("#{etag}")
250
273
 
251
274
  # If the request url contains a fingerprint, set a long
252
275
  # expires on the response
253
276
  if path_fingerprint(env["PATH_INFO"])
254
- headers["Cache-Control"] += ", max-age=31536000"
277
+ headers[Rack::CACHE_CONTROL] << ", max-age=31536000, immutable"
255
278
 
256
279
  # Otherwise set `must-revalidate` since the asset could be modified.
257
280
  else
258
- headers["Cache-Control"] += ", must-revalidate"
259
- headers["Vary"] = "Accept-Encoding"
281
+ headers[Rack::CACHE_CONTROL] << ", must-revalidate"
282
+ headers[VARY] = "Accept-Encoding"
260
283
  end
261
284
 
262
285
  headers
@@ -266,7 +289,7 @@ module Sprockets
266
289
  headers = {}
267
290
 
268
291
  # Set content length header
269
- headers["Content-Length"] = length.to_s
292
+ headers[Rack::CONTENT_LENGTH] = length.to_s
270
293
 
271
294
  # Set content type header
272
295
  if type = asset.content_type
@@ -274,7 +297,7 @@ module Sprockets
274
297
  if type.start_with?("text/") && asset.charset
275
298
  type += "; charset=#{asset.charset}"
276
299
  end
277
- headers["Content-Type"] = type
300
+ headers[Rack::CONTENT_TYPE] = type
278
301
  end
279
302
 
280
303
  headers.merge(cache_headers(env, asset.etag))
@@ -286,7 +309,7 @@ module Sprockets
286
309
  # # => "0aa2105d29558f3eb790d411d7d8fb66"
287
310
  #
288
311
  def path_fingerprint(path)
289
- path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]
312
+ path[/-([0-9a-zA-Z]{7,128})\.[^.]+\z/, 1]
290
313
  end
291
314
  end
292
315
  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