Posted over 7 years ago. Visible to the public. Repeats.

Rails asset pipeline: Why things break in production and what precompilation does to your assets

When you work with a Rails 3.1+ application, you will be working with the asset pipeline. The asset pipeline is awesome until you deploy. Then it will be less so if you haven't done everything as the pipeline expected it from you.

The problem

When using the asset pipeline your assets (images, javascripts, stylesheets, fonts) live in folders inside app:

Copy
app/assets/fonts app/assets/images app/assets/javascripts app/assets/stylesheets

With the asset pipeline, you can use the full power of Ruby to generate assets. E.g. you can have ERB tags in your Javascript. Or you can have an ERB template which generates Haml which generates HTML. You can chain as many preprocessors as you want.

When you deploy, Rails runs assets:precompile which precompiles all assets into static files that live in public/assets. This way you have all the performance of static files with all the expressiveness of Ruby.

Unfortunately, the path of every single file changes during precompilation. This means that you have to be a bit careful how you reference assets.

Copy
.tile background-image: url('../images/foo.png')

won't work any longer, because there is no public/assets/images. foo.png now lives directly in public/assets.

Example

Copy
app/assets/fonts app/assets/fonts/fonts_root.css app/assets/fonts/fonts_root.js app/assets/fonts/fonts_root.png app/assets/fonts/fonts_root.ttf app/assets/fonts/subfolder app/assets/fonts/subfolder/fonts_subfolder.css app/assets/fonts/subfolder/fonts_subfolder.js app/assets/fonts/subfolder/fonts_subfolder.png app/assets/fonts/subfolder/fonts_subfolder.ttf app/assets/images app/assets/images/images_root.css app/assets/images/images_root.js app/assets/images/images_root.png app/assets/images/images_root.ttf app/assets/images/subfolder app/assets/images/subfolder/images_subfolder.css app/assets/images/subfolder/images_subfolder.js app/assets/images/subfolder/images_subfolder.png app/assets/images/subfolder/images_subfolder.ttf app/assets/javascripts app/assets/javascripts/application.js app/assets/javascripts/javascripts_root.css app/assets/javascripts/javascripts_root.js app/assets/javascripts/javascripts_root.png app/assets/javascripts/javascripts_root.ttf app/assets/javascripts/subfolder app/assets/javascripts/subfolder/javascripts_subfolder.css app/assets/javascripts/subfolder/javascripts_subfolder.js app/assets/javascripts/subfolder/javascripts_subfolder.png app/assets/javascripts/subfolder/javascripts_subfolder.ttf app/assets/stylesheets app/assets/stylesheets/application.css app/assets/stylesheets/stylesheets_root.css app/assets/stylesheets/stylesheets_root.js app/assets/stylesheets/stylesheets_root.png app/assets/stylesheets/stylesheets_root.ttf app/assets/stylesheets/subfolder app/assets/stylesheets/subfolder/stylesheets_root.png app/assets/stylesheets/subfolder/stylesheets_subfolder.css app/assets/stylesheets/subfolder/stylesheets_subfolder.js app/assets/stylesheets/subfolder/stylesheets_subfolder.ttf

This precompiles into this (fingerprinted and compressed files removed for clarity):

Copy
public/assets/application.css public/assets/application.js public/assets/fonts_root.png public/assets/fonts_root.ttf public/assets/images_root.png public/assets/images_root.ttf public/assets/javascripts_root.png public/assets/javascripts_root.ttf public/assets/stylesheets_root.png public/assets/stylesheets_root.ttf public/assets/subfolder public/assets/subfolder/fonts_subfolder.png public/assets/subfolder/fonts_subfolder.ttf public/assets/subfolder/images_subfolder.png public/assets/subfolder/images_subfolder.ttf public/assets/subfolder/javascripts_subfolder.png public/assets/subfolder/javascripts_subfolder.ttf public/assets/subfolder/stylesheets_root.png public/assets/subfolder/stylesheets_subfolder.ttf

What has happened?

  1. All asset folders (app/assets/images, app/assets/stylesheets, app/assets/fonts, etc.) are merged into one folder: public/assets.

  2. Subfolders are also merged into public/assets. That means both app/assets/javascripts/subfolder/* and app/assets/stylesheets/subfolder/* now live in public/assets/subfolder/*

  3. All CSS files are concatenated into one file (public/assets/application.css). With the default manifest in app/assets/stylesheets/application.css, stylesheets in app/assets/stylesheets/**/*.css are concatenated. So public/application.css has the following content:

    Copy
    /* content from app/assets/stylesheets/stylesheet_root.css */ /* content from app/assets/stylesheets/subfolder/stylesheet_subfolder.css */
  4. All Javascript files are concatenated into one file in root (public/assets/application.js). With the default manifest in app/assets/javascripts/application.js, Javascripts in app/assets/javascripts/**/*.js are concatenated. So public/application.js has the following content:

    Copy
    /* content from app/assets/javascripts/javascripts_root.js */ /* content from app/assets/javascripts/subfolder/javascripts_subfolder.js */

What will break after precompilation?

  • Stylesheets, that used url(../images/foo.png)-style tags
  • @font-face definitions that used url('fonts/bar.ttf')-style tags

How can I fix my stylesheets?

  • Do not use url(../images/foo.png).
    This is plainly wrong, since from your browser's point of view, your stylesheet lives at /assets/application.css and your image at /assets/foo.png. I have no idea why it works in development in the first place. Simply use the SASS helper:

    Copy
    .tile background-image: image-url('foo.png')

How can I fix my font definitions?

  • With the Asset-Pipeline fonts are stored in app/assets/fonts.
    Depending on your rails version, you may need to add

    Copy
    config.assets.paths << Rails.root.join("app", "assets", "fonts")

    to your config/application.rb (config/initializers/assets.rb in Rails 4+)

  • Do not use src: url(...) in your @font-face definitions.
    Again instead use a sass-helper: src: font-url(...) (file need to be .scss and not .css anymore)
  • Maybe the referenced paths need adjustment. If before it was src: url(fonts/...) now the leading fonts/ directory needs to be removed, because the fonts are compiled to the assets folder by the asset-pipeline.

Managing 3rd party assets

You don't want to manually fix references in external libraries you put into your application. The card Managing asset libraries offers two solutions.

Growing Rails Applications in Practice
Check out our new e-book:
Learn to structure large Ruby on Rails codebases with the tools you already know and love.

Owner of this card:

Avatar
Tobias Kraze
Last edit:
10 months ago
by Jakob Scholz
Keywords:
vendor-libs
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Tobias Kraze to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more