March 9, 2019
•Last updated November 5, 2023
Understanding the Asset Pipeline in Ruby on Rails
The asset pipeline has been a game-changer for a number of years in the Ruby on Rails community. This article & video is an overview of what the asset pipeline is, how it's used, and the benefits of such a tool.
What is the asset pipeline?
The Ruby on Rails documentation does a great job explaining the "what".
The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages and pre-processors such as CoffeeScript, Sass, and ERB. It allows assets in your application to be automatically combined with assets from other gems.
A brand new rails application comes stock with the sprockets-rails
gem. This gem combines the power of three other gems (sass-rails
, coffee-rails
and uglifier
) with its own to make assets in your app a breeze to work with. With a little know-how, you can add CSS
, add JavaScript
, reference images, and more inside a given Ruby on Rails application using the asset pipeline.
What about Rails 6 coming around the corner?
It's true that Rails 6 is very close to being released. It will feature a different take on adding JavaScript to your app that is more node.js driven. This harnesses the power of the webpacker
gem to allow modern JavaScript and more to be included inside your ruby on rails application. This happens outside of the asset pipeline entirely. For now, it's advised to keep using the asset pipeline for images and CSS/SCSS and server JavaScript from the new webpacker app/javascript
directory.
Main Features
A generalized perk of using the Asset Pipeline is for performance and speed. Among those generalized gains we get the following:
- File concatenation - serving fewer files means fewer requests your browser has to make.
- SHA256 fingerprint - Cache files automatically with fingerprints. Update only the files that need to be.
- Minification / Compression - Minification means smaller file sizes and less "bytes" to serve per request.
[callout]Note: Right around 20:08 I try and require "log.js". It turns out you don't even need to require it thanks to the "require_tree . " declaration below it in the same file. I also spelled "require" wrong.🤦🏼♂️ I just wanted to clear that up![/callout]
Usage
In a brand new Rails application, you should find several places to house your assets. The main one tied your app is app/assets
. Within that folder is a images
, javascripts
, and stylesheets
directory.
Images
Images are relatively self-explanatory but come with some conventions to remember when rendering them in your views or CSS files.
Including an image in a view happens by way of rails helpers
<%= image_tag "cat.jpg", alt: "Black Cat" %>
Here we include an image. The image_tag
helper expects the image to be located at app/assets/images
within the app. You can add subfolders within images
and reference an image that way as well.
<%= image_tag "marketing/animals/cat.jpg", alt: "black cat" %>
Adding background images in your CSS
files comes with a gotchya as well.
body {
background-image: url('/assets/images/cat.jpg');
}
You might think the above code would work but unfortunately, it does not. Instead, we harness another helper to find the image.
body {
background-image: image-url('cat.jpg');
}
Notice the image-url
helper and the fact that I don't need to declare any relative path. Pretty cool!
Stylesheets
Inside your app/assets/stylesheets
directory you'll see an application.css
file. This is the source of truth for your apps CSS that isn't already in the vendor
or lib
directories (2 other places you can house assets). At any time you can change application.css
to application.scss
and gain the benefits of writing SCSS
in lieu of CSS
. The sass-rails
gem I mentioned prior gets put to work here.
A default application.css
file might look like the following:
/* app/assets/stylesheets/application.css */
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/
The *= require_tree .
syntax might look like a comment but it's actually responsible for rendering all the files within app/assets/stylesheets
and automatically including them here. That way you never have to include them manually. The *= require_self
comment requires the file itself which is what ends up in our main layout template. You can add any CSS file you like and require it here if it's not already inside the app/assets/stylesheets
directory. You might do this from the vendor
directory or if by chance you installed a gem that had some associated styles you needed to harness.
Linking to those assets looks like this.
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>Asset Pipeline</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<div class="container">
<%= yield %>
</div>
</body>
</html>
Above you'll see some handy Rails helpers give us a hand including both our application.css
stylesheet and an application.js
JavaScript file found in app/assets/javascripts
Notice we only render the application.css
and application.js
files and nothing else. If I were to create a new global.css
file within app/assets/stylesheets
it would be included in the page automatically.
JavaScript
Like CSS/SCSS, the same principle applies for JavaScript but we use a slightly different syntax.
// app/assets/javascripts/application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= requre log
//= require_tree .
The //= require log
I added as an example. I created a new file called log.js
and added it to app/assets/javascripts
. Then I require it in application.js
so it gets automatically injected to our layout. The perk of this locally isn't as grand as it is in production where all of your files get compiled down into one. This process saves on file size, request size, and speed which is the whole point (plus how easy it is).
Configuration and Adding new JavaScript and CSS
Say you want to add new files to the mix? In my video I added Bootstrap for grins. This meant adding a bootstrap.css
file and a bootstrap.js
file to our vendor
folder since it's code we'll utilize but not modify. You'll want your vendor folder to have an assets
directory and within that directory a stylesheets
directory and javascripts
directory.
That structure ends up looking like this:
vendor
└── assets
├── javascripts
│ └── bootstrap.js
└── stylesheets
└── bootstrap.css
Simply adding those files to vendor
won't suffice. We need to tell our app about them.
The sprockets-rails
gem comes with a handy manifest.js
file within app/assets/config/
. That file too has its own way of including assets. My file ended up like the following:
// app/assets/config/manifest.js
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
//= link bootstrap.js
//= link bootstrap.css
The top three lines come by default. The bottom two I added which in return will reference the newly added files in vendor
. Notice the link_tree
declaration. This means all files within a folder and subfolders. The link_directory
declarations mean only the files within the named directory.
We're still not 100% done. The application doesn't quite know about these two new additions. Let's fix that.
# config/initializers/assets.rb
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
Rails.application.config.assets.precompile = ["manifest.js"]
Above I made a change to an assets.rb
initializer file within our config
directory. The last line sets a configuration to look for the manifest.js
file within app/assets/
to find what new assets to precompile for use in-app. After saving this you will want to restart your rails server if it already running.
One last piece of the puzzle is to include those new files in our view. I didn't want these files to compile down into the application.js
file though they could if we wanted. Instead, I render them as different files like the following:
<!DOCTYPE html>
<html>
<head>
<title>Pipeline</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= javascript_include_tag 'https://code.jquery.com/jquery-3.3.1.slim.min.js' %>
<%= javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js' %>
<%= stylesheet_link_tag 'bootstrap', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'bootstrap', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'bootstrap', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<div class="container">
<%= yield %>
</div>
</body>
</html>
Bootstrap comes with jQuery
and popper.js
as dependencies so I needed to add those as well. Rather than including them like we did Bootstrap I simply linked to their CDN files. I wouldn't recommend trusting CDNs in production though.
A final refresh should get your Bootstrap!
Is the Asset Pipeline still relevant?
I absolutely think so. Right now, with Rails 6 on the horizon, we'll start to see JavaScript live elsewhere but images, CSS, fonts, and more can live well inside the asset pipeline. It comes with a few conventions to learn but after some continued use you'll learn to appreciate the magic behind the asset pipeline in Ruby on Rails.
Shameless Plug!
If you liked this post, I have more videos on YouTube and here on my blog. Want more content like this in your inbox? Subscribe to my newsletter and get it automatically.
Check out my course
☝ Want to learn Ruby on Rails from the ground up? Check out my upcoming course called Hello Rails.
Categories
Collection
Part of the Ruby on Rails collection
Products and courses
-
Hello Hotwire
A course on Hotwire + Ruby on Rails.
-
Hello Rails
A course for newcomers to Ruby on Rails.
-
Rails UI
UI templates and components for Rails.