sprockets-rails 2.3.3 → 3.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.
@@ -1,128 +1,119 @@
1
1
  require 'action_view'
2
2
  require 'sprockets'
3
3
  require 'active_support/core_ext/class/attribute'
4
+ require 'sprockets/rails/utils'
4
5
 
5
6
  module Sprockets
6
7
  module Rails
7
8
  module Helper
8
- class << self
9
- attr_accessor :precompile, :assets, :raise_runtime_errors
10
- end
11
-
12
- def precompile
13
- Sprockets::Rails::Helper.precompile
14
- end
15
-
16
- def assets
17
- Sprockets::Rails::Helper.assets
18
- end
19
-
20
- def raise_runtime_errors
21
- Sprockets::Rails::Helper.raise_runtime_errors
22
- end
23
-
24
- class AssetFilteredError < StandardError
9
+ class AssetNotPrecompiled < StandardError
10
+ include Sprockets::Rails::Utils
25
11
  def initialize(source)
26
- msg = "Asset filtered out and will not be served: " <<
27
- "add `Rails.application.config.assets.precompile += %w( #{source} )` " <<
28
- "to `config/initializers/assets.rb` and restart your server"
29
- super(msg)
30
- end
31
- end
32
-
33
- class AbsoluteAssetPathError < ArgumentError
34
- def initialize(bad_path, good_path, prefix)
35
- msg = "Asset names passed to helpers should not include the #{prefix.inspect} prefix. " <<
36
- "Instead of #{bad_path.inspect}, use #{good_path.inspect}"
12
+ msg =
13
+ if using_sprockets4?
14
+ "Asset `#{source}` was not declared to be precompiled in production.\n" +
15
+ "Declare links to your assets in `assets/config/manifest.js`.\n" +
16
+ "Examples:\n" +
17
+ "`//= link ../javascripts/application.js`\n" +
18
+ "`//= link_directory ../javascripts .js`\n" +
19
+ "`//= link_directory ../stylesheets .css`\n" +
20
+ "`//= link_tree ../javascripts .js`\n" +
21
+ "`//= link_tree ../images`\n" +
22
+ "and restart your server"
23
+ else
24
+ "Asset was not declared to be precompiled in production.\n" +
25
+ "Add `Rails.application.config.assets.precompile += " +
26
+ "%w( #{source} )` to `config/initializers/assets.rb` and " +
27
+ "restart your server"
28
+ end
37
29
  super(msg)
38
30
  end
39
31
  end
40
32
 
41
- if defined? ActionView::Helpers::AssetUrlHelper
42
- include ActionView::Helpers::AssetUrlHelper
43
- include ActionView::Helpers::AssetTagHelper
44
- else
45
- require 'sprockets/rails/legacy_asset_tag_helper'
46
- require 'sprockets/rails/legacy_asset_url_helper'
47
- include LegacyAssetTagHelper
48
- include LegacyAssetUrlHelper
49
- end
33
+ include ActionView::Helpers::AssetUrlHelper
34
+ include ActionView::Helpers::AssetTagHelper
35
+ include Sprockets::Rails::Utils
50
36
 
51
- VIEW_ACCESSORS = [:assets_environment, :assets_manifest,
52
- :assets_prefix, :digest_assets, :debug_assets]
37
+ VIEW_ACCESSORS = [
38
+ :assets_environment, :assets_manifest,
39
+ :assets_precompile, :precompiled_asset_checker,
40
+ :assets_prefix, :digest_assets, :debug_assets,
41
+ :resolve_assets_with
42
+ ]
53
43
 
54
44
  def self.included(klass)
55
- if klass < Sprockets::Context
56
- klass.class_eval do
57
- alias_method :assets_environment, :environment
58
- def assets_manifest; end
59
- class_attribute :config, :assets_prefix, :digest_assets, :debug_assets
45
+ klass.class_attribute(*VIEW_ACCESSORS)
46
+
47
+ klass.class_eval do
48
+ remove_method :assets_environment
49
+ def assets_environment
50
+ if instance_variable_defined?(:@assets_environment)
51
+ @assets_environment = @assets_environment.cached
52
+ elsif env = self.class.assets_environment
53
+ @assets_environment = env.cached
54
+ else
55
+ nil
56
+ end
60
57
  end
61
- else
62
- klass.class_attribute(*VIEW_ACCESSORS)
63
58
  end
