# Helpers for use in layouts for global includes, and in views for
# view-specific includes.
module AssetHatHelper
unless defined?(RAILS_ROOT)
RAILS_ROOT = File.join(File.dirname(__FILE__), '..', '..')
end
# Includes CSS or JS files. include_css and
# include_js are recommended instead.
def include_assets(type, *args) #:nodoc:
type = type.to_sym
allowed_types = AssetHat::TYPES
unless allowed_types.include?(type)
expected_types = allowed_types.map { |x| ":#{x}" }.to_sentence(
:two_words_connector => ' or ',
:last_word_connector => ', or '
)
raise "Unknown type :#{type}; should be #{expected_types}"
return
end
options = args.extract_options!.symbolize_keys
filenames = [] # May or may not have proper extensions
sources = [] # The URLs that are ultimately included via HTML
source_commit_ids = {} # Last commit ID for each source
# If `use_caching` is `true`, bundles and minified code will be used:
use_caching = AssetHat.cache?
use_caching = options[:cache] unless options[:cache].nil?
options.delete :cache # Completely avoid Rails' built-in caching
# Gather list of filenames, which may not have proper extensions yet
filenames = args.dup
if options[:bundle].present? || options[:bundles].present?
bundles = [options.delete(:bundle), options.delete(:bundles)].
flatten.reject(&:blank?)
if use_caching
filenames += bundles.map do |bundle|
File.join(AssetHat.bundles_dir(options.slice(:ssl)),
"#{bundle}.min.#{type}")
end
else
config = AssetHat.config
filenames += bundles.map { |b| AssetHat.bundle_filenames(b, type) }.
flatten.reject(&:blank?)
end
end
# Build `sources`, adding extensions if needed, using minified file if it
# already exists
filenames.each do |filename|
if filename.match(/\.#{type}$/)
sources << filename
else
min_filename_with_ext = "#{filename}.min.#{type}"
if use_caching && AssetHat.asset_exists?(min_filename_with_ext, type)
sources << min_filename_with_ext # Use minified version
else
sources << "#{filename}.#{type}" # Use original version
end
end
end
sources.uniq!
if use_caching
# Add commit IDs to bust browser caches based on when each file was
# last updated in the repository. If `use_caching` is false (e.g., in
# development environments), skip this, and instead default to Rails'
# mtime-based cache busting.
sources.map! do |src|
if src =~ %r{^http(s?)://} || src =~ %r{^//}
# Absolute URL; do nothing
elsif src =~ /^#{AssetHat.bundles_dir}\//
# Get commit ID of bundle file with most recently committed update
bundle = src.
match(/^#{AssetHat.bundles_dir}\/(ssl\/)?(.*)\.min\.#{type}$/).
to_a.last
commit_id = AssetHat.last_bundle_commit_id(bundle, type)
else
# Get commit ID of file's most recently committed update
commit_id = AssetHat.last_commit_id(
File.join(AssetHat.assets_dir(type), src))
end
if commit_id.present? # False if file isn't committed to repo
src += "#{src =~ /\?/ ? '&' : '?'}#{commit_id}"
end
src
end
end
# Prepare return value
options.delete :ssl
if options[:only_url]
# Return only asset URLs, not HTML inclusions
source_urls = sources.map { |source| asset_path(type, source) }
source_urls.size == 1 ? source_urls.first : source_urls
else
html = sources.map do |src|
case type
when :css then stylesheet_link_tag(src, options)
when :js then javascript_include_tag(src, options)
else nil
end
end.join("\n")
make_html_safe html
end
end # def include_assets
# include_css is a smart wrapper for Rails'
# stylesheet_link_tag. The two can be used together while
# migrating to AssetHat.
#
# Include a single, minified stylesheet:
# include_css 'diagnostics'
# =>
#
# Include a single, unminified stylesheet:
# include_css 'diagnostics.css'
# =>
#
# Include a bundle of stylesheets (i.e., a concatenated set of
# stylesheets; configure in config/assets.yml):
# include_css :bundle => 'application'
# =>
#
# Include multiple stylesheets separately (not as cool):
# include_css 'reset', 'application'
# =>
#
#
# Include a stylesheet with extra media types:
# include_css 'mobile', :media => 'handheld,screen,projection'
# =>
#
# Get the URL for a single, minified stylesheet:
# include_css 'diagnostics', :only_url => true
# => '/stylesheets/diagnostics.min.css'
#
# Get the URL for a single, unminified stylesheet:
# include_css 'diagnostics.css', :only_url => true
# => '/stylesheets/diagnostics.css'
#
# Get the URL for a bundle of stylesheets when environment *enables* caching
# (e.g., staging, production):
# include_css :bundle => 'application', :only_url => true
# => '/stylesheets/bundles/application.min.css'
#
# Get URLs for a bundle of stylesheets when environment *disables* caching
# (e.g., development, test):
# include_css :bundle => 'application', :only_url => true
# => ['/stylesheets/reset.css', '/stylesheets/common.css', ...]
#
# Get URLs for multiple stylesheets manually:
# include_css 'reset', 'application', :only_url => true
# => ['/stylesheets/reset.css', '/stylesheets/application.css']
def include_css(*args)
return if args.blank?
initialize_html_cache :css
options = setup_options(args,
:media => 'screen,projection',
:ssl => controller.request.ssl?
)
cache_key = setup_cache_key(args, options)
if !asset_cached?(:css, cache_key)
# Generate HTML and write to cache
options[:ssl] &&= AssetHat.ssl_asset_host_differs?
html = AssetHat.html_cache[:css][cache_key] =
include_assets(:css, *(args + [options]))
end
html ||= AssetHat.html_cache[:css][cache_key]
make_html_safe html
end
# include_js is a smart wrapper for Rails'
# javascript_include_tag. The two can be used together while
# migrating to AssetHat.
#
# Include a single, minified JS file:
# include_js 'application'
# =>
#
# Include a single, unminified JS file:
# include_js 'application.js'
# =>
#
# Include jQuery:
# # Development/test environment:
# include_js :jquery
# =>
#
# # Staging/production environment:
# include_js :jquery
# =>
# # Set jQuery versions either in `config/assets.yml`, or by using
# # `include_js :jquery, :version => '1.6.1'`.
#
# Include a bundle of JS files (i.e., a concatenated set of files;
# configure in config/assets.yml):
# include_js :bundle => 'application'
# =>
#
# Include multiple bundles of JS files:
# include_js :bundles => %w[plugins common]
# =>
#
#
# Include multiple JS files separately (not as cool):
# include_js 'bloombox', 'jquery.cookie', 'jquery.json.min'
# =>
#
#
#
# Get the URL for a single, minified JS file:
# include_js 'application', :only_url => true
# => '/javascripts/application.min.js'
#
# Get the URL for a single, unminified JS file:
# include_js 'application.js', :only_url => true
# => '/javascripts/application.js', :only_url => true
#
# Get the URL for jQuery:
# # Development/test environment:
# include_js :jquery, :only_url => true
# => '/javascripts/jquery-VERSION.min.js'
#
# # Staging/production environment:
# include_js :jquery, :only_url => true
# => 'http://ajax.googleapis.com/.../jquery.min.js'
#
# Get the URL for a bundle of JS files when environment *enables* caching
# (e.g., staging, production):
# include_js :bundle => 'application', :only_url => true
# => '/javascripts/bundles/application.min.js'
#
# Get URLs for a bundle of JS files when environment *disables* caching
# (e.g., development, test):
# include_js :bundle => 'application', :only_url => true
# => ['/javascripts/jquery.plugin-foo.js',
# '/javascripts/jquery.plugin-bar.min.js',
# '/javascripts/json2.js',
# ...]
#
# Get URLs for multiple JS files manually:
# include_js 'json2', 'application', :only_url => true
# => ['/javascripts/json2.js', '/javascripts/application.js']
#
# Load JS files with {LABjs}[http://labjs.com] (hosted either from cdnjs or
# your own web server, if found in public/javascripts/):
#
# # config/assets.yml:
# js:
# vendors:
# lab_js:
# version: 1.x.x
#
# # Usage:
# include_js :jquery, :bundle => 'application', :loader => :lab_js
# =>
#
#
# # For advanced fine-tuning, build the LABjs calls manually (based on
# # example from http://labjs.com/documentation.php ):
#
#
# # If you want to execute an inline
def include_js(*args)
return if args.blank?
initialize_html_cache :js
options = setup_options(args, :ssl => controller.request.ssl?)
cache_key = setup_cache_key(args, options)
if !asset_cached?(:js, cache_key)
# Generate HTML and write to cache
htmls = []
include_assets_options = options.except(:ssl, :version)
loader = options.delete(:loader)
include_assets_options.merge!(:only_url => true) if loader
# Get vendor HTML/URLs
included_vendors = (args & AssetHat::JS::VENDORS)
# Add HTML inclusions for vendors
included_vendors.each do |vendor|
args.delete vendor
src = AssetHat::JS::Vendors.source_for(
vendor, options.slice(:ssl, :version))
htmls << include_assets(:js, src,
include_assets_options.merge(:cache => true).
except(:bundle, :bundles))
end
# Get non-vendor HTML/URLs
htmls << include_assets(:js, *(args + [include_assets_options]))
htmls.reject!(&:blank?)
if loader
# `htmls` actually contains URLs; convert to an HTML/JS block
urls = htmls.dup.flatten
htmls = []
case loader
when :lab_js
htmls << include_js(:lab_js)
htmls << ''
end
end
# Convert to a URL (string), array of URLs, or one long HTML string
html = if options[:only_url]
# Return one URL (string) or multiple (array of strings).
# Not actually HTML.
htmls.flatten!
htmls.size == 1 ? htmls.first : htmls
else
# Return one long string of HTML
htmls.join("\n").strip
end
AssetHat.html_cache[:js][cache_key] = html
end
html ||= AssetHat.html_cache[:js][cache_key]
make_html_safe html
end
# Returns the public URL path to the given source file.
#
# type argument: :css or :js
def asset_path(type, source)
case type.to_sym
when :css ; stylesheet_path(source)
when :js ; javascript_path(source)
else
raise %{
Unknown type "#{type}"; should be one of:
#{AssetHat::TYPES.join(', ')}.
}.squish!
end
end
private
def initialize_html_cache(type)
AssetHat.html_cache ||= {}
AssetHat.html_cache[type] ||= {}
end
def setup_options(args, defaults)
options = args.extract_options!
options.symbolize_keys!.reverse_merge!(defaults)
end
def setup_cache_key(args, options)
(args + [options]).inspect
end
def make_html_safe(html)
html.respond_to?(:html_safe) ? html.html_safe : html
end
def asset_cached?(type, cache_key)
AssetHat.cache? && !AssetHat.html_cache[type.to_sym][cache_key].blank?
end
end