Closed richjdsmith closed 5 years ago
If someone else lands here with a similar problem. Here's how I've approached the issue. The following solution works with Webpacker, whether the dev server is running or the assets have been compiled to my public/ directory.
InlineSvg allows for creation of custom asset finders so that's what this does.
I'm fairly certain there may be a cleaner way to do this, but this works.
# In config/initializers/inline_svg.rb
class WebpackerAssetFinder
class FoundAsset
attr_reader :path
def initialize(path)
@path = path
end
def pathname
if Webpacker.dev_server.running?
# The dev server is running. Load the SVG into a Tempfile.
asset = open("#{Webpacker.dev_server.protocol}://#{Webpacker.dev_server.host_with_port}#{path}")
tempfile = Tempfile.new(path)
tempfile.binmode
tempfile.write(asset.read)
tempfile.rewind
tempfile
else
# The dev server isn't running. The asset will be compiled to public.
Rails.application.root.join 'public', path.gsub(/\A\//, '')
end
end
end
def find_asset(filename)
if webpack_asset_path = Webpacker.manifest.lookup(filename)
return FoundAsset.new(webpack_asset_path)
end
end
end
# Override the Sprockets asset finder.
InlineSvg.configuration.asset_finder = WebpackerAssetFinder.new
@moxie thanks for posting that solution, it seems like a perfect use of the custom asset finder. ✨
It seems the problem is related to the fact that Webpacker assets are served from a separate server in development mode. I guess another solution would be to only use your custom asset finder in Rails' development mode.
I'm going to close this out for now.
I also ran into this. To get around it I had to make some changes to the solution @moxie posted above.
First, ensure you don't use asset_pack_path
in your inline_svg
call, otherwise the asset will not be found in the webpack manifest.
The next issue I had (likely because I have some nested paths), is that the creation of the Tempfile
failed (and was caught by the gem, so the issue was hard to track down). I opted to leave off the path
and just let it generate a unique filename for me.
Lastly, since I depend on both webpacker and the asset pipeline, I ended up with a fallback approach.
Here's what I ended up with:
class WebpackerAssetFinder
class FoundAsset
attr_reader :path
def initialize(path)
@path = path
end
def pathname
if Webpacker.dev_server.running?
asset = open(webpacker_dev_server_url)
begin
tempfile = Tempfile.new(path)
tempfile.binmode
tempfile.write(asset.read)
tempfile.rewind
tempfile
rescue StandardError => e
Rails.logger.error "Error creating tempfile: #{e}"
raise
end
else
Rails.application.root.join("public", path.gsub(/\A\//, ""))
end
end
private
def webpacker_dev_server_url
"#{Webpacker.dev_server.protocol}://#{Webpacker.dev_server.host_with_port}#{path}"
end
end
def find_asset(filename)
if webpack_asset_path = Webpacker.manifest.lookup(filename)
FoundAsset.new(webpack_asset_path)
end
end
end
Then in the config/initializers/inline_svg.rb
initializer:
class FallbackAssetFinder
attr_reader :finders
def initialize(finders)
@finders = finders
end
def find_asset(filename)
# try each finder, in order, return the first path found
finders.lazy.map { |f| f.find_asset(filename) }.find { |x| x != nil }
end
end
# Some of our SVG assets will be in the asset pipeline, some from webpacker,
# This allows us to find the SVGs for inlining in both cases.
InlineSvg.configuration.asset_finder = FallbackAssetFinder.new([
WebpackerAssetFinder.new,
InlineSvg::StaticAssetFinder,
])
Hope this helps someone else trying to do the same thing. Thanks to @moxie for providing the basis for this workaround.
Since upgrading to Rails 6.0 RC1, neither @moxie or @subdigital solutions work. Not entirely sure why either.
Added support for Webpacker in v1.5.0.
Webpacker is "opt-in" as of v1.5.2 because it was breaking users that were using both Sprockets with precompiled assets and Webpacker: https://github.com/jamesmartin/inline_svg/issues/98#issuecomment-503774840
In case anyone comes across this, I've been fighting this config today and was able to get it working finally for Rail v6.0.0. The general idea of the previous exmples works, but I had to adapt it in two ways:
Finding the asset
to support identifying the asset with or without the media/images
path segments, I had to modify the lookup. You could do this in various more straightforward ways... but given I had the hashie
dependency already, I went with the following quick update to maintain consistency with the path formats accepted by the asset_path_tag
helper
require 'hashie'
def find_asset(filename)
assets = Hashie::Rash.new(Webpacker.manifest.instance_variable_get('@data'))
webpack_asset_path = assets[%r{(media\/images\/)?#{filename}}]
return unless webpack_asset_path.present?
WebpackerAssetFinder::FoundAsset.new(webpack_asset_path)
end
Reading the svg data
I was seeing this error when attempting acceses the resource on the dev server via open
as in the above examples despite it being the correct location (I can visit it directly and see the content).
Errno::ENOENT: No such file or directory @ rb_sysopen http://localhost:3035/packs/media/i.....
So, I make a request and get the data from the reponse...
def pathname
# The dev server is running. Load the SVG into a Tempfile.
if Webpacker.dev_server.running?
http = Net::HTTP.new(Webpacker.config.dev_server[:host], Webpacker.config.dev_server[:port])
resp = http.get(path)
return unless resp.code == '200'
tempfile = Tempfile.new(path)
tempfile.binmode
tempfile.write(resp.body)
tempfile.rewind
tempfile
# The dev server isn't running. The asset will be compiled to public.
else
Rails.application.root.join 'public', path.gsub(%r{\A\/}, '')
end
end
convoluted, yes...works, yes.
I personally don't want to support finding sprockets compiled assets, but extending this with @subdigital nice fallback approach would be straightforward.
Seeing as a lot of other people have looked into this, I figured I might as well tack on my own solution. It seems like the inline_svg implementation of the webpack svg resolver is similar to many of the examples here, so this is all it took for me to get it working properly in my own setup:
class AppWebpackAssetFinder < InlineSvg::WebpackAssetFinder
def initialize(filename)
super(filename)
@asset_path = Webpacker.manifest.lookup("media/svg/#{@filename}")
end
end
class FallbackAssetFinder
attr_reader :finders
def initialize(finders)
@finders = finders
end
def find_asset(filename)
# try each finder, in order, return the first path found
finders.lazy.map { |f| f.find_asset(filename) }.find { |x| x != nil }
end
end
# Some of our SVG assets will be in the asset pipeline, some from webpacker,
# This allows us to find the SVGs for inlining in both cases.
InlineSvg.configuration.asset_finder = FallbackAssetFinder.new([
AppWebpackAssetFinder,
InlineSvg::StaticAssetFinder,
])
Looks like this gem struggles to handle files served from Rails Webpacker?
I have this inline
<%= inline_svg("#{asset_pack_path('images/grapes.svg')}") %>
But it results in a no asset found error.
When I actually visit the path, the image is there. I'll take a look and see if I can do a PR on this.