Skip to content

Commit 11dc896

Browse files
committed
Fix stuck assets in dev due to a stale precompiled manifest
If an asset manifest is available, we check it for digests first even if it's stale and a Sprockets environment is available. We want to check the asset environment first and use the manifest only when we're precompiled with no Sprockets environment available.
1 parent bbb44d2 commit 11dc896

File tree

2 files changed

+95
-87
lines changed

2 files changed

+95
-87
lines changed

lib/sprockets/rails/helper.rb

Lines changed: 48 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -73,21 +73,13 @@ def compute_asset_path(path, options = {})
7373
#
7474
# Returns String path or nil if no asset was found.
7575
def asset_digest_path(path, options = {})
76-
if manifest = assets_manifest
77-
if digest_path = manifest.assets[path]
78-
return digest_path
79-
end
80-
end
81-
82-
if environment = assets_environment
83-
if asset = environment[path]
84-
unless options[:debug]
85-
if !precompiled_asset_checker.call(asset.logical_path)
86-
raise AssetNotPrecompiled.new(asset.logical_path)
87-
end
88-
end
89-
return asset.digest_path
76+
if assets_environment
77+
if asset = assets_environment[path]
78+
raise_unless_precompiled_asset asset.logical_path unless options[:debug]
79+
asset.digest_path
9080
end
81+
else
82+
assets_manifest.assets[path]
9183
end
9284
end
9385

@@ -98,42 +90,25 @@ def asset_digest_path(path, options = {})
9890
#
9991
# Returns String integrity attribute or nil if no asset was found.
10092
def asset_integrity(path, options = {})
101-
path = path.to_s
102-
if extname = compute_asset_extname(path, options)
103-
path = "#{path}#{extname}"
104-
end
93+
path = path_with_extname(path, options)
10594

106-
if manifest = assets_manifest
107-
if digest_path = manifest.assets[path]
108-
if metadata = manifest.files[digest_path]
109-
return metadata["integrity"]
110-
end
95+
if assets_environment
96+
if asset = assets_environment[path]
97+
asset.integrity
11198
end
112-
end
113-
114-
if environment = assets_environment
115-
if asset = environment[path]
116-
return asset.integrity
99+
elsif digest_path = assets_manifest.assets[path]
100+
if metadata = assets_manifest.files[digest_path]
101+
metadata["integrity"]
117102
end
118103
end
119-
120-
nil
121104
end
122105

123106
# Override javascript tag helper to provide debugging support.
124107
#
125108
# Eventually will be deprecated and replaced by source maps.
126109
def javascript_include_tag(*sources)
127110
options = sources.extract_options!.stringify_keys
128-
129-
unless request_ssl?
130-
options.delete("integrity")
131-
end
132-
133-
case options["integrity"]
134-
when true, false, nil
135-
compute_integrity = options.delete("integrity")
136-
end
111+
integrity = compute_integrity?(options)
137112

138113
if options["debug"] != false && request_debug_assets?
139114
sources.map { |source|
@@ -151,9 +126,8 @@ def javascript_include_tag(*sources)
151126
}.flatten.uniq.join("\n").html_safe
152127
else
153128
sources.map { |source|
154-
super(source, compute_integrity ?
155-
options.merge("integrity" => asset_integrity(source, :type => :javascript)) :
156-
options)
129+
options = options.merge('integrity' => asset_integrity(source, :type => :javascript)) if integrity
130+
super source, options
157131
}.join("\n").html_safe
158132
end
159133
end
@@ -163,15 +137,7 @@ def javascript_include_tag(*sources)
163137
# Eventually will be deprecated and replaced by source maps.
164138
def stylesheet_link_tag(*sources)
165139
options = sources.extract_options!.stringify_keys
166-
167-
unless request_ssl?
168-
options.delete("integrity")
169-
end
170-
171-
case options["integrity"]
172-
when true, false, nil
173-
compute_integrity = options.delete("integrity")
174-
end
140+
integrity = compute_integrity?(options)
175141

176142
if options["debug"] != false && request_debug_assets?
177143
sources.map { |source|
@@ -189,43 +155,58 @@ def stylesheet_link_tag(*sources)
189155
}.flatten.uniq.join("\n").html_safe
190156
else
191157
sources.map { |source|
192-
super(source, compute_integrity ?
193-
options.merge("integrity" => asset_integrity(source, :type => :stylesheet)) :
194-
options)
158+
options = options.merge('integrity' => asset_integrity(source, :type => :stylesheet)) if integrity
159+
super source, options
195160
}.join("\n").html_safe
196161
end
197162
end
198163

199164
protected
200-
def request_ssl?
165+
def compute_integrity?(options)
166+
if secure_subresource_integrity_context?
167+
case options['integrity']
168+
when nil, false, true
169+
options.delete('integrity') == true
170+
end
171+
else
172+
options.delete 'integrity'
173+
false
174+
end
175+
end
176+
177+
# Only serve integrity metadata for HTTPS requests:
178+
# http://www.w3.org/TR/SRI/#non-secure-contexts-remain-non-secure
179+
def secure_subresource_integrity_context?
201180
respond_to?(:request) && self.request && self.request.ssl?
202181
end
203182

204183
# Enable split asset debugging. Eventually will be deprecated
205184
# and replaced by source maps in Sprockets 3.x.
206185
def request_debug_assets?
207186
debug_assets || (defined?(controller) && controller && params[:debug_assets])
208-
rescue
209-
return false
187+
rescue # FIXME: what exactly are we rescuing?
188+
false
210189
end
211190

212191
# Internal method to support multifile debugging. Will
213192
# eventually be removed w/ Sprockets 3.x.
214193
def lookup_debug_asset(path, options = {})
215-
return unless env = assets_environment
216-
path = path.to_s
217-
if extname = compute_asset_extname(path, options)
218-
path = "#{path}#{extname}"
194+
if assets_environment && asset = assets_environment[path_with_extname(path, options), pipeline: :debug]
195+
raise_unless_precompiled_asset asset.logical_path.sub('.debug', '')
196+
asset
219197
end
198+
end
220199

221-
if asset = env[path, pipeline: :debug]
222-
original_path = asset.logical_path.gsub('.debug', '')
223-
unless precompiled_asset_checker.call(original_path)
224-
raise AssetNotPrecompiled.new(original_path)
225-
end
200+
def raise_unless_precompiled_asset(logical_path)
201+
if !precompiled_asset_checker.call(logical_path)
202+
raise AssetNotPrecompiled.new(logical_path)
226203
end
204+
end
227205

228-
asset
206+
# compute_asset_extname is in AV::Helpers::AssetUrlHelper
207+
def path_with_extname(path, options)
208+
path = path.to_s
209+
"#{path}#{compute_asset_extname(path, options)}"
229210
end
230211
end
231212
end

test/test_helper.rb

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ def setup
3434
@assets.context_class.assets_prefix = @view.assets_prefix
3535
@assets.context_class.config = @view.config
3636

37+
@foo_js_integrity = @assets['foo.js'].integrity
38+
@foo_css_integrity = @assets['foo.css'].integrity
39+
3740
@foo_js_digest = @assets['foo.js'].etag
3841
@foo_css_digest = @assets['foo.css'].etag
3942
@bar_js_digest = @assets['bar.js'].etag
@@ -391,14 +394,14 @@ def test_javascript_include_tag_integrity
391394
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js"></script>),
392395
@view.javascript_include_tag("foo", integrity: nil)
393396

394-
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>),
397+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="#{@foo_js_integrity}"></script>),
395398
@view.javascript_include_tag("foo", integrity: true)
396-
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>),
399+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="#{@foo_js_integrity}"></script>),
397400
@view.javascript_include_tag("foo.js", integrity: true)
398-
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>),
401+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="#{@foo_js_integrity}"></script>),
399402
@view.javascript_include_tag(:foo, integrity: true)
400403

401-
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>\n<script src="/assets/bar-#{@bar_js_digest}.js" integrity="sha256-g0JYFeYSYGXe376R0JrRzS6CpYpC1HiqtwBsVt/XAWU="></script>),
404+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="#{@foo_js_integrity}"></script>\n<script src="/assets/bar-#{@bar_js_digest}.js" integrity="sha256-g0JYFeYSYGXe376R0JrRzS6CpYpC1HiqtwBsVt/XAWU="></script>),
402405
@view.javascript_include_tag(:foo, :bar, integrity: true)
403406
end
404407

@@ -410,14 +413,14 @@ def test_stylesheet_link_tag_integrity
410413
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" />),
411414
@view.stylesheet_link_tag("foo", integrity: nil)
412415

413-
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="sha256-5YzTQPuOJz/EpeXfN/+v1sxsjAj/dw8q26abiHZM3A4=" />),
416+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="#{@foo_css_integrity}" />),
414417
@view.stylesheet_link_tag("foo", integrity: true)
415-
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="sha256-5YzTQPuOJz/EpeXfN/+v1sxsjAj/dw8q26abiHZM3A4=" />),
418+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="#{@foo_css_integrity}" />),
416419
@view.stylesheet_link_tag("foo.css", integrity: true)
417-
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="sha256-5YzTQPuOJz/EpeXfN/+v1sxsjAj/dw8q26abiHZM3A4=" />),
420+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="#{@foo_css_integrity}" />),
418421
@view.stylesheet_link_tag(:foo, integrity: true)
419422

