aridag
Last Updated: February 25, 2016
·
12.74K
· athal7
56b0b10e79eea3c98fb88c91ef0c6a5e

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
Say Thanks
Respond

10 Responses
Add your response

16252
0452e5d2dd609b34218b8d18e51caee6

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.

over 1 year ago ·
17063
7c3922454478e5796f615de53f875a40

Would you mind if I turned this into a gem? I'm one of the contributors to https://github.com/capistrano-plugins/

over 1 year ago ·
17064
56b0b10e79eea3c98fb88c91ef0c6a5e

@rhomeister not at all! please do send the link once you've done so so I can use it :)

over 1 year ago ·
17065
7c3922454478e5796f615de53f875a40
over 1 year ago ·
17075
56b0b10e79eea3c98fb88c91ef0c6a5e

thank you!

over 1 year ago ·
17297
None

Thanks for @athal7 's great post and @rhomeister 's work, them resolve my problem!

over 1 year ago ·
17739
41307ac215f77767b1da1b931dd3fa02

Great JObs. @rhomeister and @athal7

over 1 year ago ·
17960
E nsuuretxy

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

over 1 year ago ·
18715
56b0b10e79eea3c98fb88c91ef0c6a5e

@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.

over 1 year ago ·
18717
7c3922454478e5796f615de53f875a40

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.

over 1 year ago ·