sprockets 4.0.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 (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +665 -0
  4. data/bin/sprockets +93 -0
  5. data/lib/rake/sprocketstask.rb +153 -0
  6. data/lib/sprockets.rb +229 -0
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +202 -0
  9. data/lib/sprockets/autoload.rb +16 -0
  10. data/lib/sprockets/autoload/babel.rb +8 -0
  11. data/lib/sprockets/autoload/closure.rb +8 -0
  12. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  13. data/lib/sprockets/autoload/eco.rb +8 -0
  14. data/lib/sprockets/autoload/ejs.rb +8 -0
  15. data/lib/sprockets/autoload/jsminc.rb +8 -0
  16. data/lib/sprockets/autoload/sass.rb +8 -0
  17. data/lib/sprockets/autoload/sassc.rb +8 -0
  18. data/lib/sprockets/autoload/uglifier.rb +8 -0
  19. data/lib/sprockets/autoload/yui.rb +8 -0
  20. data/lib/sprockets/autoload/zopfli.rb +7 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +147 -0
  23. data/lib/sprockets/bower.rb +61 -0
  24. data/lib/sprockets/bundle.rb +105 -0
  25. data/lib/sprockets/cache.rb +271 -0
  26. data/lib/sprockets/cache/file_store.rb +208 -0
  27. data/lib/sprockets/cache/memory_store.rb +75 -0
  28. data/lib/sprockets/cache/null_store.rb +54 -0
  29. data/lib/sprockets/cached_environment.rb +64 -0
  30. data/lib/sprockets/closure_compressor.rb +48 -0
  31. data/lib/sprockets/coffee_script_processor.rb +39 -0
  32. data/lib/sprockets/compressing.rb +134 -0
  33. data/lib/sprockets/configuration.rb +79 -0
  34. data/lib/sprockets/context.rb +304 -0
  35. data/lib/sprockets/dependencies.rb +74 -0
  36. data/lib/sprockets/digest_utils.rb +200 -0
  37. data/lib/sprockets/directive_processor.rb +414 -0
  38. data/lib/sprockets/eco_processor.rb +33 -0
  39. data/lib/sprockets/ejs_processor.rb +32 -0
  40. data/lib/sprockets/encoding_utils.rb +262 -0
  41. data/lib/sprockets/environment.rb +46 -0
  42. data/lib/sprockets/erb_processor.rb +37 -0
  43. data/lib/sprockets/errors.rb +12 -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 +16 -0
  50. data/lib/sprockets/http_utils.rb +135 -0
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +50 -0
  53. data/lib/sprockets/loader.rb +345 -0
  54. data/lib/sprockets/manifest.rb +338 -0
  55. data/lib/sprockets/manifest_utils.rb +48 -0
  56. data/lib/sprockets/mime.rb +96 -0
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +77 -0
  59. data/lib/sprockets/path_digest_utils.rb +48 -0
  60. data/lib/sprockets/path_utils.rb +367 -0
  61. data/lib/sprockets/paths.rb +82 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +228 -0
  64. data/lib/sprockets/processor_utils.rb +169 -0
  65. data/lib/sprockets/resolve.rb +295 -0
  66. data/lib/sprockets/sass_cache_store.rb +30 -0
  67. data/lib/sprockets/sass_compressor.rb +63 -0
  68. data/lib/sprockets/sass_functions.rb +3 -0
  69. data/lib/sprockets/sass_importer.rb +3 -0
  70. data/lib/sprockets/sass_processor.rb +313 -0
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +295 -0
  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 +173 -0
  77. data/lib/sprockets/uglifier_compressor.rb +66 -0
  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 +191 -0
  81. data/lib/sprockets/utils.rb +202 -0
  82. data/lib/sprockets/utils/gzip.rb +99 -0
  83. data/lib/sprockets/version.rb +4 -0
  84. data/lib/sprockets/yui_compressor.rb +56 -0
  85. metadata +444 -0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/source_map_utils'
4
+
5
+ module Sprockets
6
+ # Processor engine class for the CoffeeScript compiler.
7
+ # Depends on the `coffee-script` and `coffee-script-source` gems.
8
+ #
9
+ # For more infomation see:
10
+ #
11
+ # https://github.com/rails/ruby-coffee-script
12
+ #
13
+ module CoffeeScriptProcessor
14
+ VERSION = '2'
15
+
16
+ def self.cache_key
17
+ @cache_key ||= "#{name}:#{Autoload::CoffeeScript::Source.version}:#{VERSION}".freeze
18
+ end
19
+
20
+ def self.call(input)
21
+ data = input[:data]
22
+
23
+ js, map = input[:cache].fetch([self.cache_key, data]) do
24
+ result = Autoload::CoffeeScript.compile(
25
+ data,
26
+ sourceMap: "v3",
27
+ sourceFiles: [File.basename(input[:filename])],
28
+ generatedFile: input[:filename]
29
+ )
30
+ [result['js'], JSON.parse(result['v3SourceMap'])]
31
+ end
32
+
33
+ map = SourceMapUtils.format_source_map(map, input)
34
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
35
+
36
+ { data: js, map: map }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/utils'
3
+
4
+ module Sprockets
5
+ # `Compressing` is an internal mixin whose public methods are exposed on
6
+ # the `Environment` and `CachedEnvironment` classes.
7
+ module Compressing
8
+ include Utils
9
+
10
+ def compressors
11
+ config[:compressors]
12
+ end
13
+
14
+ # Public: Register a new compressor `klass` at `sym` for `mime_type`.
15
+ #
16
+ # Registering a processor allows it to be looked up by `sym` later when
17
+ # assigning a JavaScript or CSS compressor.
18
+ #
19
+ # Compressors only operate on JavaScript and CSS. If you want to compress a
20
+ # different type of asset, use a processor instead.
21
+ #
22
+ # Examples
23
+ #
24
+ # register_compressor 'text/css', :my_sass, MySassCompressor
25
+ # css_compressor = :my_sass
26
+ #
27
+ # mime_type - String MIME Type (one of: 'test/css' or 'application/javascript').
28
+ # sym - Symbol registration address.
29
+ # klass - The compressor class.
30
+ #
31
+ # Returns nothing.
32
+ def register_compressor(mime_type, sym, klass)
33
+ self.config = hash_reassoc(config, :compressors, mime_type) do |compressors|
34
+ compressors[sym] = klass
35
+ compressors
36
+ end
37
+ end
38
+
39
+ # Return CSS compressor or nil if none is set
40
+ def css_compressor
41
+ if defined? @css_compressor
42
+ @css_compressor
43
+ end
44
+ end
45
+
46
+ # Assign a compressor to run on `text/css` assets.
47
+ #
48
+ # The compressor object must respond to `compress`.
49
+ def css_compressor=(compressor)
50
+ unregister_bundle_processor 'text/css', @css_compressor if defined? @css_compressor
51
+ @css_compressor = nil
52
+ return unless compressor
53
+
54
+ if compressor.is_a?(Symbol)
55
+ @css_compressor = klass = config[:compressors]['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}")
56
+ elsif compressor.respond_to?(:compress)
57
+ klass = proc { |input| compressor.compress(input[:data]) }
58
+ @css_compressor = :css_compressor
59
+ else
60
+ @css_compressor = klass = compressor
61
+ end
62
+
63
+ register_bundle_processor 'text/css', klass
64
+ end
65
+
66
+ # Return JS compressor or nil if none is set
67
+ def js_compressor
68
+ if defined? @js_compressor
69
+ @js_compressor
70
+ end
71
+ end
72
+
73
+ # Assign a compressor to run on `application/javascript` assets.
74
+ #
75
+ # The compressor object must respond to `compress`.
76
+ def js_compressor=(compressor)
77
+ unregister_bundle_processor 'application/javascript', @js_compressor if defined? @js_compressor
78
+ @js_compressor = nil
79
+ return unless compressor
80
+
81
+ if compressor.is_a?(Symbol)
82
+ @js_compressor = klass = config[:compressors]['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}")
83
+ elsif compressor.respond_to?(:compress)
84
+ klass = proc { |input| compressor.compress(input[:data]) }
85
+ @js_compressor = :js_compressor
86
+ else
87
+ @js_compressor = klass = compressor
88
+ end
89
+
90
+ register_bundle_processor 'application/javascript', klass
91
+ end
92
+
93
+ # Public: Checks if Gzip is enabled.
94
+ def gzip?
95
+ config[:gzip_enabled]
96
+ end
97
+
98
+ # Public: Checks if Gzip is disabled.
99
+ def skip_gzip?
100
+ !gzip?
101
+ end
102
+
103
+ # Public: Enable or disable the creation of Gzip files.
104
+ #
105
+ # To disable gzip generation set to a falsey value:
106
+ #
107
+ # environment.gzip = false
108
+ #
109
+ # To enable set to a truthy value. By default zlib wil
110
+ # be used to gzip assets. If you have the Zopfli gem
111
+ # installed you can specify the zopfli algorithm to be used
112
+ # instead:
113
+ #
114
+ # environment.gzip = :zopfli
115
+ #
116
+ def gzip=(gzip)
117
+ self.config = config.merge(gzip_enabled: gzip).freeze
118
+
119
+ case gzip
120
+ when false, nil
121
+ self.unregister_exporter Exporters::ZlibExporter
122
+ self.unregister_exporter Exporters::ZopfliExporter
123
+ when :zopfli
124
+ self.unregister_exporter Exporters::ZlibExporter
125
+ self.register_exporter '*/*', Exporters::ZopfliExporter
126
+ else
127
+ self.unregister_exporter Exporters::ZopfliExporter
128
+ self.register_exporter '*/*', Exporters::ZlibExporter
129
+ end
130
+
131
+ gzip
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/compressing'
3
+ require 'sprockets/dependencies'
4
+ require 'sprockets/mime'
5
+ require 'sprockets/paths'
6
+ require 'sprockets/processing'
7
+ require 'sprockets/exporting'
8
+ require 'sprockets/transformers'
9
+ require 'sprockets/utils'
10
+
11
+ module Sprockets
12
+ module Configuration
13
+ include Paths, Mime, Transformers, Processing, Exporting, Compressing, Dependencies, Utils
14
+
15
+ def initialize_configuration(parent)
16
+ @config = parent.config
17
+ @logger = parent.logger
18
+ @context_class = Class.new(parent.context_class)
19
+ end
20
+
21
+ attr_reader :config
22
+
23
+ def config=(config)
24
+ raise TypeError, "can't assign mutable config" unless config.frozen?
25
+ @config = config
26
+ end
27
+
28
+ # Get and set `Logger` instance.
29
+ attr_accessor :logger
30
+
31
+ # The `Environment#version` is a custom value used for manually
32
+ # expiring all asset caches.
33
+ #
34
+ # Sprockets is able to track most file and directory changes and
35
+ # will take care of expiring the cache for you. However, its
36
+ # impossible to know when any custom helpers change that you mix
37
+ # into the `Context`.
38
+ #
39
+ # It would be wise to increment this value anytime you make a
40
+ # configuration change to the `Environment` object.
41
+ def version
42
+ config[:version]
43
+ end
44
+
45
+ # Assign an environment version.
46
+ #
47
+ # environment.version = '2.0'
48
+ #
49
+ def version=(version)
50
+ self.config = hash_reassoc(config, :version) { version.dup }
51
+ end
52
+
53
+ # Public: Returns a `Digest` implementation class.
54
+ #
55
+ # Defaults to `Digest::SHA256`.
56
+ def digest_class
57
+ config[:digest_class]
58
+ end
59
+
60
+ # Deprecated: Assign a `Digest` implementation class. This maybe any Ruby
61
+ # `Digest::` implementation such as `Digest::SHA256` or
62
+ # `Digest::MD5`.
63
+ #
64
+ # environment.digest_class = Digest::MD5
65
+ #
66
+ def digest_class=(klass)
67
+ self.config = config.merge(digest_class: klass).freeze
68
+ end
69
+
70
+ # This class maybe mutated and mixed in with custom helpers.
71
+ #
72
+ # environment.context_class.instance_eval do
73
+ # include MyHelpers
74
+ # def asset_url; end
75
+ # end
76
+ #
77
+ attr_reader :context_class
78
+ end
79
+ end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+ require 'rack/utils'
3
+ require 'set'
4
+ require 'sprockets/errors'
5
+
6
+ module Sprockets
7
+ # They are typically accessed by ERB templates. You can mix in custom helpers
8
+ # by injecting them into `Environment#context_class`. Do not mix them into
9
+ # `Context` directly.
10
+ #
11
+ # environment.context_class.class_eval do
12
+ # include MyHelper
13
+ # def asset_url; end
14
+ # end
15
+ #
16
+ # <%= asset_url "foo.png" %>
17
+ #
18
+ # The `Context` also collects dependencies declared by
19
+ # assets. See `DirectiveProcessor` for an example of this.
20
+ class Context
21
+ # Internal: Proxy for ENV that keeps track of the environment variables used
22
+ class ENVProxy < SimpleDelegator
23
+ def initialize(context)
24
+ @context = context
25
+ super(ENV)
26
+ end
27
+
28
+ def [](key)
29
+ @context.depend_on_env(key)
30
+ super
31
+ end
32
+
33
+ def fetch(key, *)
34
+ @context.depend_on_env(key)
35
+ super
36
+ end
37
+ end
38
+
39
+ attr_reader :environment, :filename
40
+
41
+ def initialize(input)
42
+ @environment = input[:environment]
43
+ @metadata = input[:metadata]
44
+ @load_path = input[:load_path]
45
+ @logical_path = input[:name]
46
+ @filename = input[:filename]
47
+ @dirname = File.dirname(@filename)
48
+ @content_type = input[:content_type]
49
+
50
+ @required = Set.new(@metadata[:required])
51
+ @stubbed = Set.new(@metadata[:stubbed])
52
+ @links = Set.new(@metadata[:links])
53
+ @dependencies = Set.new(input[:metadata][:dependencies])
54
+ end
55
+
56
+ def metadata
57
+ { required: @required,
58
+ stubbed: @stubbed,
59
+ links: @links,
60
+ dependencies: @dependencies }
61
+ end
62
+
63
+ def env_proxy
64
+ ENVProxy.new(self)
65
+ end
66
+
67
+ # Returns the environment path that contains the file.
68
+ #
69
+ # If `app/javascripts` and `app/stylesheets` are in your path, and
70
+ # current file is `app/javascripts/foo/bar.js`, `load_path` would
71
+ # return `app/javascripts`.
72
+ attr_reader :load_path
73
+ alias_method :root_path, :load_path
74
+
75
+ # Returns logical path without any file extensions.
76
+ #
77
+ # 'app/javascripts/application.js'
78
+ # # => 'application'
79
+ #
80
+ attr_reader :logical_path
81
+
82
+ # Returns content type of file
83
+ #
84
+ # 'application/javascript'
85
+ # 'text/css'
86
+ #
87
+ attr_reader :content_type
88
+
89
+ # Public: Given a logical path, `resolve` will find and return an Asset URI.
90
+ # Relative paths will also be resolved. An accept type maybe given to
91
+ # restrict the search.
92
+ #
93
+ # resolve("foo.js")
94
+ # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
95
+ #
96
+ # resolve("./bar.js")
97
+ # # => "file:///path/to/app/javascripts/bar.js?type=application/javascript"
98
+ #
99
+ # path - String logical or absolute path
100
+ # accept - String content accept type
101
+ #
102
+ # Returns an Asset URI String.
103
+ def resolve(path, **kargs)
104
+ kargs[:base_path] = @dirname
105
+ uri, deps = environment.resolve!(path, **kargs)
106
+ @dependencies.merge(deps)
107
+ uri
108
+ end
109
+
110
+ # Public: Load Asset by AssetURI and track it as a dependency.
111
+ #
112
+ # uri - AssetURI
113
+ #
114
+ # Returns Asset.
115
+ def load(uri)
116
+ asset = environment.load(uri)
117
+ @dependencies.merge(asset.metadata[:dependencies])
118
+ asset
119
+ end
120
+
121
+ # `depend_on` allows you to state a dependency on a file without
122
+ # including it.
123
+ #
124
+ # This is used for caching purposes. Any changes made to
125
+ # the dependency file will invalidate the cache of the
126
+ # source file.
127
+ def depend_on(path)
128
+ if environment.absolute_path?(path) && environment.stat(path)
129
+ @dependencies << environment.build_file_digest_uri(path)
130
+ else
131
+ resolve(path)
132
+ end
133
+ nil
134
+ end
135
+
136
+ # `depend_on_asset` allows you to state an asset dependency
137
+ # without including it.
138
+ #
139
+ # This is used for caching purposes. Any changes that would
140
+ # invalidate the dependency asset will invalidate the source
141
+ # file. Unlike `depend_on`, this will recursively include
142
+ # the target asset's dependencies.
143
+ def depend_on_asset(path)
144
+ load(resolve(path))
145
+ end
146
+
147
+ # `depend_on_env` allows you to state a dependency on an environment
148
+ # variable.
149
+ #
150
+ # This is used for caching purposes. Any changes in the value of the
151
+ # environment variable will invalidate the cache of the source file.
152
+ def depend_on_env(key)
153
+ @dependencies << "env:#{key}"
154
+ end
155
+
156
+ # `require_asset` declares `path` as a dependency of the file. The
157
+ # dependency will be inserted before the file and will only be
158
+ # included once.
159
+ #
160
+ # If ERB processing is enabled, you can use it to dynamically
161
+ # require assets.
162
+ #
163
+ # <%= require_asset "#{framework}.js" %>
164
+ #
165
+ def require_asset(path)
166
+ @required << resolve(path, accept: @content_type, pipeline: :self)
167
+ nil
168
+ end
169
+
170
+ # `stub_asset` blacklists `path` from being included in the bundle.
171
+ # `path` must be an asset which may or may not already be included
172
+ # in the bundle.
173
+ def stub_asset(path)
174
+ @stubbed << resolve(path, accept: @content_type, pipeline: :self)
175
+ nil
176
+ end
177
+
178
+ # `link_asset` declares an external dependency on an asset without directly
179
+ # including it. The target asset is returned from this function making it
180
+ # easy to construct a link to it.
181
+ #
182
+ # Returns an Asset or nil.
183
+ def link_asset(path)
184
+ asset = depend_on_asset(path)
185
+ @links << asset.uri
186
+ asset
187
+ end
188
+
189
+ # Returns a `data:` URI with the contents of the asset at the specified
190
+ # path, and marks that path as a dependency of the current file.
191
+ #
192
+ # Uses URI encoding for SVG files, base64 encoding for all the other files.
193
+ #
194
+ # Use `asset_data_uri` from ERB with CSS or JavaScript assets:
195
+ #
196
+ # #logo { background: url(<%= asset_data_uri 'logo.png' %>) }
197
+ #
198
+ # $('<img>').attr('src', '<%= asset_data_uri 'avatar.jpg' %>')
199
+ #
200
+ def asset_data_uri(path)
201
+ asset = depend_on_asset(path)
202
+ if asset.content_type == 'image/svg+xml'
203
+ svg_asset_data_uri(asset)
204
+ else
205
+ base64_asset_data_uri(asset)
206
+ end
207
+ end
208
+
209
+ # Expands logical path to full url to asset.
210
+ #
211
+ # NOTE: This helper is currently not implemented and should be
212
+ # customized by the application. Though, in the future, some
213
+ # basics implemention may be provided with different methods that
214
+ # are required to be overridden.
215
+ def asset_path(path, options = {})
216
+ message = <<-EOS
217
+ Custom asset_path helper is not implemented
218
+
219
+ Extend your environment context with a custom method.
220
+
221
+ environment.context_class.class_eval do
222
+ def asset_path(path, options = {})
223
+ end
224
+ end
225
+ EOS
226
+ raise NotImplementedError, message
227
+ end
228
+
229
+ # Expand logical image asset path.
230
+ def image_path(path)
231
+ asset_path(path, type: :image)
232
+ end
233
+
234
+ # Expand logical video asset path.
235
+ def video_path(path)
236
+ asset_path(path, type: :video)
237
+ end
238
+
239
+ # Expand logical audio asset path.
240
+ def audio_path(path)
241
+ asset_path(path, type: :audio)
242
+ end
243
+
244
+ # Expand logical font asset path.
245
+ def font_path(path)
246
+ asset_path(path, type: :font)
247
+ end
248
+
249
+ # Expand logical javascript asset path.
250
+ def javascript_path(path)
251
+ asset_path(path, type: :javascript)
252
+ end
253
+
254
+ # Expand logical stylesheet asset path.
255
+ def stylesheet_path(path)
256
+ asset_path(path, type: :stylesheet)
257
+ end
258
+
259
+ protected
260
+
261
+ # Returns a URI-encoded data URI (always "-quoted).
262
+ def svg_asset_data_uri(asset)
263
+ svg = asset.source.dup
264
+ optimize_svg_for_uri_escaping!(svg)
265
+ data = Rack::Utils.escape(svg)
266
+ optimize_quoted_uri_escapes!(data)
267
+ "\"data:#{asset.content_type};charset=utf-8,#{data}\""
268
+ end
269
+
270
+ # Returns a Base64-encoded data URI.
271
+ def base64_asset_data_uri(asset)
272
+ data = Rack::Utils.escape(EncodingUtils.base64(asset.source))
273
+ "data:#{asset.content_type};base64,#{data}"
274
+ end
275
+
276
+ # Optimizes an SVG for being URI-escaped.
277
+ #
278
+ # This method only performs these basic but crucial optimizations:
279
+ # * Replaces " with ', because ' does not need escaping.
280
+ # * Removes comments, meta, doctype, and newlines.
281
+ # * Collapses whitespace.
282
+ def optimize_svg_for_uri_escaping!(svg)
283
+ # Remove comments, xml meta, and doctype
284
+ svg.gsub!(/<!--.*?-->|<\?.*?\?>|<!.*?>/m, '')
285
+ # Replace consecutive whitespace and newlines with a space
286
+ svg.gsub!(/\s+/, ' ')
287
+ # Collapse inter-tag whitespace
288
+ svg.gsub!('> <', '><')
289
+ # Replace " with '
290
+ svg.gsub!(/([\w:])="(.*?)"/, "\\1='\\2'")
291
+ svg.strip!
292
+ end
293
+
294
+ # Un-escapes characters in the given URI-escaped string that do not need
295
+ # escaping in "-quoted data URIs.
296
+ def optimize_quoted_uri_escapes!(escaped)
297
+ escaped.gsub!('%3D', '=')
298
+ escaped.gsub!('%3A', ':')
299
+ escaped.gsub!('%2F', '/')
300
+ escaped.gsub!('%27', "'")
301
+ escaped.tr!('+', ' ')
302
+ end
303
+ end
304
+ end