sprockets 3.7.2 → 4.1.1

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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -262
  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 +9 -0
  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 -36
  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 +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 +88 -68
  54. data/lib/sprockets/manifest.rb +67 -64
  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 +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 +38 -25
  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 +11 -8
  81. data/lib/sprockets/utils/gzip.rb +46 -14
  82. data/lib/sprockets/utils.rb +45 -75
  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 +128 -23
  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,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 !['GET', 'HEAD'].include?(env['REQUEST_METHOD'])
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)
@@ -42,27 +52,26 @@ module Sprockets
42
52
  return forbidden_response(env)
43
53
  end
44
54
 
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
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?
@@ -136,6 +145,15 @@ module Sprockets
136
145
  [ 304, cache_headers(env, etag), [] ]
137
146
  end
138
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
+
139
157
  # Returns a 403 Forbidden response tuple
140
158
  def forbidden_response(env)
141
159
  if head_request?(env)
@@ -236,22 +254,17 @@ module Sprockets
236
254
  gsub('/', '\\\\002f ')
237
255
  end
238
256
 
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
257
  def cache_headers(env, etag)
245
258
  headers = {}
246
259
 
247
260
  # Set caching headers
248
- headers["Cache-Control"] = "public"
261
+ headers["Cache-Control"] = +"public"
249
262
  headers["ETag"] = %("#{etag}")
250
263
 
251
264
  # If the request url contains a fingerprint, set a long
252
265
  # expires on the response
253
266
  if path_fingerprint(env["PATH_INFO"])
254
- headers["Cache-Control"] << ", max-age=31536000"
267
+ headers["Cache-Control"] << ", max-age=31536000, immutable"
255
268
 
256
269
  # Otherwise set `must-revalidate` since the asset could be modified.
257
270
  else
@@ -286,7 +299,7 @@ module Sprockets
286
299
  # # => "0aa2105d29558f3eb790d411d7d8fb66"
287
300
  #
288
301
  def path_fingerprint(path)
289
- path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]
302
+ path[/-([0-9a-zA-Z]{7,128})\.[^.]+\z/, 1]
290
303
  end
291
304
  end
292
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