64
59
  end
65
60
 
66
61
  def self.extended(obj)
67
62
  obj.class_eval do
68
63
  attr_accessor(*VIEW_ACCESSORS)
64
+
65
+ remove_method :assets_environment
66
+ def assets_environment
67
+ if env = @assets_environment
68
+ @assets_environment = env.cached
69
+ else
70
+ nil
71
+ end
72
+ end
69
73
  end
70
74
  end
71
75
 
72
76
  def compute_asset_path(path, options = {})
73
- # Check if we are inside Sprockets context before calling check_dependencies!.
74
- check_dependencies!(path) if defined?(depend_on)
77
+ debug = options[:debug]
75
78
 
76
- if digest_path = asset_digest_path(path)
77
- path = digest_path if digest_assets
78
- path += "?body=1" if options[:debug]
79
- File.join(assets_prefix || "/", path)
79
+ if asset_path = resolve_asset_path(path, debug)
80
+ File.join(assets_prefix || "/", legacy_debug_path(asset_path, debug))
80
81
  else
81
82
  super
82
83
  end
83
84
  end
84
85
 
85
- # Computes the full URL to a asset in the public directory. This
86
- # method checks for errors before returning path.
87
- def asset_path(source, options = {})
88
- unless options[:debug]
89
- check_errors_for(source, options)
86
+ # Resolve the asset path against the Sprockets manifest or environment.
87
+ # Returns nil if it's an asset we don't know about.
88
+ def resolve_asset_path(path, allow_non_precompiled = false) #:nodoc:
89
+ resolve_asset do |resolver|
90
+ resolver.asset_path path, digest_assets, allow_non_precompiled
90
91
  end
91
- super(source, options)
92
92
  end
93
- alias :path_to_asset :asset_path
94
93
 
95
- # Get digest for asset path.
94
+ # Expand asset path to digested form.
96
95
  #
97
96
  # path - String path
98
97
  # options - Hash options
99
98
  #
100
- # Returns String Hex digest or nil if digests are disabled.
101
- def asset_digest(path, options = {})
102
- return unless digest_assets
103
-
104
- if digest_path = asset_digest_path(path, options)
105
- digest_path[/-(.+)\./, 1]
99
+ # Returns String path or nil if no asset was found.
100
+ def asset_digest_path(path, options = {})
101
+ resolve_asset do |resolver|
102
+ resolver.digest_path path, options[:debug]
106
103
  end
107
104
  end
108
105
 
109
- # Expand asset path to digested form.
106
+ # Experimental: Get integrity for asset path.
110
107
  #
111
108
  # path - String path
112
109
  # options - Hash options
113
110
  #
114
- # Returns String path or nil if no asset was found.
115
- def asset_digest_path(path, options = {})
116
- if manifest = assets_manifest
117
- if digest_path = manifest.assets[path]
118
- return digest_path
119
- end
120
- end
111
+ # Returns String integrity attribute or nil if no asset was found.
112
+ def asset_integrity(path, options = {})
113
+ path = path_with_extname(path, options)
121
114
 
122
- if environment = assets_environment
123
- if asset = environment[path]
124
- return asset.digest_path
125
- end
115
+ resolve_asset do |resolver|
116
+ resolver.integrity path
126
117
  end
127
118
  end
128
119
 
@@ -131,21 +122,27 @@ module Sprockets
131
122
  # Eventually will be deprecated and replaced by source maps.
132
123
  def javascript_include_tag(*sources)
133
124
  options = sources.extract_options!.stringify_keys
125
+ integrity = compute_integrity?(options)
134
126
 
135
127
  if options["debug"] != false && request_debug_assets?
136
128
  sources.map { |source|
137
- check_errors_for(source, :type => :javascript)
138
- if asset = lookup_asset_for_path(source, :type => :javascript)
139
- asset.to_a.map do |a|
140
- super(path_to_javascript(a.logical_path, :debug => true), options)
129
+ if asset = lookup_debug_asset(source, :type => :javascript)
130
+ if asset.respond_to?(:to_a)
131
+ asset.to_a.map do |a|
132
+ super(path_to_javascript(a.logical_path, :debug => true), options)
133
+ end
134
+ else
135
+ super(path_to_javascript(asset.logical_path, :debug => true), options)
141
136
  end
142
137
  else
143
138
  super(source, options)
144
139
  end
145
140
  }.flatten.uniq.join("\n").html_safe
