Persuading Rails to Serve All Bower Assets

As mentioned here at least once before, I'm a big fan of Bower for handling front-end assets in Rails and Middleman sites. It works particularly well for stylesheets and JavaScript: bower install a resource, wire up the stylesheets and JavaScript in the application.css.scss and application.js manifests respectively, and you're done.

It doesn't work quite so easily with other asset types; namely images and fonts. This is what I had to do to incorporate Bower-loaded Slick's assets into a Rails application:

bower-rails gem

Use the bower-rails gem to manage Bower:

# Gemfile
gem 'bower-rails'

We'll use this to download Bower assets instead of invoking bower directly. Annoyingly, it ignores any directory preference you might have in a .bowerrc, and instead is hardcoded to download Bower assets to vendor/assets/bower_components1, but this annoyance turns out to be worth tolerating due to how otherwise useful bower-rails is.

Given a bower.json file:

// bower.json
{
  "name": "bloggy",
  "dependencies": {
    "slick.js": "~1.5.5",
    "jquery": "~2.1.4"
  }
}

We can use the gem's embedded rake task to download the assets into the vendor directory mentioned above:

bundle exec rake bower:install

The Bower asset directories will then be visible to the Rails asset pipeline, so long as this line is added to the app's configuration:

 # config/application.rb
config.assets.paths << Rails.root.join(* %w(vendor assets bower_components))

At this point, stylesheets and JavaScript can be added to the application manifests in the usual way, and those asset types should work without issue:

// app/assets/stylesheets/application.css.scss
@import "slick.js/slick/slick";
@import "slick.js/slick/slick-theme";
// app/assets/javascripts/application.js

//= require jquery
//= require slick.js/slick/slick

So far, so good.

Images and Fonts

Now to tackle the real reason for this post. If at this point, you add a Slick carousel to a view, and request the page, you'll encounter some 404s for image files and fonts. This is because slick-theme.scss references those asset types in styling:

// vendor/assets/bower_components/slick.js/slick/slick-theme.scss
.slick-list {
  .slick-loading & {
    background: #fff slick-image-url("ajax-loader.gif");
  }
}

We need to get those assets into the asset pipeline. bower-rails to the rescue:

bundle exec rake bower:resolve

This operation creates an erb parallel copy of the rendered scss file with the url directives replaced with Rails asset_path invocations:

// vendor/assets/bower_components/slick.js/slick/slick-theme.css.erb
.slick-loading .slick-list
{
   background: #fff url(<%= asset_path 'slick.js/slick/ajax-loader.gif' %>);
}

The final necessary step is to whitelist these other asset types for precompilation in the asset initializer:

# config/initializers/assets.rb
types = %w( *.png *.gif *.jpg *.eot *.woff *.ttf )
Rails.application.config.assets.precompile += types

Happy in Production

We can deploy to an AWS instance2, and observe that the Rails app assets and the Bower assets both coexist in the asset pipeline:

Bower assets served in production

Addendum I: Ambiguity

There's an issue with Slick specifically in that, modulo the file extension, @import "slick.js/slick/slick-theme"; is ambiguous:

$ tree vendor/assets/bower_components/slick.js/slick
vendor/assets/bower_components/slick.js/slick
├── ajax-loader.gif
├── config.rb
├── fonts
│   ├── slick.eot
│   ├── slick.svg
│   ├── slick.ttf
│   └── slick.woff
├── slick-theme.css.erb  # This one?
├── slick-theme.scss     # Or this one?
├── slick.css
├── slick.js
├── slick.min.js
└── slick.scss

Sure enough, Sass complains about this in the request logs:

It's not clear which file to import for "slick.js/slick/slick-theme".
Candidates:
  slick.js/slick/slick-theme.css.erb
  slick.js/slick/slick-theme.scss

For now I'll choose slick-theme.css.erb.
This will be an error in future versions of Sass.

If I try to disambiguate using the file extensions, the erb version is never picked up – resulting in asset 404s. So far, this works out okay as is, but it's a potential issue for the future, depending on the way a given Bower package is structured.

Addendum II: Middleman

The image/font problem described here also applies in the context of Middleman3 sites. Given the small scope of typical Middleman projects, I typically resolve it there by instructing sprockets to manually import necessary assets on an asset-by-asset basis:

set :css_dir,    'stylesheets'
set :js_dir,     'javascripts'
set :images_dir, 'assets'

bower_directory = 'bower_components'
compass_config do |config|
  config.add_import_path "../#{bower_directory}"
  config.output_style = :compact
end
sprockets.append_path File.join root, bower_directory

# Repeat as necessary for each asset file.
sprockets.import_asset('foundation-icon-fonts/foundation-icons.woff') do |p|
  "#{images_dir}/foundation-icons.woff"
end

  1. This is a good candidate for .gitignore inclusion, assuming you don't want to commit Bower assets. 

  2. Very easily, thanks to rubber

  3. That's the tool I use to generate this site, as described here