I have a Rails app which is not using the asset pipeline. The assets are managed by Yeoman (http://yeoman.io/) and Grunt tasks. We recently introduced a browser cache busting task called grunt-rev to revision the assets before deploying. This task re-names the assets with some uniqueness, then flow passes on to another task called grunt-usemin which updates the html and css files with the new asset names before copying them to the public folder.
For example, main.css in index.html looks like:
<link rel="stylesheet" href="styles/main.css">
And will be re-witten to:
<link rel="stylesheet" href="styles/f43ce6bf.main.css">
The Rails app works fine with local development as the assets are dynamically generated by Yeoman. But once deployed, the assets public folder contains all the revved assets and the Rails views (written in haml) cannot find them. Links like the one below are now dead...
%link{:href => "/styles/main.css", :media => "screen,print", :rel => "stylesheet"}
So I've come up with a few ideas I think might work.
Symlink main.css -> f43ce6bf.main.css
Add a view helper to find the assets
Inline the javascript & css into the view as it is not too big
Fork grunt-usemin to add haml support, then run it against the views within Rails
But am not sure whats best? or am I missing a much easier solution?
UPDATE: I'm now using the helper like below
module ApplicationHelper
def busted_css
#busted_css ||= begin
if Rails.env != "development"
File.join("/styles",
File.basename(Dir["public/styles/*main.css"].sort_by {|f| File.mtime(f)}.reverse[0])
)
else
"/styles/main.css"
end
end
end
end
Related
I'm writing a Rails4 app that uses a custom cache manifest file which needs to include references to all the required Javascript and CSS files. Due to the nature of the application, the Rack Offline gem can't be used.
The stylesheet_link_tag and javascript_include_tag calls produce the correct list of files (as generated by the asset pipeline) but embed them in HTML tags.
Is there a way to get the paths to all the compiled javascript and stylesheet files in the controller?
eg.
/assets/custom.css?body=1
/assets/incidents.css?body=1
/assets/users.css?body=1
/assets/application.css?body=
/assets/jquery.js?body=1
/assets/bootstrap/affix.js?body=1
...
That one was fun! Had to go into the Sprockets source to figure it out.
asset_list = Rails.application.assets.each_logical_path(*Rails.application.config.assets).to_a
You can then go in a grep through the asset list, something like:
asset_list.grep(/\.(js|css)/)
EDIT:
If you want the hex digests, you could do something like:
environment = Rails.application.assets
asset_list = environment.each_logical_path(*Rails.application.config.assets).to_a
asset_list.map! { |asset| environment.find_asset(asset).digest_path rescue nil }.compact
Based on #kdeisz research, this code worked in the controller for the manifest file:
#assets = Rails.application.assets.each_logical_path(*Rails.application.config.assets).to_a
#assets = #assets.map{ |p| view_context.compute_asset_path(p) }
render 'manifest.text', content_type: 'text/cache-manifest'
The compute_asset_path function is needed to get the actual asset path.
Note: I haven't yet tested this in production mode. It works in development mode if you set config.assets.debug = false
I have a Rails application that is dynamically creating and compiling assets depending from what domain you are accessing the website (let say different colors, where values of colors are stored in database )
let say for www.hello.example it will generate public/assets/hello-application-52777d36dec6f6e311560156da9da1c2.css so browser source file will point to
layout:
= stylesheet_link_tag 'hello-application'
generated html:
<link href="/assets/hello-application-52777d36dec6f6e311560156da9da1c2.css" media="all" rel="stylesheet" />
If you add www.goodbye.example it will compile public/assets/goodbye-application-52777d36dec6f6e311560156da9da1c2.css so browser source file will point to
layout:
= stylesheet_link_tag 'goodbye-application'
generated html:
<link data-turbolinks-track="true" href="/assets/goodbye-application-52777d36dec6f6e311560156da9da1c2.css" media="all" rel="stylesheet" />
Assets are compiled correctly on the fly without need to restart the server.
Examples above work fine if I restart the server
The thing I cannot figure out is how to tell Rails that new public asset file was generated so that = stylesheet_link_tag 'goodbye-application' will pick it up without the need to restart the production server.
My guess is that it has something to do to tell Rails to reload the public/manifest.json I just cannot figure it out
Rails 4.0.2
Ruby 2.1
Unicorn production server
My functionality is kinda similar to krautcomputing article however he is solwing this problem with "Digested" which is not working for rails 4 (+ it will be deprecated soon)
Update
One dude provided an answer (which he had remove) suggesting I don't need to do this because Unicorn have zero time restart, so I can just restart the unicorn and it will load the new public assets... That's true, I'm doing this this way before I've asked this question. However I'm looking for solution that avoid this. (let say users generate 1000 styles per day)
He also suggested to use Grunt... Well, that's plan B, I'm really wondering if there is a way to do this in Rails-sprockets :)
Why not just roll your own helper method?
def subdomain_application_stylesheet_link_tag(subdomain)
if Rails.env.production?
# This assumes you've precompiled the spreadsheet into the public directory via sprockets
stylesheet_link_tag File.basename(
Dir.glob("#{Rails.root}/public/assets/#{subdomain}-application-*.css").first
)
else
# Logic to development stylesheet selection
end
end
Here is the full solution to manifest reloading issue. As stated in the original post, the new manifest.json that you dynamically generate will not reload unless you fully reload your server (or kill works, etc). Just to reiterate:
If you are dynamically creating assets 'live', along with a manifest
to prevent caching, Sprockets doesn't reload the manifest between
requests.
I needed this manifest reloading capability for development only because I am rolling-my-own asset pipeline with Gulp for a performance boost. Basically, using this solution allows the assets to function exactly how they would in production, while still being able to live-reload them when developing with a new manifest...pretty slick, I hope you find it useful for your production needs.
create the file config/initializers/live-manifest.rb:
require 'sprockets/rails/helper'
module Sprockets
module Rails
module Helper
def asset_digest_path(path, options = {})
# create the new manifest, overriding sprockets' old one
if assets_manifest and manifest = Sprockets::Manifest.new(assets, Dir.glob(File.join(assets_manifest.dir, 'manifest-*.json')).first)
if digest_path = manifest.assets[path]
return digest_path
end
end
if environment = assets_environment
if asset = environment[path]
return asset.digest_path
end
end
end
end
end
end
Also, you're more than welcome to customize the new manifest directory if you desire. Mine is in the same place as the previous, with a different name, as you can see from the Dir.glob method call.
I added a custom engine for HAML in config/initializers.
When I visit http://127.0.0.1:3000/assets/page.html it renders the page successfully.
# page.html.haml
!!! 5
%html{:lang => "en"}
%head
= stylesheet_link_tag 'application'
= javascript_include_tag 'application'
The problem is Rails seems to cache the html asset. When I add or remove a file from the application.js manifest it doesn't update until I stop the Rails server and run rake tmp:clear. An alternative is to touch the application.js file. I have config.assets.debug = true set because I prefer to see individual files/folders in Chrome DevTools rather than one big application.js file.
Is there a way to exclude certain assets from being cached during development.
Just to reiterate - its the HTML being cached which is the problem, changes to the manifest are reflected when visiting http://127.0.0.1:3000/assets/application.js.
A brute force solution would be to add some depends_on attributes to the haml file:
# page.html.haml
#= depend_on application.js
#= depend_on application.css
!!! 5
%html{:lang => "en"}
%head
= stylesheet_link_tag 'application'
= javascript_include_tag 'application'
This should force the asset to be regenerated when the application.js file is changed.
There are a few cavets with this. The file names should be the names in the source tree. It assumes you can use # for comments in your HAML renderer (I don't know HAML so I'm not sure).I'm also not really sure how this works with a custom engine. Also, a better solution would be to have your custom HAML engine track dependencies itself, but I don't know how to do this.
See the sprockets docs for more details
I would like to package some common assets like css, js, and icon images into a gem for my personal use.
Can I use the assets from inside of the gem directly, or do I have to have a generator move them into the main app?
What you need to do is:
Make a railtie:
module MyGemName
module Rails
class Engine < ::Rails::Engine
end
end
end
Put them in a directory that would otherwise be a proper asset path, like lib/assets/stylesheets.
Use sprockets to include javascripts:
//= require "foobar"
Use sass to include stylesheets:
#import "foobar";
Use the sass function image-url if you refer to images inside your stylesheets:
.widget {
background-image: image-url("widget-icon.png");
}
The assets directory should behave exactly the same as if it was inside your own application.
You can find an example in formalize-rails, which has stylesheets, javascripts and images.
With Rails 3.2 you can create an engine and put the assets in the assets directory where they'll be automatically picked up. Beware though if you create a mountable engine using the generator, it'll create namespaced directories under javascripts, images, and stylesheets. Don't put your stuff in those subdirectories or the parent app won't find them. Just put them directly in javascripts, images, or stylesheets.
I want to run my Rails application in a different scope so that I can deploy it in a war file (mydepartment.war) which will share a Tomcat instance with another Java app WAR. The solution I chose was to modify the rackup file (/config.ru).
map '/mydepartment' do
run Myapp::Application
end
When I do this, my base URL becomes http://localhost:3000/mydepartment instead of just http://localhost:3000. The application runs fine, but it doesn't download CSS/JS specified by stylesheet and script helpers.
However, when I try to include stylesheets and Javascript using helpers, such as
<%= stylesheet_link_tag :all %>
<%= javascript_include_tags :defaults %>
The URLs they generate include localhost:3000/javascripts/jquery.js instead of localhost:3000/mydepartment/javascripts/jquery.js. I actually tried typing the latter in the browser, and the sheet downloads fine.
How can I coax the Rails Javascript/CSS helpers to download files in the new scope without hardcoding it?
if you're not on rails 3.1:
Add this to your config/environments/production.rb (if on production mode):
config.action_controller.asset_path = proc { |path| "/mydepartment#{path}" }