Skip to content

Instantly share code, notes, and snippets.

@donburks
Forked from vaz/rails-asset-pipeline.md
Created June 22, 2016 00:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donburks/4525d3e5eaed69d407ac03e3e434ed5b to your computer and use it in GitHub Desktop.
Save donburks/4525d3e5eaed69d407ac03e3e434ed5b to your computer and use it in GitHub Desktop.
Breakout lecture notes on the asset pipeline (May 24, 2016)

Rails Asset Pipeline

Lecture by vaz

What is an asset?

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.

What is the asset pipeline?

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

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.

The Manifest

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).

Directives

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).

Search Paths

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 code
  • lib/assets/* - not often used in the application itself
  • vendor/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.

Assets coming from gems

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.

View helpers

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 include application.js from somewhere in the search path.
  • <%= stylesheet_link_tag 'application' %> will output a <link rel="stylesheet"> tag to include application.css from somewhere in the search path.
  • <%= image_tag 'logo.png' %> will output an tag forlogo.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/compression

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).

Preprocessors

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.

Asset file extensions

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.

Note about CoffeeScript and ExecJS

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.

Other things

Fingerprinting

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.

public/assets

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

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

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.

Heroku or other deployment automation

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.

Precompiling locally

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.

Conclusion

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment