Static asset caching on Heroku with Jammit by changing ActionController::Base#page_cache_directory

StackOverflow https://stackoverflow.com/questions/4977780

  •  12-11-2019
  •  | 
  •  

Question

I'm attempting to use Jammit for packaging CSS and JS for a Rails app deployed on Heroku, which doesn't work out of the box due to Heroku's read only file system. Every example I've seen of how to do this recommends building all the packaged asset files in advance. Because of Heroku's Git-based deployment, this means you need to make a separate commit to your repository every time these files change, which is not an acceptable solution to me. Instead, I want to change the path that Jammit uses to write the cached packages to #{Rails.root}/tmp/assets (by changing ActionController::Base#page_cache_directory), which is writable on Heroku.

What I don't understand is how the cached files will be used without hitting the Rails stack every time, even using the default path for cached packages. Let me explain what I mean:

When you include a package using Jammit's helper, it looks something like this:

<%= include_javascripts :application %>

which generates this script tag:

<script src="/assets/application.js" type="text/javascript"></script>

When the browser requests this URL, what actually happens is that it gets routed to Jammit::Controller#package, which renders the contents of the package to the browser and then writes a cached copy to #{page_cache_directory}/assets/application.js. The idea is that this cached file is built on the first request, and subsequent requests should serve the cached file directly without hitting the Rails stack. I looked through the Jammit code and I don't see how this is supposed to happen. What prevents subsequent requests to /assets/application.js from simply routing to Jammit::Controller again and never using the cached file?

My guess is that there's a Rack middleware somewhere I'm not seeing that serves the file if it exists and forwards the request on to the controller if it doesn't. If that's the case, where is that code? And how would it work when changing ActionController::Base#page_cache_directory (effectively changing where Jammit writes cached packages)? Since #{Rails.root}/tmp is above the public document root, there's no URL that maps to that path.

Was it helpful?

Solution

Great question! I haven't set this up myself, but it's something I've been meaning to look into, so you've prompted me to do so. Here's what I would try (I'll give a shot myself soon, but you are probably going to beat me to it).

config.action_controller.page_cache_directory = "#{Rails.root}/tmp/page_cache"

Now change your config.ru to:

require ::File.expand_path('../config/environment',  __FILE__)
run Rack::URLMap.new(
   "/"       => Your::App.new,
   "/assets" => Rack::Directory.new("tmp/page_cache/assets"))

Just make sure not to have anything in public/assets, since that won't ever be picked up.

Notes:

  • This is for Rails 3. Not sure of the solution under Rails 2.
  • It looks like Rack::Directory sets cache control headers to 12 hours so Heroku will cache your assets to Varnish. Not sure if Jammit sets this in its controller, but even if it doesn't, it will be cached quite quickly.
  • Heroku also sets ENV['TMPDIR'] now as well, so you can use that instead of Rails.root + '/tmp' if you wish.

OTHER TIPS

This might be of use, it's for a different gem but the idea is similar and I'm trying to get it working with the plain asset helpers.

http://devcenter.heroku.com/articles/using-compass

Unfortunately it seems to be quite difficult to get rails to do this without patching/rewriting the asset helpers module (which resembles coupled spaghetti).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top