Only precompile assets when necessary (Rails 4, Capistrano 3)
The problem
rake assets:precompile
can take up a large chunk of time of Rails application deploys with Capistano, even on deploys where the assets haven't changed!
Other solutions to skip unnecessary asset precompilation seem to rely on Capistrano 2.
The solution
Make these changes in your deploy.rb
file:
Step 1: Set the locations to look for relevant changes to trigger a precompilation
set :assets_dependencies, %w(app/assets lib/assets vendor/assets Gemfile.lock config/routes.rb)
Step 2: Clear the previous deploy precompile task
Rake::Task["deploy:assets:precompile"].clear_actions
Step 3: Define the new deploy precompile task
3a: Define a custom error that we can use to have multiple exit points to for an asset precompile
class PrecompileRequired < StandardError; end
3b: Precompile if it is the first deploy
latest_release = capture(:ls, '-xr', releases_path).split[1]
raise PrecompileRequired unless latest_release
3c: Precompile if the previous deploy failed to finish asset precompilation
latest_release_path = releases_path.join(latest_release)
execute(:ls, latest_release_path.join('assets_manifest_backup')) rescue raise(PrecompileRequired)
3d: Precompile if there is a diff in the asset_dependencies set in step 1 as compared to the previous deploy
fetch(:assets_dependencies).each do |dep|
execute(:diff, '-Naur', release_path.join(dep), latest_release_path.join(dep)) rescue raise(PrecompileRequired)
end
3e: If none of the above conditions are met, copy assets from the previous deploy
execute(:cp, '-r', latest_release_path.join('public', fetch(:assets_prefix)), release_path.join('public', fetch(:assets_prefix)))
3f: If a precompile is required, do it!
rescue PrecompileRequired
execute(:rake, "assets:precompile")
end
All together now:
# set the locations that we will look for changed assets to determine whether to precompile
set :assets_dependencies, %w(app/assets lib/assets vendor/assets Gemfile.lock config/routes.rb)
# clear the previous precompile task
Rake::Task["deploy:assets:precompile"].clear_actions
class PrecompileRequired < StandardError; end
namespace :deploy do
namespace :assets do
desc "Precompile assets"
task :precompile do
on roles(fetch(:assets_roles)) do
within release_path do
with rails_env: fetch(:rails_env) do
begin
# find the most recent release
latest_release = capture(:ls, '-xr', releases_path).split[1]
# precompile if this is the first deploy
raise PrecompileRequired unless latest_release
latest_release_path = releases_path.join(latest_release)
# precompile if the previous deploy failed to finish precompiling
execute(:ls, latest_release_path.join('assets_manifest_backup')) rescue raise(PrecompileRequired)
fetch(:assets_dependencies).each do |dep|
# execute raises if there is a diff
execute(:diff, '-Naur', release_path.join(dep), latest_release_path.join(dep)) rescue raise(PrecompileRequired)
end
info("Skipping asset precompile, no asset diff found")
# copy over all of the assets from the last release
execute(:cp, '-r', latest_release_path.join('public', fetch(:assets_prefix)), release_path.join('public', fetch(:assets_prefix)))
rescue PrecompileRequired
execute(:rake, "assets:precompile")
end
end
end
end
end
end
end
Written by Andrew Thal
Related protips
10 Responses
Great post. This has really improved the speed of my deployment scripts, thank you. Hopefully this will be added to the capistrano rails gem as a default behaviour.
Would you mind if I turned this into a gem? I'm one of the contributors to https://github.com/capistrano-plugins/
@rhomeister not at all! please do send the link once you've done so so I can use it :)
Here you go: https://github.com/capistrano-plugins/capistrano-faster-assets ;)
thank you!
Thanks for @athal7 's great post and @rhomeister 's work, them resolve my problem!
Great JObs. @rhomeister and @athal7
Cool, @athal7 will this work with capistrano version 3.0.1 ? I see in @rhomeister's gem that it works only from 3.1.0 version
@yozzz sorry for the delay, had some coderwall issues. i would defer to @rhomeister's determination, I think he has spent more time with it.
hi @yozzz. Not sure if it would work with Capistrano 3.0.1. I'm using 3.2 at the moment. You could fork the gem and give it a try.