Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webpacker #91

Closed
richjdsmith opened this issue Nov 22, 2018 · 8 comments
Closed

Webpacker #91

richjdsmith opened this issue Nov 22, 2018 · 8 comments

Comments

@richjdsmith
Copy link

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.

@moxie
Copy link

moxie commented Dec 14, 2018

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

@jamesmartin
Copy link
Owner

@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.

@subdigital
Copy link
Contributor

subdigital commented Dec 17, 2018

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.

@richjdsmith
Copy link
Author

Since upgrading to Rails 6.0 RC1, neither @moxie or @subdigital solutions work. Not entirely sure why either.

@jamesmartin
Copy link
Owner

Added support for Webpacker in v1.5.0.

@jamesmartin
Copy link
Owner

Webpacker is "opt-in" as of v1.5.2 because it was breaking users that were using both Sprockets with precompiled assets and Webpacker: #98 (comment)

@carolineartz
Copy link

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:

  1. 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
  2. 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.

@growpathjadamson
Copy link

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,
])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants