Parent

Files

Jammit::Compressor

Uses the YUI Compressor or Closure Compiler to compress JavaScript. Always uses YUI to compress CSS (Which means that Java must be installed.) Also knows how to create a concatenated JST file. If "embed_assets" is turned on, creates "mhtml" and "datauri" versions of all stylesheets, with all enabled assets inlined into the css.

Constants

COMPRESSORS
DEFAULT_OPTIONS
EMBEDDABLE
EMBED_DETECTOR

CSS asset-embedding regexes for URL rewriting.

EMBED_EXTS

Font extensions for which we allow embedding:

EMBED_FONTS
EMBED_MIME_TYPES

Mapping from extension to mime-type of all embeddable assets.

EMBED_REPLACER
JST_END
JST_START

JST file constants.

MAX_IMAGE_SIZE

(32k - padding) maximum length for data-uri assets (an IE8 limitation).

MHTML_END
MHTML_SEPARATOR
MHTML_START

MHTML file constants.

Public Class Methods

new() click to toggle source

The css compressor is always the YUI Compressor. JS compression can be provided with YUI Compressor, Google Closure Compiler or UglifyJS.

# File lib/jammit/compressor.rb, line 58
def initialize
  Jammit.check_java_version
  @css_compressor = YUI::CssCompressor.new(Jammit.css_compressor_options || {})
  flavor          = Jammit.javascript_compressor || Jammit::DEFAULT_COMPRESSOR
  @options        = DEFAULT_OPTIONS[flavor].merge(Jammit.compressor_options || {})
  @js_compressor  = COMPRESSORS[flavor].new(@options)
end

Public Instance Methods

compile_jst(paths) click to toggle source

Compiles a single JST file by writing out a javascript that adds template properties to a top-level template namespace object. Adds a JST-compilation function to the top of the package, unless you've specified your own preferred function, or turned it off. JST templates are named with the basename of their file.

