require 'cssmin' module AssetHat # Methods for minifying and optimizing CSS. module CSS # A list of supported minification # engine names. ENGINES = [:weak, :cssmin] # Returns the expected path for the minified version of a CSS asset: # # AssetHat::CSS.min_filepath('public/stylesheets/bundles/application.css') # # => 'public/stylesheets/bundles/application.min.css' def self.min_filepath(filepath) AssetHat.min_filepath(filepath, 'css') end # Accepts a string of CSS, and returns that CSS minified. Options: # # [engine] Default is :cssmin; see # Engines.cssmin. # Allowed values are in ENGINES. def self.minify(input_string, options={}) options.reverse_merge!(:engine => :cssmin) engine = options[:engine].to_sym unless ENGINES.include?(engine) raise %Q{ Unknown CSS minification engine '#{engine}'. Allowed: #{ENGINES.map{ |e| "'#{e}'" }.join(', ')} }.strip.gsub(/\s+/, ' ') and return end AssetHat::CSS::Engines.send(engine, input_string) end # Given a string containing CSS, appends each referenced asset's last # commit ID to its URL, e.g., # background: url(/images/foo.png?ab12cd34e). This enables # cache busting: If the user's browser has cached a copy of foo.png from a # previous deployment, this new URL forces the browser to ignore that # cache and request the latest version. def self.add_asset_commit_ids(css) css.gsub(/url[\s]*\((\/(images|htc)\/[^)]+)\)/) do |match| src = $1 # Get absolute path filepath = File.join(ASSETS_DIR, src) # Convert to relative path filepath.sub!(/^#{FileUtils.pwd}#{File::SEPARATOR}/, '') commit_id = AssetHat.last_commit_id(filepath) commit_id.present? ? "url(#{src}?#{commit_id})" : "url(#{src})" end end # Arguments: # # - A string containing CSS; # - A string containing the app's asset host, e.g., # 'http\://assets%d.example.com'. This value is typically taken from # config.action_controller.asset_host in # the app's config/environments/production.rb. # # An asset host is added to every image URL in the CSS, e.g., # background: url(http\://assets2.example.com/images/foo.png); # if %d in the asset host, it is replaced with an arbitrary # number in 0-3, inclusive. def self.add_asset_hosts(css, asset_host) return if asset_host.blank? css.gsub(/url[\s]*\((\/images\/[^)]+)\)/) do |match| # N.B.: The `/htc/` directory is excluded because IE 6, by default, # refuses to run .htc files (e.g., TwinHelix's iepngfix.htc) from # other domains, including CDN subdomains. src = $1 "url(#{(asset_host =~ /%d/) ? asset_host % (src.hash % 4) : asset_host}#{src})" end end # Swappable CSS minification engines. module Engines # Barebones CSS minification engine that only strips whitespace from the # start and end of every line, including linebreaks. For safety, doesn't # attempt to parse the CSS itself. def self.weak(input_string) input = StringIO.new(input_string) output = StringIO.new input.each do |line| # Remove indentation and trailing whitespace (including line breaks) line.strip! next if line.blank? output.write line end output.rewind output.read end # CSS minification engine that simply uses the CSSMin gem, a Ruby port # of Lecomte's YUI Compressor and Schlueter's PHP cssmin. # # Sources: # - http://github.com/rgrove/cssmin # - http://rubygems.org/gems/cssmin def self.cssmin(input_string) CSSMin.minify(input_string) end end end end