Module AssetHat

  1. lib/asset_hat/css.rb
  2. lib/asset_hat/js/vendors.rb
  3. lib/asset_hat/js.rb
  4. lib/asset_hat/vcs.rb
  5. lib/asset_hat/version.rb
  6. lib/asset_hat.rb
  7. show all

Your assets are covered.

With Rails’ default asset caching, CSS and JS are concatenated (not even minified) at runtime when that bundle is first requested. Not good enough. AssetHat can automatically:

  • Easily minify and bundle CSS and JS on deploy to reduce file sizes and HTTP requests.
  • Load popular third-party JS (like jQuery and Prototype) from Google's CDN when in production, or from localhost in development.
  • Force image URLs in your CSS to use CDN subdomains, not just the current host.
  • Add an image’s last Git commit ID to its CSS URLs to bust browser caches (e.g., /images/foo.png?ab12cd3).

After setup, you can use these in your layouts and views:

<%= include_css :bundle => 'application' %>
  # => <link href="/stylesheets/bundles/application.min.css"
  #          media="screen,projection" rel="stylesheet" type="text/css" />

<%= include_js :bundles => ['plugins', 'common'] %>
  # => <script src="/javascripts/bundles/plugins.min.js"
  #            type="text/javascript"></script>
  #    <script src="/javascripts/bundles/common.min.js"
  #            type="text/javascript"></script>

And this in your deploy script:

rake asset_hat:minify

See README.rdoc for more.

Classes and Modules

Module AssetHat::CSS
Module AssetHat::JS

Constants

VERSION = self.version   This gem’s version number.
TYPES = [:css, :js]   Types of supported assets; currently [:css, :js].
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : 'public'   Base directory in which all assets are kept, e.g., ‘public/’.
STYLESHEETS_DIR = File.join(ASSETS_DIR, 'stylesheets')   Directory in which all stylesheets are kept, e.g., ‘public/stylesheets/’.
JAVASCRIPTS_DIR = File.join(ASSETS_DIR, 'javascripts')   Directory in which all JavaScripts are kept, e.g., ‘public/javascripts/’.
RELATIVE_CONFIG_FILEPATH = File.join('config', 'assets.yml')   Relative path for the config file.
CONFIG_FILEPATH = File.join(RAILS_ROOT, RELATIVE_CONFIG_FILEPATH)   Absolute path for the config file.

Public class methods

asset_exists? (filename, type)

Returns true if the specified asset exists in the file system:

AssetHat.asset_exists?('application.css', :css)
  # => true if public/stylesheets/application.css exists
AssetHat.asset_exists?('some-plugin.js', :js)
  # => true if public/javascripts/some-plugin.js exists

See also AssetHat.assets_dir.

[show source]
     # File lib/asset_hat.rb, line 127
127:   def self.asset_exists?(filename, type)
128:     # Process arguments
129:     type = type.to_sym
130:     unless TYPES.include?(type)
131:       raise "Unknown type \"#{type}\"; should be one of: #{TYPES.join(', ')}."
132:       return
133:     end
134: 
135:     # Default to `{:css => {}, :js => {}}`
136:     @asset_exists ||= TYPES.inject({}) { |hsh, t| hsh.merge(t => {}) }
137: 
138:     if @asset_exists[type][filename].nil?
139:       @asset_exists[type][filename] =
140:         File.exist?(File.join(self.assets_dir(type), filename))
141:     end
142:     @asset_exists[type][filename]
143:   end
assets_dir (type)

Returns the relative path to the directory where the original CSS or JS files are stored.

type argument: :css or :js

[show source]
    # File lib/asset_hat.rb, line 76
76:   def self.assets_dir(type)
77:     type = type.to_sym
78: 
79:     unless TYPES.include?(type)
80:       raise "Unknown type \"#{type}\"; should be one of: #{TYPES.join(', ')}."
81:       return
82:     end
83: 
84:     case type
85:     when :css ; STYLESHEETS_DIR
86:     when :js  ; JAVASCRIPTS_DIR
87:     else nil
88:     end
89:   end
bundle_filenames (bundle, type)

Returns the extension-less names of files in the given bundle:

AssetHat.bundle_filenames('application', :css)
  # => ['reset', 'application', 'clearfix']
AssetHat.bundle_filenames('non-existent-file', :css)
  # => nil
[show source]
     # File lib/asset_hat.rb, line 169
169:   def self.bundle_filenames(bundle, type)
170:     # Process arguments
171:     unless TYPES.include?(type.to_sym)
172:       raise "Unknown type \"#{type}\"; should be one of: #{TYPES.join(', ')}."
173:       return
174:     end
175: 
176:     self.config[type.to_s]['bundles'][bundle] rescue nil
177:   end
bundle_filepaths (bundle, type)

