sprockets 2.2.3 → 4.0.0

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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +68 -0
  3. data/README.md +482 -255
  4. data/bin/sprockets +20 -7
  5. data/lib/rake/sprocketstask.rb +28 -15
  6. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  7. data/lib/sprockets/asset.rb +142 -207
  8. data/lib/sprockets/autoload/babel.rb +8 -0
  9. data/lib/sprockets/autoload/closure.rb +8 -0
  10. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  11. data/lib/sprockets/autoload/eco.rb +8 -0
  12. data/lib/sprockets/autoload/ejs.rb +8 -0
  13. data/lib/sprockets/autoload/jsminc.rb +8 -0
  14. data/lib/sprockets/autoload/sass.rb +8 -0
  15. data/lib/sprockets/autoload/sassc.rb +8 -0
  16. data/lib/sprockets/autoload/uglifier.rb +8 -0
  17. data/lib/sprockets/autoload/yui.rb +8 -0
  18. data/lib/sprockets/autoload/zopfli.rb +7 -0
  19. data/lib/sprockets/autoload.rb +16 -0
  20. data/lib/sprockets/babel_processor.rb +66 -0
  21. data/lib/sprockets/base.rb +89 -249
  22. data/lib/sprockets/bower.rb +61 -0
  23. data/lib/sprockets/bundle.rb +105 -0
  24. data/lib/sprockets/cache/file_store.rb +190 -14
  25. data/lib/sprockets/cache/memory_store.rb +75 -0
  26. data/lib/sprockets/cache/null_store.rb +54 -0
  27. data/lib/sprockets/cache.rb +271 -0
  28. data/lib/sprockets/cached_environment.rb +64 -0
  29. data/lib/sprockets/closure_compressor.rb +48 -0
  30. data/lib/sprockets/coffee_script_processor.rb +39 -0
  31. data/lib/sprockets/compressing.rb +134 -0
  32. data/lib/sprockets/configuration.rb +79 -0
  33. data/lib/sprockets/context.rb +204 -135
  34. data/lib/sprockets/dependencies.rb +74 -0
  35. data/lib/sprockets/digest_utils.rb +200 -0
  36. data/lib/sprockets/directive_processor.rb +224 -216
  37. data/lib/sprockets/eco_processor.rb +33 -0
  38. data/lib/sprockets/ejs_processor.rb +32 -0
  39. data/lib/sprockets/encoding_utils.rb +262 -0
  40. data/lib/sprockets/environment.rb +23 -68
  41. data/lib/sprockets/erb_processor.rb +37 -0
  42. data/lib/sprockets/errors.rb +6 -13
  43. data/lib/sprockets/exporters/base.rb +72 -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 +16 -0
  49. data/lib/sprockets/http_utils.rb +135 -0
  50. data/lib/sprockets/jsminc_compressor.rb +32 -0
  51. data/lib/sprockets/jst_processor.rb +36 -19
  52. data/lib/sprockets/loader.rb +343 -0
  53. data/lib/sprockets/manifest.rb +231 -96
  54. data/lib/sprockets/manifest_utils.rb +48 -0
  55. data/lib/sprockets/mime.rb +80 -32
  56. data/lib/sprockets/npm.rb +52 -0
  57. data/lib/sprockets/path_dependency_utils.rb +77 -0
  58. data/lib/sprockets/path_digest_utils.rb +48 -0
  59. data/lib/sprockets/path_utils.rb +367 -0
  60. data/lib/sprockets/paths.rb +82 -0
  61. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  62. data/lib/sprockets/processing.rb +140 -192
  63. data/lib/sprockets/processor_utils.rb +169 -0
  64. data/lib/sprockets/resolve.rb +295 -0
  65. data/lib/sprockets/sass_cache_store.rb +30 -0
  66. data/lib/sprockets/sass_compressor.rb +63 -0
  67. data/lib/sprockets/sass_functions.rb +3 -0
  68. data/lib/sprockets/sass_importer.rb +3 -0
  69. data/lib/sprockets/sass_processor.rb +313 -0
  70. data/lib/sprockets/sassc_compressor.rb +56 -0
  71. data/lib/sprockets/sassc_processor.rb +297 -0
  72. data/lib/sprockets/server.rb +138 -90
  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 +173 -0
  76. data/lib/sprockets/uglifier_compressor.rb +66 -0
  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 +191 -0
  80. data/lib/sprockets/utils/gzip.rb +99 -0
  81. data/lib/sprockets/utils.rb +186 -53
  82. data/lib/sprockets/version.rb +2 -1
  83. data/lib/sprockets/yui_compressor.rb +56 -0
  84. data/lib/sprockets.rb +217 -52
  85. metadata +250 -59
  86. data/LICENSE +0 -21
  87. data/lib/sprockets/asset_attributes.rb +0 -126
  88. data/lib/sprockets/bundled_asset.rb +0 -79
  89. data/lib/sprockets/caching.rb +0 -96
  90. data/lib/sprockets/charset_normalizer.rb +0 -41
  91. data/lib/sprockets/eco_template.rb +0 -38
  92. data/lib/sprockets/ejs_template.rb +0 -37
  93. data/lib/sprockets/engines.rb +0 -74
  94. data/lib/sprockets/index.rb +0 -99
  95. data/lib/sprockets/processed_asset.rb +0 -152
  96. data/lib/sprockets/processor.rb +0 -32
  97. data/lib/sprockets/safety_colons.rb +0 -28
  98. data/lib/sprockets/static_asset.rb +0 -57
  99. data/lib/sprockets/trail.rb +0 -90