146
141
  else
147
- sources.push(options)
148
- super(*sources)
142
+ sources.map { |source|
143
+ options = options.merge('integrity' => asset_integrity(source, :type => :javascript)) if integrity
144
+ super source, options
145
+ }.join("\n").html_safe
149
146
  end
150
147
  end
151
148
 
@@ -154,81 +151,205 @@ module Sprockets
154
151
  # Eventually will be deprecated and replaced by source maps.
155
152
  def stylesheet_link_tag(*sources)
156
153
  options = sources.extract_options!.stringify_keys
154
+ integrity = compute_integrity?(options)
155
+
157
156
  if options["debug"] != false && request_debug_assets?
158
157
  sources.map { |source|
159
- check_errors_for(source, :type => :stylesheet)
160
- if asset = lookup_asset_for_path(source, :type => :stylesheet)
161
- asset.to_a.map do |a|
162
- super(path_to_stylesheet(a.logical_path, :debug => true), options)
158
+ if asset = lookup_debug_asset(source, :type => :stylesheet)
159
+ if asset.respond_to?(:to_a)
160
+ asset.to_a.map do |a|
161
+ super(path_to_stylesheet(a.logical_path, :debug => true), options)
162
+ end
163
+ else
164
+ super(path_to_stylesheet(asset.logical_path, :debug => true), options)
163
165
  end
164
166
  else
165
167
  super(source, options)
166
168
  end
167
169
  }.flatten.uniq.join("\n").html_safe
168
170
  else
169
- sources.push(options)
170
- super(*sources)
171
+ sources.map { |source|
172
+ options = options.merge('integrity' => asset_integrity(source, :type => :stylesheet)) if integrity
173
+ super source, options
174
+ }.join("\n").html_safe
171
175
  end
172
176
  end
173
177
 
174
178
  protected
175
- # Ensures the asset is included in the dependencies list.
176
- def check_dependencies!(dep)
177
- depend_on(dep)
178
- depend_on_asset(dep)
179
- rescue Sprockets::FileNotFound
179
+ # This is awkward: `integrity` is a boolean option indicating whether
180
+ # we want to include or omit the subresource integrity hash, but the
181
+ # options hash is also passed through as literal tag attributes.
182
+ # That means we have to delete the shortcut boolean option so it
183
+ # doesn't bleed into the tag attributes, but also check its value if
184
+ # it's boolean-ish.
185
+ def compute_integrity?(options)
186
+ if secure_subresource_integrity_context?
187
+ case options['integrity']
188
+ when nil, false, true
189
+ options.delete('integrity') == true
190
+ end
191
+ else
192
+ options.delete 'integrity'
193
+ false
194
+ end
180
195
  end
181
196
 
182
- # Raise errors when source is not in the precompiled list, or
183
- # incorrectly contains the assets_prefix.
184
- def check_errors_for(source, options)
185
- return unless self.raise_runtime_errors
197
+ # Only serve integrity metadata for HTTPS requests:
198
+ # http://www.w3.org/TR/SRI/#non-secure-contexts-remain-non-secure
199
+ def secure_subresource_integrity_context?
200
+ respond_to?(:request) && self.request && self.request.ssl?
201
+ end
186
202
 
187
- source = source.to_s
188
- return if source.blank? || source =~ URI_REGEXP
203
+ # Enable split asset debugging. Eventually will be deprecated
204
+ # and replaced by source maps in Sprockets 3.x.
205
+ def request_debug_assets?
206
+ debug_assets || (defined?(controller) && controller && params[:debug_assets])
207
+ rescue # FIXME: what exactly are we rescuing?
208
+ false
209
+ end
189
210
 
190
- asset = lookup_asset_for_path(source, options)
211
+ # Internal method to support multifile debugging. Will
212
+ # eventually be removed w/ Sprockets 3.x.
213
+ def lookup_debug_asset(path, options = {})
214
+ path = path_with_extname(path, options)
191
215
 
192
- if asset && asset_needs_precompile?(asset.logical_path, asset.pathname.to_s)
193
- raise AssetFilteredError.new(asset.logical_path)
216
+ resolve_asset do |resolver|
217
+ resolver.find_debug_asset path
194
218
  end
