# Asset helpers for Sinatra applications. Adds asset timestamping with caching,
# and javascript/stylesheet bundling. To use, include in app file like so:
#
# helpers AssetHelpers
#
# Code is mostly lifted from ActionPack, with slight modifications.
module AssetHelpers
@@asset_timestamps_cache = {}
# Add a file-updated timestamp to an asset in the public directory,
# e.g. image, stylesheet, javascript, flash etc. Timestamps are cached in a
# class variable, so you only have to check the filesystem once. Example:
#
# asset_path('images/icon.png') # => "images/icon.png?1255799678"
def asset_path(source)
"#{source}?#{asset_timestamp(source)}"
end
# Bundle included javascript files into one concatenated file, to reduce
# HTTP lookups. Bundling only occurs when the bundle_assets setting is true. Example:
#
# In app:
# set :bundle_assets, production?
#
# In views:
# javascript_include_tag(
# 'javascripts/jquery-1.3.2.js',
# 'javascripts/application.js',
# :cache => 'javascripts/all.js'
# )
#
# Asset timestamp will be appended to js include tag src url.
def javascript_include_tag(*args)
bundled_asset_include_tag(*args) do |cache|
%{}
end
end
# Bundle included stylesheets into one concatenated file, to reduce #
# HTTP lookups. Bundling only occurs when the bundle_assets setting is true. Example:
#
# In app:
# set :bundle_assets, production?
#
# In views:
# stylesheet_link_tag(
# 'stylesheets/base.css',
# 'stylesheets/application.css',
# :cache => 'stylesheets/all.css'
# )
#
# Asset timestamp will be appended to stylesheet link tag href url.
def stylesheet_link_tag(*args)
bundled_asset_include_tag(*args) do |cache|
%{}
end
end
private
# Modified from Rails' asset timestamping
# Cache writes aren't threadsafe, but, using Passenger, we're not deploying
# to a mutithreaded environment, so not an issue. Could add a mutex (as Rails does)
# if thread safety becomes an issue
def asset_timestamp(source)
if timestamp = @@asset_timestamps_cache[source]
timestamp
else
path = asset_file_path(source)
timestamp = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
@@asset_timestamps_cache[source] = timestamp
end
end
def bundled_asset_include_tag(*args)
opts = args.last.is_a?(Hash) ? args.pop : {}
cache = opts.delete(:cache)
if cache && settings.respond_to?(:bundle_assets) && settings.bundle_assets
file_path = asset_file_path(cache)
unless File.exist?(file_path)
write_asset_file_contents(file_path, args)
end
yield cache
else
args.map {|f| yield f}.join("\n")
end
end
def join_asset_file_contents(paths)
paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n")
end
def write_asset_file_contents(joined_asset_path, asset_paths)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
# Set mtime to the latest of the combined files to allow for
# consistent ETag without a shared filesystem.
mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
File.utime(mt, mt, joined_asset_path)
end
def asset_file_path(path)
File.join('public', path)
end
end