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