219
+ end
220
+
221
+ # compute_asset_extname is in AV::Helpers::AssetUrlHelper
222
+ def path_with_extname(path, options)
223
+ path = path.to_s
224
+ "#{path}#{compute_asset_extname(path, options)}"
225
+ end
195
226
 
196
- full_prefix = File.join(self.assets_prefix || "/", '')
197
- if !asset && source.start_with?(full_prefix)
198
- short_path = source[full_prefix.size, source.size]
199
- if lookup_asset_for_path(short_path, options)
200
- raise AbsoluteAssetPathError.new(source, short_path, full_prefix)
227
+ # Try each asset resolver and return the first non-nil result.
228
+ def resolve_asset
229
+ asset_resolver_strategies.detect do |resolver|
230
+ if result = yield(resolver)
231
+ break result
201
232
  end
202
233
  end
203
234
  end
204
235
 
205
- # Returns true when an asset will not be available after precompile is run
206
- def asset_needs_precompile?(source, filename)
207
- if assets_environment && assets_environment.send(:matches_filter, precompile || [], source, filename)
208
- false
236
+ # List of resolvers in `config.assets.resolve_with` order.
237
+ def asset_resolver_strategies
238
+ @asset_resolver_strategies ||=
239
+ Array(resolve_assets_with).map do |name|
240
+ HelperAssetResolvers[name].new(self)
241
+ end
242
+ end
243
+
244
+ # Append ?body=1 if debug is on and we're on old Sprockets.
245
+ def legacy_debug_path(path, debug)
246
+ if debug && !using_sprockets4?
247
+ "#{path}?body=1"
209
248
  else
210
- true
249
+ path
211
250
  end
212
251
  end
252
+ end
213
253
 
214
- # Enable split asset debugging. Eventually will be deprecated
215
- # and replaced by source maps in Sprockets 3.x.
216
- def request_debug_assets?
217
- debug_assets || (defined?(controller) && controller && params[:debug_assets])
218
- rescue
219
- return false
254
+ # Use a separate module since Helper is mixed in and we needn't pollute
255
+ # the class namespace with our internals.
256
+ module HelperAssetResolvers #:nodoc:
257
+ def self.[](name)
258
+ case name
259
+ when :manifest
260
+ Manifest
261
+ when :environment
262
+ Environment
263
+ else
264
+ raise ArgumentError, "Unrecognized asset resolver: #{name.inspect}. Expected :manifest or :environment"
220
265
  end
266
+ end
221
267
 
222
- # Internal method to support multifile debugging. Will
223
- # eventually be removed w/ Sprockets 3.x.
224
- def lookup_asset_for_path(path, options = {})
225
- return unless env = assets_environment
226
- path = path.to_s
227
- if extname = compute_asset_extname(path, options)
228
- path = "#{path}#{extname}"
268
+ class Manifest #:nodoc:
269
+ def initialize(view)
270
+ @manifest = view.assets_manifest
271
+ raise ArgumentError, 'config.assets.resolve_with includes :manifest, but app.assets_manifest is nil' unless @manifest
272
+ end
273
+
274
+ def asset_path(path, digest, allow_non_precompiled = false)
275
+ if digest
276
+ digest_path path, allow_non_precompiled
277
+ end
278
+ end
279
+
280
+ def digest_path(path, allow_non_precompiled = false)
281
+ @manifest.assets[path]
282
+ end
283
+
284
+ def integrity(path)
285
+ if meta = metadata(path)
286
+ meta["integrity"]
287
+ end
288
+ end
289
+
290
+ def find_debug_asset(path)
291
+ nil
292
+ end
293
+
294
+ private
295
+ def metadata(path)
296
+ if digest_path = digest_path(path)
297
+ @manifest.files[digest_path]
298
+ end
299
+ end
300
+ end
301
+
302
+ class Environment #:nodoc:
303
+ def initialize(view)
304
+ raise ArgumentError, 'config.assets.resolve_with includes :environment, but app.assets is nil' unless view.assets_environment
305
+ @env = view.assets_environment
306
+ @precompiled_asset_checker = view.precompiled_asset_checker
307
+ end
308
+
309
+ def asset_path(path, digest, allow_non_precompiled = false)
310
+ # Digests enabled? Do the work to calculate the full asset path.
311
+ if digest
312
+ digest_path path, allow_non_precompiled
313
+
314
+ # Otherwise, ask the Sprockets environment whether the asset exists
315
+ # and check whether it's also precompiled for production deploys.
316
+ elsif find_asset(path)
317
+ raise_unless_precompiled_asset path unless allow_non_precompiled
318
+ path
319
+ end
320
+ end
321
+
322
+ def digest_path(path, allow_non_precompiled = false)
323
+ if asset = find_asset(path)
324
+ raise_unless_precompiled_asset path unless allow_non_precompiled
325
+ asset.digest_path
229
326
  end
