Lecture by vaz
Assets are additional resources like images, stylesheets, scripts, fonts, etc. that are loaded when an HTML page is loaded in the browser.
In order to load assets, the browser must make additional HTTP requests for each asset to be loaded.
The asset pipeline is a way of managing assets that:
- defines which directories assets are found in (see Search Path, below)
- the URI path assets are served at (by default,
/assets/*
) - concatenates stylesheet and script files (to reduce the number of HTTP requests required for better performance)
- minifies/compresses stylesheets and scripts (to reduce file size)
- runs preprocessors like CoffeeScript and Sass to generate plain CSS or Javascript
- generates filenames with fingerprints so they can be cached indefinitely by clients (browsers): new filenames will be generated if the content changes.
The asset pipeline was introduced in Rails 3 and since then was extracted into
its own gem, called sprockets
. Sprockets is still included and configured
by default in Rails applications. It's possible to use Sprockets with other
frameworks (like Sinatra).
The primary documentation on using the asset pipeline in Rails is The Asset Pipeline chapter in the Rails Guide.
Concatenation means joining multiple files into a single file. This is done to reduce the number of HTTP requests that need to be made, since only the concatenated file needs to be requested.
Concatenation is performed by reading a manifest file that describes which files are included an in what order.
By default, there are two manifest files created:
app/assets/javascripts/application.js
app/assets/stylesheets/application.css
Usually, the application.js
would be used to concatenate all your
application's javascripts, and application.css
would be used to concatenate
all your application's stylesheets, though it is possible to create multiple
manifests or load assets separately. Only these default filenames are included
by default in the "precompile list", that is, the list of files that will
be generated when you precompile assets (more on this later).
Inside manifest files, directives are used to show which files are included and in which order. Directives appear as "special comments":
/*
* application.css
*
* This is a normal comment. Below are directives:
*
*= require first_file
*= require second_file
*= require_tree .
*= require_self
*/
require
will include a file by that name at that point in the output.
The extension is figured out automatically (.css
for stylesheets, .js
for javascripts). The file will be searched for in multiple locations (see
Search Path).
require_tree .
tells Sprockets to require every file in the same directory
as the manifest file, as well as recursively requiring files in
subdirectories. require_directory
does the same but without recursively
requiring subdirectories.
require_self
will include the contents of the manifest file (in particular,
actual style/script coming after the initial comment block) at that point
in the output. This content will normally appear at the end but require_self
can be used to place it in a different position in the output.
It's usually recommended to just use the manifest as a manifest and put all actual scripts/styles in files of their own.
It's also usually recommended that, at least once you start to add more assets,
especially 3rd-party assets, you avoid using require_tree
and just list assets
one by one. This way you're sure what the order is, because the order matters
(some JS files might depend on jQuery being loaded before it, for example).
When using require
in manifests, or when HTTP-requesting an asset directly
(like GET /assets/some_file.js
), Sprockets will look in multiple
locations for the file to include. By default it will look in:
app/assets/*
- usual place for application asset codelib/assets/*
- not often used in the application itselfvendor/assets/*
- a place to put 3rd-party assets- In
app/assets/*
,lib/assets/*
,vendor/assets/*
within installed gems
This can be customized by modifying Rails.application.config.assets.paths
(see the file config/initializers/assets.rb
).
Note that the search path prefix (like app/assets
) as well as the first
path component (like javascripts
) are both dropped. So ultimately it
doesn't matter what subdirectory of assets
a file is in, as far as the
search path is concerned.
Only the first path segment is dropped, though,
so app/assets/javascripts/some/path/file.js
will be served at
/assets/some/path/file.js
and required like so: //= require some/path/file
.
Often, for popular frontend libraries (like jQuery, Bootstrap, Foundation,
Underscore, etc), instead of downloading their files and throwing them
in vendor/assets
, you can instead install a gem that provides the
assets. Sometimes these gems add special features specifically to make
using them with Rails even better. If a gem exists, usually it's the easiest
way to use that library.
There are helper methods you can use in views that also use the asset search path:
<%= javascript_include_tag 'application' %>
will output a<script>
tag to includeapplication.js
from somewhere in the search path.<%= stylesheet_link_tag 'application' %>
will output a<link rel="stylesheet">
tag to includeapplication.css
from somewhere in the search path.<%= image_tag 'logo.png' %> will output an
tag for
logo.png` somewhere in the search path.
This will also handle adding fingerprints to filenames if
config.assets.digest
is set to true
(see Fingerprinting, below).
Minification means stripping unnecessary characters from scripts and stylesheets (for example extra whitespace and comments). Other minification techniques involve renaming identifiers (variables, parameters) to one- or two-character names. The result is often all crammed onto one or a few lines and is generally unreadable.
Thus, the gem that provides minification for Javascript files is called
uglifier
which is in the Gemfile by default. sass-rails
takes care of
minification for CSS by default.
Compression means using a tool like gzip to compress a file before sending it over HTTP. Browsers generally know to decompress these assets and they let the server know if they'll accept gzipped resources (all of this is negotiated via HTTP headers and you don't really have to think about it usually).
See the Rails Guide if you want to override the defaults and use a different minifier (like YUI).
Some preprocessors come configured with new Rails apps by default:
- CoffeeScript, which compiles into Javascript
- Sass, which comes in two dialects, and compiles into CSS
- ERB, which can preprocess any text-based asset
CoffeeScript is basically its own programming language but it is mostly a more "sugary" way to write Javascript. You probably won't have a reason to use this during the program and now that Javascript is becoming more popular, people aren't as hyped about it as they used to be.
Sass, on the other hand, adds some very handy extensions to CSS. It comes
with two dialects, the original Sass dialect using the .sass
extension
and the SCSS dialect using the .scss
extension. Nobody uses the SASS dialect
anymore and neither should you: SCSS is a superset of CSS (meaning valid CSS
should also be valid SCSS) and is much preferred now.
ERB can process any text-based file. Usually it's used for HTML view files but you can have ERB preprocess stylesheets or scripts. You can even chain them so ERB processes into SCSS and then into CSS.
Other preprocessors can be used by adding gems that support them.
Sprockets handles preprocessing by looking at file extensions. If you
have a SCSS file and it should compile to a CSS file, it's named like
filename.css.scss
. If you wanted to run it through ERB and then Sass into
CSS you'd have filename.css.scss.erb
.
(Note about Sass files: If you are using Sass partials--which we didn't
cover in this lecture, but if you are--they don't need to be named
_partial.css.scss
, they can have a simple _partial.scss
extension.
This is because they're imported by Sass and not used to generate a
stand-alone CSS file.)
(Note about using ERB with Sass: Most of the reasons you might think of to use
ERB in CSS files are covered by Rails-specific Sass functions that
are added by sass-rails
. For example, you might want to use <%= image_url('image.png') %>
but sass-rails
already adds a Sass function
image-url('image.png')
. See sass-rails for more.
The coffee-rails
gem depends on having a Javascript runtime installed on the
machine (they might also refer to this as having ExecJS-compatible runtime).
All this means is that you need to have a way running javascript locally on
the machine. The easiest way is probably to have NodeJS installed. Otherwise,
you can add therubyracer
to your Gemfile to get this support.
Fingerprinting generates filenames with unique hashes, or digests (long strings of hexadecimal generated with the MD5 algorithm). The hash is used as a "cache-breaker", so that your production webserver can be configured with caching with "far-future expiry"--essentially meaning "cache this file indefinitely, don't ever request it again". This is great for performance but it means they'll never see any changes if they site is updated, which is bad. The cache-breaker ensures that every time the file changes, the filename changes with it, so this issue is avoided.
If the config.assets.digest
setting is enabled, view asset helpers like
stylesheet_link_tag
will generate paths with fingerprints in the filenames.
If this is enabled in development (I don't see any reason it should be, but
sometimes it is), Sprockets will happily ignore the fingerprint and pass
through to processing the actual asset file. In production, though,
you'll be precompiling assets (see below), and if digest is enabled,
the static files generated will have the fingerprints in their filenames and
will be served directly--Sprockets' usual compile-on-demand will be
circumvented.
You can probably safely disable fingerprinting in development, and you probably never should disable it in production.
This directory is different from the other asset directories. Any files in
public/
will be served directly as static files, and if a file exists in
public/
that matches a request path they'll take precedence over hitting
the actual application.
So, if you have public/assets/logo.png
and a user requests
/assets/logo.png
, the static file will be served directly, and your app
will never see the request. (If you're ever changing a stylesheet over
and over and not seeing changes no matter what you do, make sure it's
not actually serving something in public/
.)
Usually a few assets like favicons might be stored in public/
, as well
as error pages like public/404.html
and a few other things. But most
assets should be put in app/assets
or vendor/assets
.
Asset precompilation is a step that happens when you deploy to production.
In production, Sprockets compilation of assets, as well as using search
paths to locate assets dynamically, is disabled (config.assets.compile
is
false
). This is because it is much, much faster to serve static assets.
So, when deploying to production, usually assets are precompiled on the server. Precompilation runs all the preprocessing, minification, concatenation, fingerprinting, etc. that Sprockets is configured for, but up-front, instead of happening each time an asset is requested.
Precompiled assets are placed in public/assets
. Usually, it's good to
avoid placing assets in there manually, because then it's easy to delete
that directory entirely to "undo" precompilation.
Assets are precompiled like this:
RAILS_ENV=production bin/rake assets:precompile
They can be cleaned with:
RAILS_ENV=production bin/rake assets:clean
The precompile list is the list of assets that will be placed in
public/assets
when you precompile.
By default, any asset that doesn't have a .js
or .css
extension (after
preprocessors have run) will automatically be included in the precompile list.
For .js
and .css
files, only application.js
and application.css
are
included by default. The idea is that you will probably concatenate all your
stuff into those two files.
If you're not going to do that (and there are a lot of reasons why you might not, like having stylesheets only for certain sections of the site, like admin backends), you have to add additional files to the precompile list:
# in config/initializers/assets.rb
# say we have app/assets/stylesheets/admin.css we want to add to the list
Rails.application.config.assets.precompile += %w(admin.css)
Note that if you don't use +=
to add to the list, you'll actually
throw away all the defaults.
Note also that %w(one.css one.js subdir/another.js)
is another way to write
['one.css', 'one.js', 'subdir/another.js']
. It separates on whitespace.
If you're deploying to Heroku, asset precompilation is a step that happens automatically.
Also, many use gems like capistrano
to develop custom build systems that
run tasks (like migration, or asset precompilation) on the production machine
each time the app is deployed.
It can be a pain when it doesn't work properly (you'll see, probably), but remember you can always debug it by precompiling locally and seeing what happens.
Usually you don't want to precompile locally and actually check the files into your repo. It's a lot of clutter. But if your assets rarely change it can save a lot of time, since precompilation can take a while.
If you precompile locally, you'll stop seeing any changes you make to assets unless you delete the precompiled assets. Though, there is a way around this: see here in the guide about changing the URL prefix for serving assets in development mode.
Well, it may seem like a lot, especially if you've never deployed a production app that needs to perform well. But the asset pipeline in Rails addresses a ton of things that can and definitely do come up when it comes time to deploy. Reducing the number of HTTP requests and the size of files, making far-future caching easy, and using filenaming conventions to drive preprocessors is actually much cleaner than what you see in a lot of other frameworks. So happy asset-ing!