Returns the full paths of files in the given bundle:

AssetHat.bundle_filenames('application', :css)
  # => ['/path/to/app/public/stylesheets/reset.css',
        '/path/to/app/public/stylesheets/application.css',
        '/path/to/app/public/stylesheets/clearfix.css']
AssetHat.bundle_filenames('non-existent-file', :css)
  # => nil
[show source]
     # File lib/asset_hat.rb, line 187
187:   def self.bundle_filepaths(bundle, type)
188:     # Process arguments
189:     unless TYPES.include?(type.to_sym)
190:       raise "Unknown type \"#{type}\"; should be one of: #{TYPES.join(', ')}."
191:       return
192:     end
193: 
194:     dir = self.assets_dir(type)
195:     filenames = self.bundle_filenames(bundle, type)
196:     filepaths = filenames.present? ?
197:       filenames.map { |fn| File.join(dir, "#{fn}.#{type}") } : nil
198:   end
bundles_dir (*args)

Returns the relative path to the directory where CSS or JS bundles are stored.

Usage:

AssetHat.bundles_dir
  # => 'bundles'
AssetHat.bundles_dir(:ssl => true)
  # => 'bundles/ssl'
AssetHat.bundles_dir(:css)
  # => 'public/stylesheets/bundles'
AssetHat.bundles_dir(:js, :ssl => true)
  # => 'public/javascripts/bundles/ssl

Options:

ssl
Set this to true if the stylesheet references images via SSL. Defaults to false.
[show source]
     # File lib/asset_hat.rb, line 109
109:   def self.bundles_dir(*args)
110:     options = args.extract_options!
111:     options.symbolize_keys!.reverse_merge!(:ssl => false)
112:     type = args.first
113: 
114:     dir = type.present? ? File.join(assets_dir(type), 'bundles') : 'bundles'
115:     dir = File.join(dir, 'ssl') if options[:ssl]
116:     dir
117:   end
cache? ()

Returns true if bundles should be included as single minified files (e.g., in production), or false if bundles should be included as separate, unminified files (e.g., in development). To modify this value, set config.action_controller.perform_caching = true in your environment.

[show source]
     # File lib/asset_hat.rb, line 150
150:   def self.cache? ; ActionController::Base.perform_caching ; end
cache_last_commit_ids ()

Precomputes and caches the last commit ID for all bundles. Your web server process(es) should run this at boot to avoid overhead during user runtime.

[show source]
    # File lib/asset_hat/vcs.rb, line 74
74:   def self.cache_last_commit_ids
75:     AssetHat::TYPES.each do |type|
76:       next if AssetHat.config[type.to_s].blank? ||
77:               AssetHat.config[type.to_s]['bundles'].blank?
78: 
79:       AssetHat.config[type.to_s]['bundles'].keys.each do |bundle|
80:         # Memoize commit ID for this bundle
81:         AssetHat.last_bundle_commit_id(bundle, type) if AssetHat.cache?
82: 
83:         # Memoize commit IDs for each file in this bundle
84:         AssetHat.bundle_filepaths(bundle, type).each do |filepath|
85:           AssetHat.last_commit_id(filepath)
86:         end
87:       end
88:     end
89:   end
clear_html_cache ()
[show source]
     # File lib/asset_hat.rb, line 249
249:   def self.clear_html_cache
250:     html_cache = {}
251:   end
compute_asset_host (asset_host, source, options={})

Reads ActionController::Base.asset_host, which can be a String or Proc, and returns a String. Should behave just like Rails 2.3.x’s private `compute_asset_host` method, but with a simulated request.

Example environment config for CDN support via SSL:

# In config/environments/production.rb:
config.action_controller.asset_host = Proc.new do |source, request|
  "#{request.protocol}cdn#{source.hash % 4}.example.com"
    # => 'http://cdn0.example.com', 'https://cdn1.example.com', etc.
end

If your CDN doesn’t have SSL support, you can instead revert SSL pages to serving assets from your web server:

config.action_controller.asset_host = Proc.new do |source, request|
  request.ssl? ? nil : "http://cdn#{source.hash % 4}.example.com"
end

Options:

ssl
Set to true to simulate a request via SSL. Defaults to false.
[show source]
     # File lib/asset_hat.rb, line 223
