This workshop is important because:
The asset pipeline reduces load time for users. Also, it's important to use it correctly if you want your site to work with many other gems that modify the front end.
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.
After this workshop, developers will be able to:
- List three things the Rails Asset Pipeline does.
- Use asset helpers to include assets in Rails apps.
- Require custom and third-party assets in Rails.
Before this workshop, developers should already be able to:
- Build HTML pages with a templating language like ERB, Handlebars, EJS, Jade, etc.
- Explain how JavaScript and CSS contribute to the front end of a website.
- Explain why the order in which JavaScript or CSS files are loaded matters.
You can think of the asset pipeline as a piece of software that packs up all of your files so that they will be easier and lighter to deliver to the browser.
The goal of the asset pipeline is to:
- Compress assets (CSS and JavaScript files) so they are as small as possible, since smaller files load faster.
- Reduce the number of files transferred to the client, by:
- Combining files before sending them to the browser.
- All CSS files are combined into a single
application.css
file. - All JavaScript files are combined into a single
application.js
file.
- Caching files in the browser.
- Facilitate use of languages like SASS and CoffeeScript, which compile into CSS or JavaScript.
Have you ever included a handful of JS files like this?
<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/javascript" src="vendor/scripts/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="vendor/scripts/materialize.min.js"></script>
<script type="text/javascript" src="app.js"></script>
<title>Load things!</title>
</head>
<body>
</body>
</html>
In the chrome network tab you can see how these files download to the browser:
That's three different requests to the server, for three different files. And that's just for JavaScript. See how that time adds up?
In Rails, the asset pipeline is a better way. It makes one request for JS files and one request for CSS files. Here's how Rails requires files, using the asset helpers provided by the asset pipeline:
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<!-- sign the page for security -->
<%= csrf_meta_tags %>
<!-- application stylesheet -->
<%= stylesheet_link_tag :application, media: :all %>
<!-- application javascript file -->
<%= javascript_include_tag :application %>
<title>Load things with Rails Asset Pipeline!</title>
</head>
<body>
</body>
</html>
That's just two files, one for JavaScript and one for CSS!
In Rails, the asset pipeline:
- concatentates files together
- minifies them
- compresses them
Note: There are similar tools for JS backends as well, but they usually require more setup than Rails does.
If your site has one JavaScript and one CSS file that are linked in the <head>
of app/views/layouts/application.html.erb
, which pages of your site will those scripts and styles apply to?
click for answer
All of your JavaScript and CSS is active on EVERY PAGE. So is your CSS. When writing code for a specific page, you need to think about whether it will affect other pages on the site.With the Asset Pipeline, instead of manually adding <script>
tags, you're going to use <%= javascript_include_tag :application %>
in your application's <head>
. Then, you'll specify what exactly that "includes" in a file called app/assets/javascripts/application.js
.
Inside this file, there's a weird looking series of comments called a "manifest":
// app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .
Actually, these aren't comments! This manifest file lists a series of instructions saying which files need to be loaded in your HTML, and in what order. With these instructions, a gem called sprockets-rails
loads the files specified, processes them if necessary, concatenates them into one single file, and then compresses them (if Rails.application.config.assets.compress
is true
).
The //= require_tree .
line has the important function of including all of the JS files in the .
(current) directory. That is, it includes all of the JS files in app/assets/javascripts
.
When you require
a file, the Asset Pipeline will look for the name of the file (e.g. jquery
) in the following directories:
app/assets/
- application-specific codelib/assets/
- custom libraries not specific to this appvendor/assets/
- third-party libraries
After Rails processes all the files, it replaces the <%= javascript_include_tag :application %>
tag in app/views/layouts/application.html.erb
with a link to the new file it generates. It does the same with the stylesheet_link_tag
.
Note that by default, Rails links both CSS and JavaScript in the
<head>
. You can move JavaScript to the bottom of the<body>
to make sure it doesn't slow down the loading of your HTML.
- Why is it important to specify the order of files in a JavaScript or CSS manifest?
click for answer
It determines the load order of the files. If a file uses jQuery syntax, for example, jQuery must appear before that file in the manifest. Otherwise, jQuery won't have loaded and it won't work.- Which file do you think is the CSS manifest?
click for answer
It's `app/assets/stylesheets/application.css`, and by default it looks like this: /* inside app/assets/stylesheets/application.css
*= require_tree .
*= require_self
*/
It determines the load order of CSS files just like the JavaScript manifest determines the order of JavaScript files.
In views, you can access images in the public/assets/images
directory like this: <%= image_tag "rails.png" %>
.
Images can also be organized into subdirectories, and then can be accessed by specifying the directory's name in the tag: <%= image_tag "icons/rails.png" %>
.
The asset pipeline automatically evaluates ERB. It is able to translate ERB into CSS, HTML, or JS. This means if you add a .erb
extension to a CSS asset (for example, application.css.erb
), then helpers like asset_path
are available in your CSS rules:
.header {
background-image: url(<%= asset_path 'image.png' %>)
}
This writes the path to the particular asset being referenced. In this example, it would make sense to have an image in one of the asset load paths, such as app/assets/images/image.png
, which would be referenced here. If this image is already available in public/assets
as a fingerprinted file, then that path is referenced. You don't have to guess the fingerprint.
Similarly, if you add a .erb
extension to a JavaScript asset (like application.js.erb
), you can then use the asset_path
helper in your JavaScript code:
$('#logo').attr({ src: "<%= asset_path('logo.png') %>" });
Manifests help Rails know how to order all of the different files and combine them into one. But Rails does something with the file name that might seem odd. It adds a large string of random-looking characters in production. For example, you might see a CSS file called application-908e25f4bf641868d8683022a5b62f54.css
. Why not just call it application.css
?
As you know, web applications can be configured to "cache" files in the client's browser, including JavaScript and CSS.
Cached files don't need to be requested on every page load. They're already there, ready to go. This means less wait time and faster page loads.
By default, Rails enables caching.
But what if you changed the file, and the browser was still using an older version? This is where "fingerprinting" comes in - it gives us a way to bust the cache!
In Rails, assets are given a "fingerprint" that changes every time the file is updated. The asset pipeline inserts a hash of the file contents into the file name itself. That's why we'll see file names like application-908e25f4bf641868d8683022a5b62f54.js
instead of application.js
.
Cache-busting works like this:
- If the fingerprint is the same, the browser simply uses its cached copy.
- If the fingerprint has changed, the browser requests the new version of the file (and then caches it!).
You may have noticed a line in a new Rails app's app/assets/javascripts/application.js
that says to //= require turbolinks
.
Turbolinks is a gem that ships with Rails and that eliminates page refreshes when navigating around your app in the browser.
This sounds pretty cool, but the downside is that it only loads your assets once, on the first page-load, then it never loads them again. This means any jQuery events you have set to run on page load won't work on subsequent pages. Turbolinks gives developers new events to listen to in order to get around this, like "turbolinks:load"
. However, most apps the size we're working with won't need this added complexity. Plus, it won't hurt to practice jQuery with regular events over the next few weeks.
We suggest removing turbolinks for the time being.
To create an app without turbolinks:
- Run the
rails new
command with an additional--skip-turbolinks
option.
To remove turbolinks from an existing app:
- Remove
'data-turbolinks-track' => true
from<%= stylesheet_link_tag 'application' %>
and<%= javascript_include_tag 'application' %>
inapp/views/layouts.application.html.erb
. - Remove
//= require turbolinks
fromapp/assets/javascripts/application.js
. - Delete
gem 'turbolinks'
from yourGemfile
, andbundle
again.
In addition to putting all the JavaScript in one file and naming it based on contents, the Asset Pipeline compresses this file so it's smaller.
-
What's the difference between jquery-1.11.3.js and jquery-1.11.3.min.js?
-
Use the
curl
command to do a quick comparison on the command line:
# uncompressed jQuery file
➜ curl https://code.jquery.com/jquery-1.11.3.js | wc
# lines words bytes
# 10351 43235 284394
# compressed (minified) jQuery file
➜ curl https://code.jquery.com/jquery-1.11.3.min.js | wc
# lines words bytes
# 5 1413 95957
Rails also compresses CSS this way.
All of these techniques make Rails faster, but in the Rails development environment many of the above techniques are not enabled. We can try them out just to see what it's like.
Take a look at the instructions for Precompiling Assets for a way to try out the full power of the asset pipeline in your development environment.
Note: You will probably not precompile assets by hand in WDI.
A CDN is a "content delivery network" and a handy way to deliver common "vendor" or "third-party" libraries to your application. It's common to use a CDN for jQuery, Bootstrap, Handlebars, etc.
But is it fast?
If a common file like jQuery is delivered via CDN it is likely:
- Cached in your browser.
- Cached by your ISP (Internet Service Provider).
- Dispatched from a nearby server.
But is that faster than just sending one JavaScript file from your own server?
It could be! It's more likely for very widespread libraries that your user's browser might have cached already. You'll have to make a decision about whether you want to host third-party libraries (like jQuery) on your server, or if you want to use a public CDN.
- See exercises
- Rails Guides: Asset Pipeline
- Sprockets Directives
- List of view helpers for asset tags.
- Caching in Rails
- See Controller-Specific Assets and/or Working with JS in Rails for strategies on scoping your JS.
- See Additional Reading for a more in-depth discussion of view helpers, assets and disabling turbolinks.