In a rush? Check out the tl;dr

Where we left off

In part 1, we examined some of the benefits of moving AngularJS templates over to the Rails Asset Pipeline. In today’s post, we’ll get a closer look at some of the implications and considerations that come with using the Asset Pipeline to serve HTML templates.

Asset Pipeline Considerations

As discussed in part 1, the most significant benefit of Asset Pipeline for AngularJS templates is file name fingerprinting and the potential for far-future cache expiration. This perk, however, comes at the cost of some additional complexity. This complexity arises from the need to correctly reference the compiled template by its fingerprinted file name from other assets. For example, referring to a client-side template from a JavaScript file. If you’re using the Asset Pipeline for your AngularJS templates, but you still write templateUrls like this, then you’ve gained little:

templateUrl: ‘/assets/templates/index.html’

It may be true that your AngularJS templates are “using” the Asset Pipeline, but by using the normal file name you lose the ability to leverage far-future cache expiration. In order to correctly refer to the fingerprinted version of an asset, it is necessary to use two Rails helpers.

The first helper is asset_path (or asset_url alternatively). Similar to image_path and javascript_path, the asset_path helper is useful for referencing the fingerprinted version of any asset. Using asset_path, the previous templateUrl example changes to something more like:

templateUrl: “<%= asset_path(‘index.html’) %>”

There are two important things to make note of with this change. First, you’ll notice that templateUrl is now determined using an Embedded Ruby expression that invokes the asset_path Rails helper. This means that any vanilla JavaScript files you have that refer to fingerprinted templates must be modified to run the ERb preprocessor. This is typically as simple as adding an .erb extension to the JavaScript file’s name.

For example, application.js would become application.js.erb. This equates to more pre-processing and more time required to compile assets. It also means mixing JavaScript and Ruby logic in a way that I’ve never been a fan of, but can feel unavoidable.

The second implication of this change is more subtle: we’ve created an implicit dependency between the JavaScript file and the template file. This behavior isn’t something that is in any way specific to client-side templates, but rather, it is a consequence that comes with referencing an asset from another asset. Where this most commonly occurs is when you reference an image in your CSS using image_url (or the Sassy image-url). Since images tend to change less frequently than other assets, they create fewer problems like the one we’ll consider in a moment.

Handling asset dependencies brings us back to the the second Rails helper, depend_on_asset. The depend_on_asset helper allows you to make an implicit asset dependency explicit and avoid some strange behaviors that can arise when deploying implied dependencies to production.

As an example of the weirdness that can arise from implicit asset dependencies, consider the following: when a dependency isn’t explicitly declared, it is possible for the dependency to be modified without the dependent asset updated. In terms of client-side templates, this means that an AngularJS template could be updated and recompiled with a different fingerprinted name, but that updated file name would not be propagated to a JavaScript file that references that template. Now, suddenly your production template and JavaScript are out of sync. Maybe this breaks everything. Maybe Capistrano saves your ass and the JavaScript continues working, but refers to an older version of the template, and then you spend four hours trying to figure out why.

By using depend_on_asset, you let Rails know that it will need to recompile an asset whenever the dependency is updated. Typically depend_on_asset is included at the top of the dependent file or near the context of where the dependency is formed. So a final form of our updated templateUrl might look like so:

//= depend_on_asset ‘index.html’<br /> templateUrl: “<%= asset_path(‘index.html’) %>”<br />

This may not be the most offensive code you’ve seen this week, but the addition of this dependency has far reaching consequences that are often overlooked and can require additional infrastructure changes to reconcile.

Further on down the rabbit hole

Despite my tone being a little dire at times, at this point, we are actually in a really good place. Hypothetically, our client-side templates are reaping the benefits of the Asset Pipeline and everything is hooked up in a way that should work. High five! The only dangling thread is this dependency that’s been forged between our JavaScripts and our AngularJS templates. The implications of this dependency are going to be incredibly dependent on your application, but let’s consider a few of the possible consequences.

If your application compiles all of your JavaScripts into a single file, i.e. application.js, you will likely find that browser caching of your JavaScript is substantially less effective. This is because anytime ANY of your AngularJS templates change, that change is propagated to application.js, which is recompiled, which generates a new fingerprint, which means that all of your users will need to pull down the new version.

If your application uses multiple JavaScript files the effect is the same, though possibly multiplied. This is because each file that references an updated template would need to be recompiled and pulled down by your users anew. That said, in some cases, serving more JavaScripts can actually improve caching of your JavaScripts by reducing the footprint of code that is invalidated when templates are updated. More on that later.

As I’ve said before, depending on your application, you might not care about browser caching. This might not matter if your application isn’t under active development. Similarly, you’re also less affected if your application is a single-use type application that’s not subject to repeated visits (browser caching isn’t of incredible utility in this case anyway).

That said, if you’re working on a repeated-use, enterprise SaaS application that is frequently subject to change, it may warrant additional attention. The bottom line is that trying to leverage better caching for client-side templates has reduced the benefit of caching JavaScripts.