223:   def self.compute_asset_host(asset_host, source, options={})
224:     host = asset_host
225:     if host.is_a?(Proc) || host.respond_to?(:call)
226:       case host.is_a?(Proc) ?
227:            host.arity : host.method(:call).arity
228:       when 2
229:         request = ActionController::Request.new(
230:           'HTTPS' => options[:ssl] ? 'on' : 'off')
231:         host = host.call(source, request)
232:       else
233:         host = host.call(source)
234:       end
235:     else
236:       host %= (source.hash % 4) if host =~ /%d/
237:     end
238:     host
239:   end
config ()

Nested-hash version of config/assets.yml.

[show source]
    # File lib/asset_hat.rb, line 65
65:   def self.config
66:     if !cache? || @config.blank?
67:       @config = YAML.load(ERB.new(File.read(CONFIG_FILEPATH)).result)
68:     end
69:     @config
70:   end
last_bundle_commit_id (bundle, type)

Usage:

AssetHat.last_bundle_commit_id('application', :css)

Returns a string of the latest commit ID for the given bundle, based on which of its files were most recently modified in the repository. If no ID can be found, `nil` is returned.

[show source]
    # File lib/asset_hat/vcs.rb, line 45
45:   def self.last_bundle_commit_id(bundle, type)
46:     # Process arguments
47:     type = type.to_sym
48:     unless TYPES.include?(type)
49:       raise "Unknown type \"#{type}\"; should be one of: #{TYPES.join(', ')}."
50:       return
51:     end
52: 
53:     # Default to `{:css => {}, :js => {}}`
54:     @last_bundle_commit_ids ||=
55:       TYPES.inject({}) { |hsh, t| hsh.merge(t => {}) }
56: 
57:     if @last_bundle_commit_ids[type][bundle].blank?
58:       dir = self.assets_dir(type)
59:       filepaths = self.bundle_filepaths(bundle, type)
60:       if filepaths.present?
61:         @last_bundle_commit_ids[type][bundle] = self.last_commit_id(*filepaths)
62:       end
63:     end
64: 
65:     @last_bundle_commit_ids[type][bundle]
66:   end
last_commit_id (*args)

Usage:

AssetHat.last_commit_id('public/stylesheets/application.css')
AssetHat.last_commit_id('public/stylesheets/ie.css',
                        'public/stylesheets/ie7.css',
                        'public/stylesheets/ie6.css')

Returns a string of the commit ID for the file with the most recent commit. If the file(s) cannot be found, `nil` is returned. Options:

vcs
Version control system. Currently, the only supported value is :git.
[show source]
    # File lib/asset_hat/vcs.rb, line 18
18:   def self.last_commit_id(*args)
19:     # Process arguments
20:     options = args.extract_options!
21:     options = options.symbolize_keys.reverse_merge(:vcs => :git)
22:     filepaths = args.join(' ')
23: 
24:     # Validate options
25:     if options[:vcs] != :git
26:       raise 'Git is currently the only supported VCS.' and return
27:     end
28: 
29:     @last_commit_ids ||= {}
30:     if @last_commit_ids[filepaths].blank?
31:       h = `git log -1 --pretty=format:%h #{filepaths} 2>/dev/null`
32:         # `h` has either the abbreviated Git commit hash or an empty string
33:       @last_commit_ids[filepaths] = h if h.present?
34:     end
35:     @last_commit_ids[filepaths]
36:   end
min_filepath (filepath, extension)

Returns the expected path for the minified version of an asset:

AssetHat.min_filepath('public/stylesheets/bundles/application.css', 'css')
  # => 'public/stylesheets/bundles/application.min.css'

See also AssetHat::CSS.min_filepath and AssetHat::JS.min_filepath.

[show source]
     # File lib/asset_hat.rb, line 159
159:   def self.min_filepath(filepath, extension)
160:     filepath.sub(/([^\.]*).#{extension}$/, "\\1.min.#{extension}")
161:   end
ssl_asset_host_differs? ()

Returns true if the asset host differs between SSL and non-SSL pages, or false if the asset host doesn’t change.

[show source]
     # File lib/asset_hat.rb, line 243
243:   def self.ssl_asset_host_differs?
244:     asset_host = ActionController::Base.asset_host
245:     AssetHat.compute_asset_host(asset_host, 'x.png') !=
246:       AssetHat.compute_asset_host(asset_host, 'x.png', :ssl => true)
247:   end
version ()

Returns this gem’s version number. See also VERSION.

[show source]
   # File lib/asset_hat/version.rb, line 3
3:   def self.version
4:     data_filepath = File.join(File.dirname(__FILE__), %w[.. .. VERSION.yml])
5:     data = YAML.load(File.open(data_filepath, 'r'))
6:     [:major, :minor, :patch, :build].
7:       map { |x| data[x] }.reject(&:blank?).join('.')
8:   end