@@ -1,15 +1,12 @@
1
- require 'base64'
1
+ # frozen_string_literal: true
2
2
  require 'rack/utils'
3
- require 'sprockets/errors'
4
- require 'sprockets/utils'
5
- require 'pathname'
6
3
  require 'set'
4
+ require 'sprockets/errors'
7
5
 
8
6
  module Sprockets
9
- # `Context` provides helper methods to all `Tilt` processors. They
10
- # are typically accessed by ERB templates. You can mix in custom
11
- # helpers by injecting them into `Environment#context_class`. Do not
12
- # mix them into `Context` directly.
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.
13
10
  #
14
11
  # environment.context_class.class_eval do
15
12
  # include MyHelper
@@ -21,98 +18,118 @@ module Sprockets
21
18
  # The `Context` also collects dependencies declared by
22
19
  # assets. See `DirectiveProcessor` for an example of this.
23
20
  class Context
24
- attr_reader :environment, :pathname
25
- attr_reader :_required_paths, :_stubbed_assets
26
- attr_reader :_dependency_paths, :_dependency_assets
27
- attr_writer :__LINE__
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
28
40
 
29
- def initialize(environment, logical_path, pathname)
30
- @environment = environment
31
- @logical_path = logical_path
32
- @pathname = pathname
33
- @__LINE__ = nil
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]
34
49
 
35
- @_required_paths = []
36
- @_stubbed_assets = Set.new
37
- @_dependency_paths = Set.new
38
- @_dependency_assets = Set.new([pathname.to_s])
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)
39
65
  end
40
66
 
41
67
  # Returns the environment path that contains the file.
42
68
  #
43
69
  # If `app/javascripts` and `app/stylesheets` are in your path, and
44
- # current file is `app/javascripts/foo/bar.js`, `root_path` would
70
+ # current file is `app/javascripts/foo/bar.js`, `load_path` would
45
71
  # return `app/javascripts`.
46
- def root_path
47
- environment.paths.detect { |path| pathname.to_s[path] }
48
- end
72
+ attr_reader :load_path
73
+ alias_method :root_path, :load_path
49
74
 
50
75
  # Returns logical path without any file extensions.
51
76
  #
52
77
  # 'app/javascripts/application.js'
53
78
  # # => 'application'
54
79
  #
55
- def logical_path
56
- @logical_path[/^([^.]+)/, 0]
57
- end
80
+ attr_reader :logical_path
58
81
 
59
82
  # Returns content type of file
60
83
  #
61
84
  # 'application/javascript'
62
85
  # 'text/css'
63
86
  #
64
- def content_type
65
- environment.content_type_of(pathname)
66
- end
87
+ attr_reader :content_type
67
88
 
68
- # Given a logical path, `resolve` will find and return the fully
69
- # expanded path. Relative paths will also be resolved. An optional
70
- # `:content_type` restriction can be supplied to restrict the
71
- # search.
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.
72
92
  #
73
93
  # resolve("foo.js")
74
- # # => "/path/to/app/javascripts/foo.js"
94
+ # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
75
95
  #