# File lib/jammit/compressor.rb, line 97
def compile_jst(paths)
  namespace   = Jammit.template_namespace
  paths       = paths.grep(Jammit.template_extension_matcher).sort
  base_path   = find_base_path(paths)
  compiled    = paths.map do |path|
    contents  = read_binary_file(path)
    contents  = contents.gsub(/\r?\n/, "\\n").gsub("'", '\\\')
    name      = template_name(path, base_path)
    "#{namespace}['#{name}'] = #{Jammit.template_function}('#{contents}');"
  end
  compiler = Jammit.include_jst_script ? read_binary_file(DEFAULT_JST_SCRIPT) : '';
  setup_namespace = "#{namespace} = #{namespace} || {};"
  [JST_START, setup_namespace, compiler, compiled, JST_END].flatten.join("\n")
end
compress_css(paths, variant=nil, asset_url=nil) click to toggle source

Concatenate and compress a list of CSS stylesheets. When compressing a :datauri or :mhtml variant, post-processes the result to embed referenced assets.

# File lib/jammit/compressor.rb, line 80
def compress_css(paths, variant=nil, asset_url=nil)
  @asset_contents = {}
  css = concatenate_and_tag_assets(paths, variant)
  css = @css_compressor.compress(css) if Jammit.compress_assets
  case variant
  when nil      then return css
  when :datauri then return with_data_uris(css)
  when :mhtml   then return with_mhtml(css, asset_url)
  else raise PackageNotFound, "\"#{variant}\" is not a valid stylesheet variant"
  end
end
compress_js(paths) click to toggle source

Concatenate together a list of JavaScript paths, and pass them through the YUI Compressor (with munging enabled). JST can optionally be included.

# File lib/jammit/compressor.rb, line 68
def compress_js(paths)
  if (jst_paths = paths.grep(Jammit.template_extension_matcher)).empty?
    js = concatenate(paths)
  else
    js = concatenate(paths - jst_paths) + compile_jst(jst_paths)
  end
  Jammit.compress_assets ? @js_compressor.compress(js) : js
end

Private Instance Methods

absolute_path(asset_pathname, css_pathname) click to toggle source

Get the site-absolute public path for an asset file path that may or may not be relative, given the path of the stylesheet that contains it.

# File lib/jammit/compressor.rb, line 188
def absolute_path(asset_pathname, css_pathname)
  (asset_pathname.absolute? ?
    Pathname.new(File.join(Jammit.public_root, asset_pathname)) :
    css_pathname.dirname + asset_pathname).cleanpath
end
concatenate(paths) click to toggle source

Concatenate together a list of asset files.

# File lib/jammit/compressor.rb, line 243
def concatenate(paths)
  [paths].flatten.map {|p| read_binary_file(p) }.join("\n")
end
concatenate_and_tag_assets(paths, variant=nil) click to toggle source

In order to support embedded assets from relative paths, we need to expand the paths before contatenating the CSS together and losing the location of the original stylesheet path. Validate the assets while we're at it.

# File lib/jammit/compressor.rb, line 140
def concatenate_and_tag_assets(paths, variant=nil)
  stylesheets = [paths].flatten.map do |css_path|
    contents = read_binary_file(css_path)
    contents.gsub(EMBED_DETECTOR) do |url|
      ipath, cpath = Pathname.new($1), Pathname.new(File.expand_path(css_path))
      is_url = URI.parse($1).absolute?
      is_url ? url : "url(#{construct_asset_path(ipath, cpath, variant)})"
    end
  end
  stylesheets.join("\n")
end
construct_asset_path(asset_path, css_path, variant) click to toggle source

Return a rewritten asset URL for a new stylesheet -- the asset should be tagged for embedding if embeddable, and referenced at the correct level if relative.

# File lib/jammit/compressor.rb, line 179
def construct_asset_path(asset_path, css_path, variant)
  public_path = absolute_path(asset_path, css_path)
  return "__EMBED__#{public_path}" if embeddable?(public_path, variant)
  source = asset_path.absolute? ? asset_path.to_s : relative_path(public_path)
  rewrite_asset_path(source, public_path)
end
embeddable?(asset_path, variant) click to toggle source

An asset is valid for embedding if it exists, is less than 32K, and is stored somewhere inside of a folder named "embed". IE does not support Data-URIs larger than 32K, and you probably shouldn't be embedding assets that large in any case. Because we need to check the base64 length here, save it so that we don't have to compute it again later.

# File lib/jammit/compressor.rb, line 220
def embeddable?(asset_path, variant)
  font = EMBED_FONTS.include?(asset_path.extname)
  return false unless variant
  return false unless asset_path.to_s.match(EMBEDDABLE) && asset_path.exist?
  return false unless EMBED_EXTS.include?(asset_path.extname)
  return false unless font || encoded_contents(asset_path).length < MAX_IMAGE_SIZE
  return false if font && variant == :mhtml
  return true
end
encoded_contents(asset_path) click to toggle source

Return the Base64-encoded contents of an asset on a single line.

# File lib/jammit/compressor.rb, line 231
def encoded_contents(asset_path)
  return @asset_contents[asset_path] if @asset_contents[asset_path]
  data = read_binary_file(asset_path)
  @asset_contents[asset_path] = Base64.encode64(data).gsub(/\n/, '')
end
find_base_path(paths) click to toggle source

Given a set of paths, find a common prefix path.

# File lib/jammit/compressor.rb, line 116
def find_base_path(paths)
  return nil if paths.length <= 1
  paths.sort!
  first = paths.first.split('/')
  last  = paths.last.split('/')
  i = 0
  while first[i] == last[i] && i <= first.length
    i += 1
  end
  res = first.slice(0, i).join('/')
  res.empty? ? nil : res
end
mime_type(asset_path) click to toggle source

Grab the mime-type of an asset, by filename.

# File lib/jammit/compressor.rb, line 238
def mime_type(asset_path)
  EMBED_MIME_TYPES[File.extname(asset_path)]
end
rails_asset_id(path) click to toggle source

Similar to the AssetTagHelper's method of the same name, this will determine the correct asset id for a file.

# File lib/jammit/compressor.rb, line 209
def rails_asset_id(path)
  asset_id = ENV["RAILS_ASSET_ID"]
  return asset_id if asset_id
  File.exists?(path) ? File.mtime(path).to_i.to_s : ''
end
read_binary_file(path) click to toggle source

`File.read`, but in "binary" mode.

# File lib/jammit/compressor.rb, line 248
def read_binary_file(path)
  File.open(path, 'rb:UTF-8') {|f| f.read }
end
relative_path(absolute_path) click to toggle source

CSS assets that are referenced by relative paths, and are not being embedded, must be rewritten relative to the newly-merged stylesheet path.

# File lib/jammit/compressor.rb, line 196
def relative_path(absolute_path)
  File.join('../', absolute_path.sub(Jammit.public_root, ''))
end
rewrite_asset_path(path, file_path) click to toggle source

Similar to the AssetTagHelper's method of the same name, this will append the RAILS_ASSET_ID cache-buster to URLs, if it's defined.

# File lib/jammit/compressor.rb, line 202
def rewrite_asset_path(path, file_path)
  asset_id = rails_asset_id(file_path)
  (!asset_id || asset_id == '') ? path : "#{path}?#{asset_id}"
end
template_name(path, base_path) click to toggle source

Determine the name of a JS template. If there's a common base path, use the namespaced prefix. Otherwise, simply use the filename.

# File lib/jammit/compressor.rb, line 131
def template_name(path, base_path)
  return File.basename(path, ".#{Jammit.template_extension}") unless base_path
  path.gsub(/\A#{Regexp.escape(base_path)}\/(.*)\.#{Jammit.template_extension}\Z/, '\1')
end
with_data_uris(css) click to toggle source

Re-write all enabled asset URLs in a stylesheet with their corresponding Data-URI Base-64 encoded asset contents.

# File lib/jammit/compressor.rb, line 154
def with_data_uris(css)
  css.gsub(EMBED_REPLACER) do |url|
    "url(\"data:#{mime_type($1)};charset=utf-8;base64,#{encoded_contents($1)}\")"
  end
end
with_mhtml(css, asset_url) click to toggle source

Re-write all enabled asset URLs in a stylesheet with the MHTML equivalent. The newlines ("rn") in the following method are critical. Without them your MHTML will look identical, but won't work.

# File lib/jammit/compressor.rb, line 163
def with_mhtml(css, asset_url)
  paths, index = {}, 0
  css = css.gsub(EMBED_REPLACER) do |url|
    i = paths[$1] ||= "#{index += 1}-#{File.basename($1)}"
    "url(mhtml:#{asset_url}!#{i})"
  end
  mhtml = paths.sort.map do |path, identifier|
    mime, contents = mime_type(path), encoded_contents(path)
    [MHTML_SEPARATOR, "Content-Location: #{identifier}\r\n", "Content-Type: #{mime}\r\n", "Content-Transfer-Encoding: base64\r\n\r\n", contents, "\r\n"]
  end
  [MHTML_START, mhtml, MHTML_END, css].flatten.join('')
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.