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_components
1, 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 404
s 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:
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 404
s. 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
-
This is a good candidate for
.gitignore
inclusion, assuming you don't want to commit Bower assets. ↩ -
Very easily, thanks to
rubber
! ↩ -
That's the tool I use to generate this site, as described here. ↩