Rails is a favored Ruby web framework and a popular choice for building web applications. It has numerous awesome parts, many of which can be used independently of Rails. ActiveSupport, for example, is usable in any Ruby project to get some of the great features that Rails core provides. ActiveRecord is a standalone ORM, providing a sound way of dealing with different databases with a single codebase.
There is another component of Rails that is widely being used outside of Rails: the Asset Pipeline or, technically speaking, Sprockets. Sprockets is a Ruby gem developed by Sam Stephenson that provides support for asset compilation, asset minification, asset serving, and much more. In this article, we will discuss how Rails serves assets using Sprockets.
I would recommend reading How Asset Precompile Works, Part I and How Asset Precompile Works, Part II before proceeding. There are many concepts used here that are discussed in great detail in those articles.
This article is based on Rails 3.2.17
and Sprockets 2.2.2
. Please install Rails 3.2.17
first if you haven’t already.
Let’s answer the following questions related to Rails assets:
- How asset tags are inserted?
- What is
/assets
and it’s purpose? - What is
ActionDispatch::Static
middleware used for?
How Asset Tags Are Inserted?
The first step to serving an asset is to have proper asset tags (JavaScript tags, Stylesheet tags, image tags etc.) in the HTML response sent to the client. If there are no asset tags in the HTML, then we aren’t using/linking any external libraries, which is pretty taboo nowadays. We need at least one Stylesheet tag, one JavaScript tag, and some image tags in our site. Let’s see how a JavaScript tag gets inserted into the HTML response generated from a typical Rails application.
Source code paths have been mentioned both local and online, as appropriate. Two folders are of interest here, which can be discovered with the following commands in a Rails app:
bundle show actionpack
bundle show sprockets
Use your editor to open each of these libraries so that you can following along in the source.
The default way of inserting a JavaScript asset tag in Rails is:
<%= javascript_include_tag "application.js" %>
As of Rails 3.1, the default application.js
might look like:
//= require jquery
//= require jquery_ujs
//= require_tree .
Rails developers know that they can define dependencies of any JavaScript file by using Sprockets special syntax and additional files that get inserted are actually dependencies of application.js
.
javascript_include_tag
is a Rails helper defined here. Default Rails helpers are defined in actionpack/lib/action_view/helpers
directory and javascript_include_tag
is no exception. But this method can only add script tags to the HTML response and cannot intelligently parse files to resolve dependencies. Resolving asset dependencies is handled by Sprockets.
Sprockets is configured according to the Rails environment in actionpack/lib/sprockets/railtie.rb
. Railtie is the core of the Rails framework and provides several hooks to extend Rails and/or modify the initialization process. If we want to modify Rails behavior at any point during or after the initialization process, we can use Railtie
.
Sprockets extends Rails behavior by including the Sprockets::Helpers::RailsHelper
module after action_view
has been loaded. This means that the newly added module will override methods with same name that are already defined in modules under action_view
.
Now it’s time for a little experiment. Create a new Rails application with a controller and set it’s route.
# creates a new rails application
rails new rails_asset_serve
# navigate to newly created rails directory
cd rails_asset_serve
# create a new controller named test with one method index
rails generate controller test index
Go ahead and edit app/views/test/index.html.erb
and add some dummy text there. Now edit config/routes.rb
and add following line so that test
controller can be accessed from browser.
root :to => 'test#index'
Run the Rails server, navigate to localhost:3000
, and you will see your desired output. However, our point of interest is the source of this page. View the source and you’ll see the following JavaScript tags included in your HTML response.
<script src="/assets/jquery.js?body=1" type="text/javascript"></script>
<script src="/assets/jquery_ujs.js?body=1" type="text/javascript"></script>
<script src="/assets/test.js?body=1" type="text/javascript"></script>
<script src="/assets/application.js?body=1" type="text/javascript"></script>
These JavaScript files are dependencies of application.js
, as mentioned above. Now the fun part. Comment out actionpack/lib/sprockets/railtie.rb:46
and restart your server.
This time, when you visit the page, you won’t see any difference in output, unless you view the source. Here, observe that there is only one JavaScript tag pointing towards application.js
. This is because the default Rails helper is not intelligent, and hence, has skipped dependencies. Sprockets is intelligent enough to resolve dependencies, leading to the links to all our JS source files.
Let’s see how Sprockets
performs this magic.
Don’t forget to uncomment actionpack/lib/sprockets/railtie.rb:46
The Journey Begins
Let’s take application.js
as an example. In actionpack/lib/sprockets/helpers/rails_helper.rb
at line:7
you can see that the Sprockets::Helpers::RailsHelper
module has included the ActionView::Helpers::AssetTagHelper
module. ActionView::Helpers::AssetTagHelper
is the main module that holds the default Rails helpers. By including them within it’s own module, Sprockets is ensuring that it still has access to all the default Rails helpers.
On line:20
you can see javascript_include_tag
is defined. This method gets called when we want to include a JavaScript asset. On line:26
there is a collect
enumerator called which returns an array of script tags separated. In our case sources
will be pointing to application.js
.
On line:27
you can see an if
statement. If we are in the development
environment then debug
evaluates to true
. The other part (asset = asset_paths.asset_for(source, 'js')
) is a bit interesting. If you have read those two recommended articles that I mentioned above, then you will easily understand what is happening.
assets_paths
is a method defined at line:9
which returns an instance of class AssetPaths
defined at line:117
. There are a bunch of assignments in that method, but the one we are interested in is on line:12
.
asset_environment
is a method whose result is assigned to paths.asset_environment
. asset_environment
is defined at line:113
. This method is returning Rails.application.assets
which is an instance of Sprockets::Environment
, the top-level class responsible for everything to do with assets under Sprockets.
asset_for
, at line:122
, is an instance method which is called to get the desired asset. asset_environment[source][/source]
is responsible for returning an instance of Sprockets::BundledAsset
that points towards the desired asset (application.js
in our example). []
is a shorthand for the find_asset
method and is defined here.
asset_environment
returns an instance of Sprockets::BundledAsset
containing all the dependencies of our asset. Iterating over these dependencies is done with the to_a
method, just as Sprockets
is doing online:28
.
At line:29
, there is a call to super
, which is the default Rails helper. Yeah, that’s right. Sprockets has done some clever things. First, it has included its module when action_view
gets loaded resulting in overriding those methods. Then, it included the default Rails helpers in its own modules. By calling super
in javascript_include_tag
, it is calling javascript_include_tag
from the default Rails helpers. The default javascript_include_tag
helper is capable of adding script tags for sources, so at line:29
we are calling it and passing the necessary information and voila! The script tag for each dependency of application.js
is inserted.
Summarizing this process, Sprockets
first overrides the default Rails helpers, intelligently resolves asset dependencies, and then calls the overrided methods again to complete its job.
You might be thinking that the resolving of assets is carried out on each call to our Rails application, but this is not true. When we start/restart the Rails server, all asset resolution happens on the first call for those assets. The assets are cached and reused on subsequent calls until the assets are modified.
In the production
environment, this process is straightforward. Let’s head back to line:20
, where the javascript_include_tag
is defined.
When we are in production
environment, the if
statement on line:27
evaluates to false because debug
is false. The else
case (line:31
will be executed and call super
by passing the appropriate values. In production
, only one asset tag is inserted because all of it’s dependencies are merged into a single file by Sprockets (when we run rake assets:precompile
).
Process for inserting Stylesheet asset tags is same as of JavaScript one. Inclusion of other asset tags such as image, font, audio etc. is pretty easy. In default Rails helpers paths to those assets were prepended with appropriate directory (images for image, audios for audio, fonts for font). But in Sprockets
this thing is replaced with one method and that is prepend all assets paths with Rails.application.config.assets.prefix
(which is /assets by-default) which is defined here. We all know that every asset in Rails application using Sprockets
have this format /assets/__asset_name__
. At line:94
there is a method asset_prefix
which is returning Rails.application.config.assets.prefix
.
Wrap Up
We have seen how Sprockets
performs its magic to resolve dependencies and insert the appropriate asset tags. In a forthcoming article, we will answer the remaining two points mentioned at the beginning of this article. Until then, go tell all your friends that you have mastered Sprockets and asset tags!
Imran Latif is a Web Developer and Ruby on Rails and JavaScript enthusiast from Pakistan. He is a passionate programmer and always keeps on learning new tools and technologies. During his free time he watches tech videos and reads tech articles to increase his knowledge.