420-
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="sha256-5YzTQPuOJz/EpeXfN/+v1sxsjAj/dw8q26abiHZM3A4=" />\n<link href="/assets/bar-#{@bar_css_digest}.css" media="screen" rel="stylesheet" integrity="sha256-Vd370+VAW4D96CVpZcjFLXyeHoagI0VHwofmzRXetuE=" />),
423+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="#{@foo_css_integrity}" />\n<link href="/assets/bar-#{@bar_css_digest}.css" media="screen" rel="stylesheet" integrity="sha256-Vd370+VAW4D96CVpZcjFLXyeHoagI0VHwofmzRXetuE=" />),
421424
@view.stylesheet_link_tag(:foo, :bar, integrity: true)
422425
end
423426

@@ -636,12 +639,8 @@ def setup
636639
@manifest.assets["foo.js"] = "foo-#{@foo_js_digest}.js"
637640
@manifest.assets["foo.css"] = "foo-#{@foo_css_digest}.css"
638641

639-
@manifest.files["foo-#{@foo_js_digest}.js"] = {
640-
"integrity" => "sha-256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="
641-
}
642-
@manifest.files["foo-#{@foo_css_digest}.css"] = {
643-
"integrity" => "sha-256-5YzTQPuOJz/EpeXfN/+v1sxsjAj/dw8q26abiHZM3A4="
644-
}
642+
@manifest.files["foo-#{@foo_js_digest}.js"] = { "integrity" => @foo_js_integrity }
643+
@manifest.files["foo-#{@foo_css_digest}.css"] = { "integrity" => @foo_css_integrity }
645644

646645
@view.digest_assets = true
647646
@view.assets_environment = nil
@@ -673,22 +672,22 @@ def test_stylesheet_link_tag
673672
def test_javascript_include_tag_integrity
674673
super
675674

676-
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="sha-256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>),
675+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="#{@foo_js_integrity}"></script>),
677676
@view.javascript_include_tag("foo", integrity: true)
678-
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="sha-256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>),
677+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="#{@foo_js_integrity}"></script>),
679678
@view.javascript_include_tag("foo.js", integrity: true)
680-
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="sha-256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>),
679+
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="#{@foo_js_integrity}"></script>),
681680
@view.javascript_include_tag(:foo, integrity: true)
682681
end
683682

684683
def test_stylesheet_link_tag_integrity
685684
super
686685

687-
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="sha-256-5YzTQPuOJz/EpeXfN/+v1sxsjAj/dw8q26abiHZM3A4=" />),
686+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="#{@foo_css_integrity}" />),
688687
@view.stylesheet_link_tag("foo", integrity: true)
689-
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="sha-256-5YzTQPuOJz/EpeXfN/+v1sxsjAj/dw8q26abiHZM3A4=" />),
688+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="#{@foo_css_integrity}" />),
690689
@view.stylesheet_link_tag("foo.css", integrity: true)
691-
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="sha-256-5YzTQPuOJz/EpeXfN/+v1sxsjAj/dw8q26abiHZM3A4=" />),
690+
assert_dom_equal %(<link href="/assets/foo-#{@foo_css_digest}.css" media="screen" rel="stylesheet" integrity="#{@foo_css_integrity}" />),
692691
@view.stylesheet_link_tag(:foo, integrity: true)
693692
end
694693

@@ -728,6 +727,34 @@ def test_stylesheet_link_tag_integrity
728727
end
729728
end
730729

730+
class StaleManifestVsEnvironmentHelperTest < HelperTest
731+
def setup
732+
super
733+
734+
@view.digest_assets = true
735+
736+
@view.assets_manifest = Sprockets::Manifest.new(@assets, FIXTURES_PATH).tap do |stale|
737+
stale.assets["foo.js"] = "foo-stale.js"
738+
@manifest.files["foo-stale.js"] = { "integrity" => "stale-manifest" }
739+
@manifest.files["foo-#{@foo_js_digest}.js"] = { "integrity" => "current-manifest" }
740+
741+
stale.assets["foo.css"] = "foo-stale.css"
742+
@manifest.files["foo-stale.css"] = { "integrity" => "stale-manifest" }
743+
@manifest.files["foo-#{@foo_css_digest}.css"] = { "integrity" => "current-manifest" }
744+
end
745+
end
746+
747+
def test_digest_prefers_asset_environment_over_manifest
748+
assert_equal "foo-#{@foo_js_digest}.js", @view.asset_digest_path("foo.js")
749+
assert_equal "foo-#{@foo_css_digest}.css", @view.asset_digest_path("foo.css")
750+
end
751+
752+
def test_digest_prefers_asset_environment_over_manifest
753+
assert_equal @foo_js_integrity, @view.asset_integrity("foo.js")
754+
assert_equal @foo_css_integrity, @view.asset_integrity("foo.css")
755+
end
756+
end
757+
731758
class AssetUrlHelperLinksTarget < HelperTest
732759
def test_precompile_allows_links
733760
@view.assets_precompile = ["url.css"]

0 commit comments

Comments
 (0)