Skip to content

Commit e003cfd

Browse files
committed
Merge pull request #278 from jeremy/explicit-asset-lookup-locations
Restore manifest/env asset lookup order and make it configurable
2 parents 76d1f65 + a955c16 commit e003cfd

File tree

5 files changed

+199
-71
lines changed

5 files changed

+199
-71
lines changed

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,6 @@ config.assets.version = 'v2'
7474

7575
Defaults to `/assets`. Changes the directory to compile assets to.
7676

77-
**`config.assets.manifest`**
78-
79-
Defines the full path to be used for the asset precompiler's manifest file. Defaults to a randomly-generated filename in the `config.assets.prefix` directory within the public folder.
80-
8177
**`config.assets.digest`**
8278

8379
When enabled, fingerprints will be added to asset filenames.
@@ -108,6 +104,24 @@ config.assets.configure do |env|
108104
end
109105
```
110106

107+
**`config.assets.resolve_with`**
108+
109+
A list of `:environment` and `:manifest` symbols that defines the order that
110+
we try to find assets: manifest first, environment second? Manifest only?
111+
112+
By default, we check the manifest first if asset digests are enabled and debug
113+
is not enabled, then we check the environment if compiling is enabled:
114+
```
115+
# Dev where debug is true, or digests are disabled
116+
%i[ environment ]
117+
118+
# Dev default, or production with compile enabled.
119+
%i[ manifest environment ]
120+
121+
# Production default.
122+
%i[ manifest ]
123+
```
124+
111125

112126
## Complementary plugins
113127

lib/sprockets/rails/helper.rb

Lines changed: 110 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ def initialize(source)
2020
include ActionView::Helpers::AssetTagHelper
2121
include Sprockets::Rails::Utils
2222

23-
VIEW_ACCESSORS = [:assets_environment, :assets_manifest,
24-
:assets_precompile, :precompiled_asset_checker,
25-
:assets_prefix, :digest_assets, :debug_assets]
23+
VIEW_ACCESSORS = [
24+
:assets_environment, :assets_manifest,
25+
:assets_precompile, :precompiled_asset_checker,
26+
:assets_prefix, :digest_assets, :debug_assets,
27+
:resolve_assets_with
28+
]
2629

2730
def self.included(klass)
2831
klass.class_attribute(*VIEW_ACCESSORS)
@@ -73,13 +76,8 @@ def compute_asset_path(path, options = {})
7376
#
7477
# Returns String path or nil if no asset was found.
7578
def asset_digest_path(path, options = {})
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
80-
end
81-
else
82-
assets_manifest.assets[path]
79+
resolve_asset do |resolver|
80+
resolver.digest_path path, options[:debug]
8381
end
8482
end
8583

@@ -92,14 +90,8 @@ def asset_digest_path(path, options = {})
9290
def asset_integrity(path, options = {})
9391
path = path_with_extname(path, options)
9492

95-
if assets_environment
96-
if asset = assets_environment[path]
97-
asset.integrity
98-
end
99-
elsif digest_path = assets_manifest.assets[path]
100-
if metadata = assets_manifest.files[digest_path]
101-
metadata["integrity"]
102-
end
93+
resolve_asset do |resolver|
94+
resolver.integrity path, options[:debug]
10395
end
10496
end
10597

@@ -197,15 +189,10 @@ def request_debug_assets?
197189
# Internal method to support multifile debugging. Will
198190
# eventually be removed w/ Sprockets 3.x.
199191
def lookup_debug_asset(path, options = {})
200-
if assets_environment && asset = assets_environment[path_with_extname(path, options), pipeline: :debug]
201-
raise_unless_precompiled_asset asset.logical_path.sub('.debug', '')
202-
asset
203-
end
204-
end
192+
path = path_with_extname(path, options)
205193

206-
def raise_unless_precompiled_asset(logical_path)
207-
if !precompiled_asset_checker.call(logical_path)
208-
raise AssetNotPrecompiled.new(logical_path)
194+
resolve_asset do |resolver|
195+
resolver.find_debug_asset path, options[:debug]
209196
end
210197
end
211198

@@ -214,6 +201,103 @@ def path_with_extname(path, options)
214201
path = path.to_s
215202
"#{path}#{compute_asset_extname(path, options)}"
216203
end
204+
205+
# Try each asset resolver and return the first non-nil result.
206+
def resolve_asset
207+
asset_resolver_strategies.detect do |resolver|
208+
if result = yield(resolver)
209+
break result
210+
end
211+
end
212+
end
213+
214+
# List of resolvers in `config.assets.resolve_with` order.
215+
def asset_resolver_strategies
216+
@asset_resolver_strategies ||=
217+
Array(resolve_assets_with).map do |name|
218+
HelperAssetResolvers[name].new(self)
219+
end
220+
end
221+
end
222+
223+
# Use a separate module since Helper is mixed in and we needn't pollute
224+
# the class namespace with our internals.
225+
module HelperAssetResolvers #:nodoc:
226+
def self.[](name)
227+
case name
228+
when :manifest
229+
Manifest
230+
when :environment
231+
Environment
232+
else
233+
raise ArgumentError, "Unrecognized asset resolver: #{name.inspect}. Expected :manifest or :environment"
234+
end
235+
end
236+
237+
class Manifest #:nodoc:
238+
def initialize(view)
239+
@manifest = view.assets_manifest
240+
raise ArgumentError, 'config.assets.resolve_with includes :manifest, but app.assets_manifest is nil' unless @manifest
241+
end
242+
243+
def digest_path(path, debug = false)
244+
@manifest.assets[path]
245+
end
246+
247+
def integrity(path, debug = false)
248+
if meta = metadata(path, debug)
249+
meta["integrity"]
250+
end
251+
end
252+
253+
def find_debug_asset(path, debug = false)
254+
nil
255+
end
256+
257+
private
258+
def metadata(path, debug = false)
259+
if digest_path = digest_path(path, debug)
260+
@manifest.files[digest_path]
261+
end
262+
end
263+
end
264+
265+
class Environment #:nodoc:
266+
def initialize(view)
267+
raise ArgumentError, 'config.assets.resolve_with includes :environment, but app.assets is nil' unless view.assets_environment
268+
@env = view.assets_environment
269+
@precompiled_asset_checker = view.precompiled_asset_checker
270+
end
271+
272+
def digest_path(path, debug = false)
273+
if asset = find_asset(path)
274+
raise_unless_precompiled_asset asset.logical_path unless debug
275+
asset.digest_path
276+
end
277+
end
278+
279+
def integrity(path, debug = false)
280+
find_asset(path).try :integrity
281+
end
282+
283+
def find_debug_asset(path, options = {})
284+
if asset = find_asset(path, pipeline: :debug)
285+
raise_unless_precompiled_asset asset.logical_path.sub('.debug', '')
286+
asset
287+
end
288+
end
289+
290+
private
291+
def find_asset(path, options = {})
292+
@env[path, options]
293+
end
294+
295+
def raise_unless_precompiled_asset(logical_path)
296+
if !@precompiled_asset_checker.call(logical_path)
297+
raise Helper::AssetNotPrecompiled.new(logical_path)
298+
end
299+
end
300+
end
217301
end
218302
end
219303
end

lib/sprockets/railtie.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,15 @@ def self.build_manifest(app)
166166
mount app.assets => config.assets.prefix
167167
end
168168
end
169+
169170
app.assets_manifest = build_manifest(app)
170171

172+
if config.assets.resolve_with.nil?
173+
config.assets.resolve_with = []
174+
config.assets.resolve_with << :manifest if config.assets.digest && !config.assets.debug
175+
config.assets.resolve_with << :environment if config.assets.compile
176+
end
177+
171178
ActionDispatch::Routing::RouteWrapper.class_eval do
172179
class_attribute :assets_prefix
173180

@@ -192,6 +199,8 @@ def self.build_manifest(app)
192199
self.assets_environment = app.assets
193200
self.assets_manifest = app.assets_manifest
194201

202+
self.resolve_assets_with = config.assets.resolve_with
203+
195204
# Expose the app precompiled asset check to the view
196205
self.precompiled_asset_checker = -> logical_path { app.asset_precompiled? logical_path }
197206
end

test/test_helper.rb

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ def setup
2121

2222
@view = ActionView::Base.new
2323
@view.extend ::Sprockets::Rails::Helper
24-
@view.assets_environment = @assets
25-
@view.assets_manifest = @manifest
26-
@view.assets_prefix = "/assets"
27-
@view.assets_precompile = %w( manifest.js )
24+
@view.assets_environment = @assets
25+
@view.assets_manifest = @manifest
26+
@view.resolve_assets_with = [ :manifest, :environment ]
27+
@view.assets_prefix = "/assets"
28+
@view.assets_precompile = %w( manifest.js )
2829
precompiled_assets = @manifest.find(@view.assets_precompile).map(&:logical_path)
2930
@view.precompiled_asset_checker = -> logical_path { precompiled_assets.include? logical_path }
3031
@view.request = ActionDispatch::Request.new({
@@ -36,6 +37,7 @@ def setup
3637

3738
@foo_js_integrity = @assets['foo.js'].integrity
3839
@foo_css_integrity = @assets['foo.css'].integrity
40+
@bar_js_integrity = @assets['bar.js'].integrity
3941

4042
@foo_js_digest = @assets['foo.js'].etag
4143
@foo_css_digest = @assets['foo.css'].etag
@@ -401,7 +403,7 @@ def test_javascript_include_tag_integrity
401403
assert_dom_equal %(<script src="/assets/foo-#{@foo_js_digest}.js" integrity="#{@foo_js_integrity}"></script>),
402404
@view.javascript_include_tag(:foo, integrity: true)
403405

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>),
406+
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="#{@bar_js_integrity}"></script>),
405407
@view.javascript_include_tag(:foo, :bar, integrity: true)
406408
end
407409

@@ -645,6 +647,7 @@ def setup
645647
@view.digest_assets = true
646648
@view.assets_environment = nil
647649
@view.assets_manifest = @manifest
650+
@view.resolve_assets_with = [ :manifest ]
648651
end
649652

650653
def test_javascript_include_tag
@@ -727,32 +730,48 @@ def test_stylesheet_link_tag_integrity
727730
end
728731
end
729732

730-
class StaleManifestVsEnvironmentHelperTest < HelperTest
733+
class AssetResolverOrderingTest < HelperTest
731734
def setup
732735
super
733736

734737
@view.digest_assets = true
735738

736739
@view.assets_manifest = Sprockets::Manifest.new(@assets, FIXTURES_PATH).tap do |stale|
737740
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" }
741+
stale.files["foo-stale.js"] = { "integrity" => "stale-manifest" }
740742

741743
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+
stale.files["foo-stale.css"] = { "integrity" => "stale-manifest" }
744745
end
745746
end
746747

747748
def test_digest_prefers_asset_environment_over_manifest
749+
@view.resolve_assets_with = [ :environment, :manifest ]
750+
748751
assert_equal "foo-#{@foo_js_digest}.js", @view.asset_digest_path("foo.js")
749752
assert_equal "foo-#{@foo_css_digest}.css", @view.asset_digest_path("foo.css")
750-
end
751753

752-
def test_digest_prefers_asset_environment_over_manifest
753754
assert_equal @foo_js_integrity, @view.asset_integrity("foo.js")
754755
assert_equal @foo_css_integrity, @view.asset_integrity("foo.css")
755756
end
757+
758+
def test_try_resolvers_until_first_result
759+
@view.resolve_assets_with = [ :manifest, :environment ]
760+
761+
assert_equal 'foo-stale.js', @view.asset_digest_path('foo.js')
762+
assert_equal "bar-#{@bar_js_digest}.js", @view.asset_digest_path('bar.js')
763+
assert_nil @view.asset_digest_path('nonexistent')
764+
765+
assert_equal 'stale-manifest', @view.asset_integrity('foo.js')
766+
assert_equal @bar_js_integrity, @view.asset_integrity('bar.js')
767+
assert_nil @view.asset_integrity('nonexistent')
768+
end
769+
770+
def test_obeys_asset_resolver_order
771+
@view.resolve_assets_with = []
772+
assert_nil @view.asset_digest_path('foo.js')
773+
assert_nil @view.asset_integrity('foo.js')
774+
end
756775
end
757776

758777
class AssetUrlHelperLinksTarget < HelperTest

0 commit comments

Comments
 (0)