How Asset Precompile Works, Part I

Abstract tooth wheels

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 many factors that affect web application performance and an important one is loading assets such as images, CSS files, JavaScript files etc. Every web application contains some images and CSS files to make it look pretty, along with some JavaScript files to handle user interaction and behavior. If assets are loading faster, the web application ought to perform better. There are many strategies to make assets load fast such as minifying, compressing (gzipping), caching etc.

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

We use 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 asset_pipeline_test. 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 rails to 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.

Also open actionpack-3.2.15 in your favorite editor, because it has a directory sprockets which contains necessary code to run Sprockets from Rails.

Sprockets::Environment

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 Sprocket::Environment to app.assets. app is an instance of our Rails app and you are familiar with it in the form of Rails.application.

We have two versions of assets. app.assets and config.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.

In review, Rails.application.assets is an instance of Sprockets::Environment and 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.assets and 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

Static Asset

We will be looking in more detail about Bundled and 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. build_asset is used to build an asset (duh). logical_path is the name of asset i.e. rails.png and 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 lib/sprockets directory. StaticAsset is a subclass of Asset. 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.

Run 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 rails.png.

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 lib/sprockets directory. 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.

In the 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::SHA1 too. Digest::MD5 is assigned to digest_class attribute here. The new digest is created in digest method by using following line:

digest_class.new.update(VERSION).update(version.to_s)

Here VERSION is Sprockets::VERSION and version.to_s is production-1.0.

Based on this information, let’s use our console to see if we can generate same digests that Sprockets generates for rails.png. Type 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 rails.png is /home/ubuntu/Desktop/work/asset_pipeline_article/app/assets/images/rails.png.

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, a3386665c05a2d82f711a4aaa72d247c for rails.png, confirming the same digest that Sprockets generated. Digest is same for default rails.png across all Rails applications as long as Sprockets::VERSION and 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!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • junaid ehsan

    Nice Article :)

    • Imran Latif

      Hi Junaid,

      Thanks for liking this article. Part 2 will cover Rails asset pipeline in more detail. Stay tuned.

      • junaid ehsan

        yeah! fingers crossed :)

  • Imran Latif

    Thanks Sohail for liking this article.