Rails asset precompilation happening even if there is no change in assets - ruby-on-rails

I am running a rails stack on AWS Opsworks. In before_symlink.rb deploy hook, I have the following code.
rails_env = new_resource.environment["RAILS_ENV"]
shared_path = "#{new_resource.deploy_to}/shared"
# create shared directory for assets, if it doesn't exist
directory "#{shared_path}/assets" do
mode 0770
action :create
recursive true
not_if do
Dir.exists?("#{shared_path}/assets")
end
end
# symlink current deploy's asset folder to shared assets each deploy
link "#{release_path}/public/assets" do
to "#{shared_path}/assets"
end
# precompile assets into public/assets (which is symlinked to shared assets folder)
execute "rake assets:precompile" do
cwd release_path
command "bundle exec rake assets:precompile --trace"
environment 'RAILS_ENV' => rails_env
end
The problem is that assets are precompiled all the time even if there is no change, inspite of being in a symlinked shared folder. I am running on Rails 4.1.2 and I guess Rails is intelligent enough to compile only changed assets after Rails 4? Why is this happening?
UPDATE
This is now solved.
I missed out symlinking /tmp/cache to /shared/tmp/cache. This is where sprockets stores cached files.
Updated code.
# Precompile assets. Assets are compiled into shared/assets and shared between deploys.
rails_env = new_resource.environment["RAILS_ENV"]
shared_path = "#{new_resource.deploy_to}/shared"
# create shared directory for assets, if it doesn't exist
directory "#{shared_path}/assets" do
mode 0770
action :create
recursive true
not_if do
Dir.exists?("#{shared_path}/assets")
end
end
#create shared directory to store sprockets cache
directory "#{shared_path}/tmp/cache" do
mode 0770
action :create
recursive true
not_if do
Dir.exists?("#{shared_path}/tmp/cache")
end
end
# symlink current deploy's asset folder to shared assets each deploy
link "#{release_path}/public/assets" do
to "#{shared_path}/assets"
end
# symlink current deploy's sprockets cache folder to shared cache folder on each deploy
link "#{release_path}/tmp/cache" do
to "#{shared_path}/tmp/cache"
end
# precompile assets into public/assets (which is symlinked to shared assets folder)
execute "rake assets:precompile" do
cwd release_path
command "bundle exec rake assets:precompile --trace"
environment 'RAILS_ENV' => rails_env
end

OpsWorks rebuilds your entire rails app upon each deployment and keeps a number of backup copies. It should create it in a path like:
/srv/www/yourappname/current
You can confirm this by ssh into your server. If you go to path
ls -la /srv/www/yourappname/releases
You should see a number of folder with datetime stamps names.
If you look at contents they will each contain your entire rails app.
This makes it easy to roll back if something goes wrong during deployment.
Since each folder is basically a new install of your app, asset pre-compilation needs to happen otherwise the current version won't have any assets. This type of deployment doesn't just have the same directory do a
git pull origin master
UPDATE: It looks like this was an issue with Sprockets that was resolved in this pull request so make sure your gem sprockets version includes this commit. It looks like it was included in v4.0.0beta1 & v4.0.0beta2 so try that or if not, try downgrade to >=2.12.4 as per this comment

Related

How use rails 5.1+ without yarn and webpack?

I update my rails app to pass at 5.1.X to day,
but surprise, my config to minify JS and CSS dosn't work anymore...
config.assets.js_compressor = Uglifier.new(output: {comments: /^!/})
** Invoke yarn:install (first_time)
** Execute yarn:install Yarn executable was not detected in the system. Download Yarn at https://yarnpkg.com/en/docs/install
** Execute assets:precompile rake aborted! Uglifier::Error: Invalid assignment
If I comment out the line, it works, but JS is not minified and there is inaccessible on site... like all others assets, but there are present in cache in /public folder... I don't understand why asset pipeline doesn't work now?!
So how can I stay with classic asset pipeline and disabled yarn call ?
I have been looking into this and as it is a very new issue, I would think about posting also an issue on Uglifier github page. I do not understand why Uglifier is causing problem to yarn as asset pipeline files are in app while yarn will keep them in node_modules.
I believe the issue here is that Uglifier already works on production files (in fact that is a production setting) and the asset pipeline will be working with yarn. You can require files in your application.js and .scss from the yarn node_modules folder, they will be included in your assets.
in this guide you can find out how rails requires the different components
active_record/railtie
action_controller/railtie
action_view/railtie
action_mailer/railtie
active_job/railtie
action_cable/engine
rails/test_unit/railtie
sprockets/railtie
I gave a very quick look at this, could not find anything relevant to yarn in railties/lib/rails/all.rb so I decided to search the rails repository for yarn keyword and find out this file
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
<%- unless options[:skip_yarn] -%>
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')
<%- end -%>
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
so maybe you should search for this option\[:skip_yarn\]?
I wish you good luck
Fabrizio

OpsWorks: assets precompiled multiple times

