How Asset Precompile Works, Part I
Web applications are becoming faster and faster by each passing day. One of the most important features to keep users coming back is speed. If a web application is slow and takes too much time to load, then it will lose it’s users. If a web application is fast then its users will increase. Developing a slow web application is not an option nowadays.
There are a few frameworks that have built-in functionality to perform the aforementioned operations on assets. We (Ruby Community) love Rails and Rails has built-in support for performing different operations on assets as of version 3.1. Thanks to @sstephenson and @joshpeek for the great effort in developing and maintaining Sprockets. Sprockets is a Ruby Gem that touts itself as a “Rack-based asset packaging system”. Sprockets can be used with any Rack-based web framework such as Sinatra, Padrino etc. Sprockets comes built-in as of Rails 3.1 and is considered as an integral part of Rails.
To learn about Sprockets there is an excellent Rails Guide available. I encourage you to read the Rails Guide on Sprockets before proceeding with this article. In this article, we will be looking at how the asset pipeline works in Rails and the precompilation of assets work.
rake assets:precompile to precompile our assets before pushing code to production. This command precompiles assets and places them under the
public/assets directory in our Rails application. Let’s begin our journey by looking at the internals of the Rails Asset Pipeline. This article is based on
Rails 3.2.15 and
Sprocket 2.2.2. If you haven’t installed
Rails 3.2.15 then install that first.
I’ am using RVM to manage my gems. After you have installed
Rails 3.2.15 it’s time to get to the real stuff. As I told you, we will be reading the internals of the Rails asset pipeline, so it would be awesome if you open the Rails gem directory and Sprockets directory in your favorite editor. I will provide the full GitHub path to different code sections, so you can also view the code on GitHub.
Let’s create a test Rails application first. We will be using this application to see and adjust various configuration options and to see our precompiled assets. To create a Rails application we use following command:
rails new asset_pipeline_test
You can use any name you like in-place of
cd into the newly created Rails application and issue the following command to find the path to the Rails gem directory (I am assuming you have installed the necessary gems by issuing the
bundle install command).
bundle show rails
This command will return the path of the Rails gem directory. Run the same command, changing
sprockets to get the path to the Sprockets gem directory.
Now I have a surprise for you. Go ahead and check the contents of the Sprockets gem directory by navigating to the path from running those commands. You will see different files in the Sprockets gem directory.
Now navigate to the Rails gem directory and you might be surprised that this directory is empty :-). I was a bit shocked by seeing an empty directory. We all know that each gem has a directory and all of it’s files reside there. If this is true, then where are Rails gem files??? Rails 3 was a major refactoring and things are now much more organized as compared to Rails 2.x. The Rails gem files are in the
railties-3.2.15 directory so instead of using
rails-3.2.15 we will be using the
railties-3.2.15 directory to inspect different parts of Rails.
actionpack-3.2.15 in your favorite editor, because it has a directory
sprockets which contains necessary code to run Sprockets from Rails.
According to Sprockets, we need an instance of the
Sprockets::Environment class to access and serve assets from your application. Let’s see where this is defined in the code.
Here is the code responsible for creating an instance of
Sprockets::Environment for our Rails application. You might be wondering why this code is in
railtie.rb. The answer to this question is very simple.
As I mentioned earlier, Rails 3 was a major refactoring (thanks to the Rails core team for their awesome efforts).
Rails::Railtie is the core of the Rails framework and provides several hooks to extend Rails and/or modify the initialization process. Railtie provides us with several hooks to perform our desired functionality. We want a
Sprockets::Environment instance available to our application before any other code is executed, so we extend our
Sprockets::Railtie class from
Rails::Railtie. In the code block on line:23, we are assigning the instance of
app is an instance of our Rails app and you are familiar with it in the form of
We have two versions of assets.
config is an instance of
app.config which was assigned to
config in line:18.
config.assets in the
Sprockets::Railtie‘s context or
Rails.application.config.assets context is a configuration object that is used to hold various configuration options for
Rails.application.assets is an instance of
Rails.application.config.assets is a configuration object.
Rails.application.config.assets is defined here. As you can see, it contains configuration options that we use to set up out different environments from different files from our Rails application.
Now, we know where the instance of
Sprockets::Environment is created and what is the difference between
Rails.application.config.assets. Let’s deep dive into the internals of Sprockets.
Types of Assets
There are following three types of assets as per Sprockets
- Bundled Assets
- Processed Assets
- Static Assets
We will be looking in more detail about
Processed Assets in Part 2. Here, we’re discussing
Static Assets. Static assets are assets that don’t need any extra processing. They are just created with a new name. Images, for example, are Static assets. Let’s take a look how Static assets are precompiled and saved.
Navigate here and you will see the method
build_asset is used to build an asset (duh).
logical_path is the name of asset i.e.
pathname is something like
/home/ubuntu/Desktop/work/asset_pipeline_article/app/assets/images/rails.png (your path will be different based on the
root of your application).
At line:381 you will see following line
StaticAsset.new(index, logical_path, pathname)
StaticAsset is defined in the Sprockets gem under the
StaticAsset is a subclass of
Asset is the base class for all types of assets.
Navigate here to see the
initialize method for the
Asset class. In the
build_asset method, we have seen that an instance of
StaticAsset is being created. When an instance of
StaticAsset is created, the
initialize method of
Asset is called. For
StaticAsset, a digest is created and a new file is saved with new name which contains
file_name-digest.file_ext. The digest is created based on the contents of file.
Here, the file is saved. This method is responsible for saving assets to disk, and is pretty self-explanatory.
rake assets:precompile from the command-line from the root of your Rails application. Look at the
public/assets directory and you’ll see the assets of your Rails application. Our interest at this point is
Notice that there are two versions of
rails.png: One named
rails.png name and another named
rails-a3386665c05a2d82f711a4aaa72d247c.png. You might be wondering how two files are created because as you can see in the
write_to method that only one
filename parameter is passed. How does
Sprockets creates two files? This question will be answered in Part 2 of this article.
You might also be wondering where and how this
digest gets created. In the constructor of the
Asset class you will see following line:
@digest = environment.file_digest(pathname).hexdigest
environment is an instance of
Index defined in
index.rb in the Sprockets gem under the
lib/sprockets directory. It is sub-class of
Base defined in
base.rb in the
file_digest is defined in
base.rb. Navigate to the
file_digest method in
base.rb and on line 242 the
digest method is called.
digest method, you can see that if
@digest exists, it is used and dupped copy is returned or new digest is created and it’s dupped version is returned.
digest_class returns, by default,
Digest::MD5. However, we can assign some other class such as
Digest::MD5 is assigned to
digest_class attribute here. The new digest is created in
digest method by using following line:
Based on this information, let’s use our console to see if we can generate same digests that Sprockets generates for
rails console at the root of your Rails application.
To generate a digest for
rails.png, we should have the pathname of
rails.png. As already mentioned, the pathname is the absolute path of the asset. In my case,the pathname of
Type following commands in console:
digest = Digest::MD5.new digest.update(Sprockets::VERSION).update('production-1.0') digest.file("/home/ubuntu/Desktop/work/asset_pipeline_article/app/assets/images/rails.png").hexdigest
digest.file method returns the digest for the file based on its content. This will give us, in my case,
rails.png, confirming the same digest that Sprockets generated. Digest is same for default
rails.png across all Rails applications as long as
version.to_s are same.
In this article, we discovered how Rails the asset pipeline works at a basic level and how static assets are precompiled, along wiht how a digest is created. The next part will contain an in-depth coverage of the Rails asset pipeline, covering things like how paths are assigned, how paths are resolved, how bundled assets and processed assets work, and many other concepts.
I hope you learned something about the Asset Pipeline today. Be on the look out for Part 2!