76
96
  # resolve("./bar.js")
77
- # # => "/path/to/app/javascripts/bar.js"
97
+ # # => "file:///path/to/app/javascripts/bar.js?type=application/javascript"
78
98
  #
79
- def resolve(path, options = {}, &block)
80
- pathname = Pathname.new(path)
81
- attributes = environment.attributes_for(pathname)
82
-
83
- if pathname.absolute?
84
- pathname
85
-
86
- elsif content_type = options[:content_type]
87
- content_type = self.content_type if content_type == :self
88
-
89
- if attributes.format_extension
90
- if content_type != attributes.content_type
91
- raise ContentTypeMismatch, "#{path} is " +
92
- "'#{attributes.content_type}', not '#{content_type}'"
93
- end
94
- end
95
-
96
- resolve(path) do |candidate|
97
- if self.content_type == environment.content_type_of(candidate)
98
- return candidate
99
- end
100
- end
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
101
109
 
102
- raise FileNotFound, "couldn't find file '#{path}'"
103
- else
104
- environment.resolve(path, :base_path => self.pathname.dirname, &block)
105
- end
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
106
119
  end
107
120
 
108
121
  # `depend_on` allows you to state a dependency on a file without
109
122
  # including it.
110
123
  #
111
124
  # This is used for caching purposes. Any changes made to
112
- # the dependency file with invalidate the cache of the
125
+ # the dependency file will invalidate the cache of the
113
126
  # source file.
114
127
  def depend_on(path)
115
- @_dependency_paths << resolve(path).to_s
128
+ if environment.absolute_path?(path) && environment.stat(path)
129
+ @dependencies << environment.build_file_digest_uri(path)
130
+ else
131
+ resolve(path)
132
+ end
116
133
  nil
117
134
  end
118
135
 
@@ -121,12 +138,19 @@ module Sprockets
121
138
  #
122
139
  # This is used for caching purposes. Any changes that would
123
140
  # invalidate the dependency asset will invalidate the source
124
- # file. Unlike `depend_on`, this will include recursively include
141
+ # file. Unlike `depend_on`, this will recursively include
125
142
  # the target asset's dependencies.
126
143
  def depend_on_asset(path)
127
- filename = resolve(path).to_s
128
- @_dependency_assets << filename
129
- nil
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}"
130
154
  end
131
155
 
132
156
  # `require_asset` declares `path` as a dependency of the file. The
@@ -139,9 +163,7 @@ module Sprockets
139
163
  # <%= require_asset "#{framework}.js" %>
140
164
  #
141
165
  def require_asset(path)
142
- pathname = resolve(path, :content_type => :self)
143
- depend_on_asset(pathname)
144
- @_required_paths << pathname.to_s
166
+ @required << resolve(path, accept: @content_type, pipeline: :self)
145
167
  nil
146
168
  end
147
169
 
@@ -149,60 +171,25 @@ module Sprockets
149
171
  # `path` must be an asset which may or may not already be included
150
172
  # in the bundle.
151
173
  def stub_asset(path)
152
- @_stubbed_assets << resolve(path, :content_type => :self).to_s
174
+ @stubbed << resolve(path, accept: @content_type, pipeline: :self)
153
175
  nil
154
176
  end
155
177
 
156
- # Tests if target path is able to be safely required into the
157
- # current concatenation.
158
- def asset_requirable?(path)
159
- pathname = resolve(path)
160
- content_type = environment.content_type_of(pathname)
161
- stat = environment.stat(path)
162
- return false unless stat && stat.file?
163
- self.content_type.nil? || self.content_type == content_type
164
- end
165
-
166
- # Reads `path` and runs processors on the file.
167
- #
168
- # This allows you to capture the result of an asset and include it
169
- # directly in another.
170
- #
171
- # <%= evaluate "bar.js" %>
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.
172
181
  #
173
- def evaluate(path, options = {})
174
- pathname = resolve(path)
175
- attributes = environment.attributes_for(pathname)
176
- processors = options[:processors] || attributes.processors
177
-
178
- if options[:data]
179
- result = options[:data]
180
- else
181
- if environment.respond_to?(:default_external_encoding)
182
- mime_type = environment.mime_types(pathname.extname)
183
- encoding = environment.encoding_for_mime_type(mime_type)
184
- result = Sprockets::Utils.read_unicode(pathname, encoding)
185
- else
186
- result = Sprockets::Utils.read_unicode(pathname)
187
- end
188
- end
189
-
190
- processors.each do |processor|
191
- begin
192
- template = processor.new(pathname.to_s) { result }
193
- result = template.render(self, {})
194
- rescue Exception => e
195
- annotate_exception! e
196
- raise
197
- end
198
- end
199
-
200
- result
182
+ # Returns an Asset or nil.
183
+ def link_asset(path)
184
+ asset = depend_on_asset(path)
185
+ @links << asset.uri
186
+ asset
201
187
  end
202
188
 
203
- # Returns a Base64-encoded `data:` URI with the contents of the
204
- # asset at the specified path, and marks that path as a dependency
205
- # of the current file.
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.
206
193
  #
207
194
  # Use `asset_data_uri` from ERB with CSS or JavaScript assets:
208
195
  #
@@ -211,25 +198,107 @@ module Sprockets
211
198
  # $('<img>').attr('src', '<%= asset_data_uri 'avatar.jpg' %>')
212
199
  #
213
200
  def asset_data_uri(path)
214
- depend_on_asset(path)
215
- asset = environment.find_asset(path)
216
- base64 = Base64.encode64(asset.to_s).gsub(/\s+/, "")
217
- "data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}"
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
218
207
  end
219
208
 
220
- private
221
- # Annotates exception backtrace with the original template that
222
- # the exception was raised in.
223
- def annotate_exception!(exception)
224
- location = pathname.to_s
225
- location << ":#{@__LINE__}" if @__LINE__
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
226
218
 
227
- exception.extend(Sprockets::EngineError)
228
- exception.sprockets_annotation = " (in #{location})"
229
- end
219
+ Extend your environment context with a custom method.
230
220
 
231
- def logger
232
- environment.logger
221
+ environment.context_class.class_eval do
222
+ def asset_path(path, options = {})
233
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
234
303
  end
235
304
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/digest_utils'
3
+ require 'sprockets/path_digest_utils'
4
+ require 'sprockets/uri_utils'
5
+
6
+ module Sprockets
7
+ # `Dependencies` is an internal mixin whose public methods are exposed on the
8
+ # `Environment` and `CachedEnvironment` classes.
9
+ module Dependencies
10
+ include DigestUtils, PathDigestUtils, URIUtils
11
+
12
+ # Public: Mapping dependency schemes to resolver functions.
13
+ #
14
+ # key - String scheme
15
+ # value - Proc.call(Environment, String)
16
+ #
17
+ # Returns Hash.
18
+ def dependency_resolvers
19
+ config[:dependency_resolvers]
20
+ end
21
+
22
+ # Public: Default set of dependency URIs for assets.
23
+ #
24
+ # Returns Set of String URIs.
25
+ def dependencies
26
+ config[:dependencies]
27
+ end
28
+
29
+ # Public: Register new dependency URI resolver.
30
+ #
31
+ # scheme - String scheme
32
+ # block -
33
+ # environment - Environment
34
+ # uri - String dependency URI
35
+ #
36
+ # Returns nothing.
37
+ def register_dependency_resolver(scheme, &block)
38
+ self.config = hash_reassoc(config, :dependency_resolvers) do |hash|
39
+ hash.merge(scheme => block)
40
+ end
41
+ end
42
+
43
+ # Public: Add environmental dependency inheirted by all assets.
44
+ #
45
+ # uri - String dependency URI
46
+ #
47
+ # Returns nothing.
48
+ def add_dependency(uri)
49
+ self.config = hash_reassoc(config, :dependencies) do |set|
50
+ set + Set.new([uri])
51
+ end
52
+ end
53
+ alias_method :depend_on, :add_dependency
54
+
55
+ # Internal: Resolve dependency URIs.
56
+ #
57
+ # Returns resolved Object.
58
+ def resolve_dependency(str)
59
+ # Optimize for the most common scheme to
60
+ # save 22k allocations on an average Spree app.
61
+ scheme = if str.start_with?('file-digest:'.freeze)
62
+ 'file-digest'.freeze
63
+ else
64
+ str[/([^:]+)/, 1]
65
+ end
66
+
67
+ if resolver = config[:dependency_resolvers][scheme]
68
+ resolver.call(self, str)
69
+ else
70
+ nil
71
+ end
72
+ end
73
+ end
74
+ end