I am deploying a Rails application using AWS OpsWorks. To precompile the assets, I use the following Chef recipe:
node[:deploy].each do |application, deploy|
deploy_to = node[:deploy][application][:deploy_to]
rails_env = node[:deploy][application][:rails_env]
directory "#{deploy_to}/shared/assets"
link "#{deploy_to}/current/public/assets" do
to "#{deploy_to}/shared/assets"
end
execute "rake assets:precompile" do
cwd "#{deploy_to}/current"
command "bundle exec rake assets:precompile"
environment "RAILS_ENV" => rails_env
end
end
It precompiles correctly, but in following deployments it goes through the whole precompile process again, even though no asset was modified and the assets folder is shared. I also tried a Chef hook, as suggested here, with the same result. How could you make it run only when needed?
You can add not_if or only_if clause to the statement:
Something like this:
execute "rake assets:precompile" do
cwd "#{deploy_to}/current"
command "bundle exec rake assets:precompile"
environment "RAILS_ENV" => rails_env
not_if { File.exists?("<path to expected precompiled asset>") }
end
If you want it to run every time there is a change in a certain directory, you can use chef notifications.

rails asset pipeline production precompile

I have a directory projName/vendor/assets/bootstrap/css/
I am in production mode.
production.rb contains: config.assets.precompile << /(^[^_\/]|\/[^_])[^\/]*$/
when I run rake assets:precompile
I get
projName/public/assets/css/ but I want projName/public/assets/bootstrap/css/
I do not understand why the bootstrap directory is not there.
Actually all the top level directories under vendor/assets and app/assets are missing in public assets/
Compiled assets are written to the location specified in config.assets.prefix. By default, this is the public/assets directory.
To understand this you have to first understand what precompiling is and does. Let me explain
When you run (a rake task)
rake assets:precompile
it will create a public folder inside your application folder which will compile all your asset manifests (ie. your application.css and application.js)
How exactly does this happen ?? -> Rails comes bundled with a rake task which will compile all this. That rake task is what is shown above.
Compiled assets are written to the location specified in config.assets.prefix. By default, this is the public/assets directory.
The default matcher for compiling files includes application.js, application.css and all non-JS/CSS files (this will include all image assets automatically) from app/assets folders including your gems.
And thats exactly what that regex means (to include everything in your app/assets folder), you can also explicitly provide it as shown in the answer above.
Hope this helped.
Here are few links for your reference
http://guides.rubyonrails.org/asset_pipeline.html#precompiling-assets
http://dennisreimann.de/blog/precompiling-rails-assets-for-development/
What does that regex mean? If you want to precompile everything, try this.
config.assets.precompile = ['*.js', '*.css']
Actually, if you want to precompile everything, try this:
def precompile?(path)
%w(app lib vendor).each do |asset_root|
assets_path = Rails.root.join(asset_root, 'assets').to_path
return true if path.starts_with?(assets_path)
end
false
end
# Precompile all assets under app/assets (unless they start with _)
Rails.application.config.assets.precompile << proc do |name, path|
starts_with_underscore = name.split('/').last.starts_with?('_')
unless starts_with_underscore
path = Rails.application.assets.resolve(name).to_path unless path # Rails 4 passes path; Rails 3 doesn't
precompile?(path)
end
end
(Based on the code in the Rails Guide.)

Turbo Sprockets and capistrano

I recently added the turbo sprockets gem https://github.com/ndbroadbent/turbo-sprockets-rails3
to my rails application, I'm using capistrano to deploy to Amazon EC2.
I'm a little confused on how I can make this work.
assets:precompile worked on my local machine, but on the amazon instance it didn't.
Long story short capistrano makes a new release directory for each deployment and the public/assets
directory is empty, so every time it creates a new one and when running assets:precompile it precompiles all of the assets.
Should I precompile localy and add them to git or copy the public/assets directory from the last deployment before capistrano runs assets:precompile?
What would be the cleanest/best practice solution?
Or should I keep the compiled assets in a shared directory?
The simplest solution I could think of is using the shared/assets directory to store
my assets and make a symbolic link to the the release public/assets directory before the assets are being compiled .
task :assets_precompile do
run "ln -s #{shared_path}/assets #{release_path}/public/assets"
run "cd #{release_path} && RAILS_ENV=production bundle exec rake assets:precompile"
end
Edit: Anjan pointed out that if you use deploy:assets ( If you have load 'deploy/assets' in your Capfile ) this is done by default so that's a cleaner solution .
Lesce's answer has greater weight if the deploy process includes symlinking to static files.
Example: config/database.yml is something you don't tweak, nor want a deploy to necessarily change on you...
Having that in shared/assets will lead to failure when assets:precompile gets called.
It also works for cases with user loaded images, attachments...

Chef with rails: Handling asset precompiling during deploy

If you're using rails with Sprockets and Opscode Chef, how are you handling asset precompilation during deployment? I want to keep my git history clean, so I don't want do precompile them locally and then commit them to the repository.
The easiest way seems to be to add them to the application cookbook's migration command, but that seems nasty to me. Asset precompilation should be separate from database migrations. Any other suggestions on how to handle this?
If you are using the deploy_revision resource, you can stick the rake task to do the asset precompiling into the before_restart block.
Here is the snippet of code that is in my deploy_revision resource. Since I am using RVM, I have installed Fletcher Nichol's amazing RVM cookbook of awesomeness. You could replace this with a ruby-block resource.
Checkout a more complete example in my gist.
app = node[:rails][:app_name]
before_restart do
rvm_shell "assets precompile" do
ruby_string "#{app[:ruby_ver]}##{app[:gemset]}"
cwd release_path
user app[:deploy_user]
group app[:deploy_user]
# TODO I could not set the environment via the builtin command. Does not look like it is getting passed to popen4
# So instead of `environment "RAILS_ENV" => app[:environment]` I have it in the code block
code %{
export RAILS_ENV=#{app[:environment]}
bundle exec rake assets:precompile
}
end
end

Resources