230
- env[path]
231
327
  end
328
+
329
+ def integrity(path)
330
+ find_asset(path).try :integrity
331
+ end
332
+
333
+ def find_debug_asset(path)
334
+ if asset = find_asset(path, pipeline: :debug)
335
+ raise_unless_precompiled_asset asset.logical_path.sub('.debug', '')
336
+ asset
337
+ end
338
+ end
339
+
340
+ private
341
+ def find_asset(path, options = {})
342
+ @env[path, options]
343
+ end
344
+
345
+ def precompiled?(path)
346
+ @precompiled_asset_checker.call path
347
+ end
348
+
349
+ def raise_unless_precompiled_asset(path)
350
+ raise Helper::AssetNotPrecompiled.new(path) unless precompiled?(path)
351
+ end
352
+ end
232
353
  end
233
354
  end
234
355
  end
@@ -0,0 +1,23 @@
1
+ module Sprockets
2
+ module Rails
3
+ module RouteWrapper
4
+
5
+ def internal_assets_path?
6
+ path =~ %r{\A#{self.class.assets_prefix}\z}
7
+ end
8
+
9
+ def internal?
10
+ super || internal_assets_path?
11
+ end
12
+
13
+ def self.included(klass)
14
+ klass.class_eval do
15
+ def internal_with_sprockets?
16
+ internal_without_sprockets? || internal_assets_path?
17
+ end
18
+ alias_method_chain :internal?, :sprockets
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -16,7 +16,9 @@ module Sprockets
16
16
 
17
17
  def environment
18
18
  if app
19
- app.assets
19
+ # Use initialized app.assets or force build an environment if
20
+ # config.assets.compile is disabled
21
+ app.assets || Sprockets::Railtie.build_environment(app)
20
22
  else
21
23
  super
22
24
  end
@@ -24,7 +26,8 @@ module Sprockets
24
26
 
25
27
  def output
26
28
  if app
27
- File.join(app.root, 'public', app.config.assets.prefix)
29
+ config = app.config
30
+ File.join(config.paths['public'].first, config.assets.prefix)
28
31
  else
29
32
  super
30
33
  end
@@ -46,17 +49,12 @@ module Sprockets
46
49
  end
47
50
  end
48
51
 
49
- def cache_path
50
- if app
51
- "#{app.config.root}/tmp/cache/assets"
52
- else
53
- @cache_path
54
- end
55
- end
56
- attr_writer :cache_path
57
-
58
52
  def define
59
53
  namespace :assets do
54
+ %w( environment precompile clean clobber ).each do |task|
55
+ Rake::Task[task].clear if Rake::Task.task_defined?(task)
56
+ end
57
+
60
58
  # Override this task change the loaded dependencies
61
59
  desc "Load asset compile environment"
62
60
  task :environment do
@@ -73,9 +71,8 @@ module Sprockets
73
71
 
74
72
  desc "Remove old compiled assets"
75
73
  task :clean, [:keep] => :environment do |t, args|
76
- keep = Integer(args.keep || 2)
77
74
  with_logger do
78
- manifest.clean(keep)
75
+ manifest.clean(Integer(args.keep || self.keep))
79
76
  end
80
77
  end
81
78
 
@@ -83,7 +80,6 @@ module Sprockets
83
80
  task :clobber => :environment do
84
81
  with_logger do
85
82
  manifest.clobber
86
- rm_rf cache_path if cache_path
87
83
  end
88
84
  end
89
85
  end
@@ -0,0 +1,11 @@
1
+ require 'sprockets'
2
+
3
+ module Sprockets
4
+ module Rails
5
+ module Utils
6
+ def using_sprockets4?
7
+ Gem::Version.new(Sprockets::VERSION) >= Gem::Version.new('4.0.0')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,5 +1,5 @@
1
1
  module Sprockets
2
2
  module Rails
3
- VERSION = "2.3.3"
3
+ VERSION = "3.0.0"
4
4
  end
5
5
  end