Now, chances are that the JavaScript files in question are changing for other reasons anyway, i.e. active development, so this isn’t really impairing anything further. Though this may be true, it’s not a reason to throw browser caching out the window. If anything, this demonstrates an existing, app-specific problem that this template dependency happens to illuminate: insufficient modularity of compiled JavaScripts.

Dr. ng-love or: How I learned to stop worrying and love (or at least tolerate) more HTTP requests

Is it just me or is the concept of ‘modular compiled JavaScripts’ a little foreign in the Rails world? The word “modular” is frequently overloaded, so I should be clear that, in this case, I mean serving multiple JavaScript files, typically with each file encapsulating related functionality. This is in contrast to the practice of serving all JavaScripts in as few requests as possible, often times in the form of a single application.js.

Modularity of compiled JavaScripts is a fairly straightforward idea, but one that can be easy to cast aside in an industry that emphasizes fewer HTTP requests and imperceptible page load times. Not that fewer requests and lightning fast page loads are a bad thing, but like most things in software engineering, they come with a handful of trade-offs. Depending on your application, the trade-offs may be worth it.

In terms of client-side templates, it is one of the trade-offs faster page load speeds that we find ourselves at odds with: by serving fewer, larger JavaScripts in the name of speed and fewer HTTP requests, we’ve increased cache volatility. If we are willing to serve more numerous, more modular JavaScripts, we stand to gain significantly more effective browser caching at the cost of a slower, request-heavy initial page load. Since this speed degradation only impacts the first page load, it stands to reason that the cost is well worth the benefit for repeat-use web apps.

Let us consider for a moment a more canonical example: Gmail. Using the Dev Tools in Chrome, I can see that 19 of the about 100 HTTP requests made while reloading Gmail are for JavaScripts. Of those 19, 11 came right out of the browser cache. Reloading Gmail for the second time, the app still pulls in 19 JavaScripts except now 100% are from the browser cache. Though it is certainly within Google’s power to serve the full Gmail payload as a single script, we instead find a more modularized approach that allows more flexibility for updating individual components while pulling the rest out of the browser cache.

How can we put this more modular JavaScript compilation strategy into practice? Sure, we can break our JavaScripts into more modular chunks, but templates are referenced all over the place and can’t be limited to a single module. So, updating templates still ends up invalidating a good number of cached JavaScript modules, right? Well, yes and no.

Thus far, I’ve only mentioned modularization around related chunks of code. But there are other boundaries we can use to break our JavaScripts in to more “cache friendly” pieces. For example, where possible, we can separate the assets that are likely to change from assets that are more or less static.

We can also employ tactics to reduce the footprint of more dynamic JavaScripts. With our templateUrl example, we can make it so templateUrl refers to a constant defined in another more frequently changing script rather than referring to the actual template URL. In this case, the file that includes templateUrl doesn’t need to change whenever the template changes. Instead, a smaller script that defines the constant with the actual template URL is updated and the templateUrl reference can be kept up-to-date while the file in which it is contained can remain static.

A final note on the increased number of HTTP requests: Client-side templates may lead to more HTTP requests. You can combat this by grouping templates into logical modules to reduce the number of requests required. That, however, is an article for another day.

Conclusion

While using the Asset Pipeline to compile client-side templates may require a deeper understanding of the technology stack, it comes with some pretty radical benefits. Though the exact implementation will vary depending on the need of the application, using the Asset Pipeline for client-side templates is a win in almost every single case. If I’ve missed something, you disagree, you want a better example, or you have a question, drop me a note in the comments. I’d love to hear about it.

Additional Resources

http://guides.rubyonrails.org/asset_pipeline.html
http://guides.rubyonrails.org/asset_pipeline.html#far-future-expires-header
http://www.amberbit.com/blog/2014/1/20/angularjs-templates-in-ruby-on-rails-assets-pipeline/
http://gaslight.co/blog/4-lessons-learned-doing-angular-on-rails
https://groups.google.com/forum/#!topic/angular/V3hMGIDwE3o
http://cmme.org/tdumitrescu/blog/2013/12/rails-backbone-templates/
http://api.rubyonrails.org/classes/ActionView/Helpers/AssetUrlHelper.html
https://github.com/sstephenson/sprockets#javascript-templating-with-ejs-and-eco

tl;dr:

Treat client-side templates like assets

  • Helps maintain separation of concerns: Don’t cross the streams
  • No controller action required
  • Reduces dynamic code/data injection

Can be served directly by web server

  • Better gzip compression than web server’s on-the-fly compression
  • Fingerprinted filenames for far-future cache expiration
  • May need X-Sendfile and/or additional web server config

Use Rails’ asset_url and depend_on_asset helpers to reference fingerprinted versions of templates

  • Make sure web server configured to include correct Expires response headers
  • But..

Watch out for browser cache performance regression from coupling JavaScript recompilation to updates to client-side templates

  • Serve multiple JavaScript files to reduce impact of cache misses
  • Separate dynamic code from static code
  • Separate static vendor assets from “static” development code
  • Create lookup services for referencing dynamic values like asset URLs

Group templates where logical to reduce number of HTTP requests