Class: Jammit::Compressor

Inherits:
Object
Defined in:
lib/jammit/compressor.rb

Overview

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.

Constant Summary

EMBED_MIME_TYPES = Mapping from extension to mime-type of all embeddable assets.
{
  '.png'  => 'image/png',
  '.jpg'  => 'image/jpeg',
  '.jpeg' => 'image/jpeg',
  '.gif'  => 'image/gif',
  '.tif'  => 'image/tiff',
  '.tiff' => 'image/tiff',
  '.ttf'  => 'font/truetype',
  '.otf'  => 'font/opentype'
}
EMBED_EXTS = Font extensions for which we allow embedding:.
EMBED_MIME_TYPES.keys
EMBED_FONTS =
['.ttf', '.otf']
MAX_IMAGE_SIZE = Maximum size for embeddable images (an IE8 limitation).
32.kilobytes
EMBED_DETECTOR = CSS asset-embedding regexes for URL rewriting.
/url\(['"]?([^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
EMBEDDABLE =
/[\A\/]embed\//
EMBED_REPLACER =
/url\(__EMBED__([^\s)]+)(\?\d+)?\)/
MHTML_START = MHTML file constants.
"/*\r\nContent-Type: multipart/related; boundary=\"JAMMIT_MHTML_SEPARATOR\"\r\n\r\n"
MHTML_SEPARATOR =
"--JAMMIT_MHTML_SEPARATOR\r\n"
MHTML_END =
"*/\r\n"
JST_START = JST file constants.
"(function(){"
JST_END =
"})();"
COMPRESSORS =
{
  :yui     => YUI::JavaScriptCompressor,
  :closure => Closure::Compiler
}
DEFAULT_OPTIONS =
{
  :yui     => {:munge => true},
  :closure => {}
}

Method Summary

Constructor Details

- (Compressor) initialize

Creating a compressor initializes the internal YUI Compressor from the “yui-compressor” gem, or the internal Closure Compiler from the “closure-compiler” gem.

Returns:



56
57
58
59
60
61
# File 'lib/jammit/compressor.rb', line 56

def initialize
  @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

Method Details

- (Object) compile_jst(paths)

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.



89
90
91
92
93
94
95
96
97
98
99
# File 'lib/jammit/compressor.rb', line 89

def compile_jst(paths)
  namespace = Jammit.template_namespace
  compiled = paths.map do |path|
    template_name = File.basename(path, File.extname(path))
    contents      = File.read(path).gsub(/\n/, '').gsub("'", '\\\\\'')
    "#{namespace}.#{template_name} = #{Jammit.template_function}('#{contents}');"
  end
  compiler = Jammit.include_jst_script ? File.read(DEFAULT_JST_SCRIPT) : '';
  setup_namespace = "#{namespace} = #{namespace} || {};"
  [JST_START, setup_namespace, compiler, compiled, JST_END].flatten.join("\n")
end

- (Object) compress_css(paths, variant = nil, asset_url = nil)

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



73
74
75
76
77
78
79
80
81
82
# File 'lib/jammit/compressor.rb', line 73

def compress_css(paths, variant=nil, asset_url=nil)
  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

- (Object) compress_js(paths)

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



65
66
67
68
# File 'lib/jammit/compressor.rb', line 65

def compress_js(paths)
  js = concatenate(paths)
  Jammit.compress_assets ? @js_compressor.compress(js) : js
end

- (Object) absolute_path(asset_pathname, css_pathname) (private)

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.



154
155
156
157
158
# File 'lib/jammit/compressor.rb', line 154

def absolute_path(asset_pathname, css_pathname)
  (asset_pathname.absolute? ?
    Pathname.new(File.join(PUBLIC_ROOT, asset_pathname)) :
    css_pathname.dirname + asset_pathname).cleanpath
end

- (Object) concatenate(paths) (private)

Concatenate together a list of asset files.



191
192
193
# File 'lib/jammit/compressor.rb', line 191

def concatenate(paths)
  [paths].flatten.map {|p| File.read(p) }.join("\n")
end

- (Object) concatenate_and_tag_assets(paths, variant = nil) (private)

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.



108
109
110
111
112
113
114
115
116
117
# File 'lib/jammit/compressor.rb', line 108

def concatenate_and_tag_assets(paths, variant=nil)
  stylesheets = [paths].flatten.map do |css_path|
    File.read(css_path).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(#{rewrite_asset_path(ipath, cpath, variant)})"
    end
  end
  stylesheets.join("\n")
end

- (Object) embeddable?(asset_path, variant) (private)

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.



170
171
172
173
174
175
176
177
178
# File 'lib/jammit/compressor.rb', line 170

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 || asset_path.size < MAX_IMAGE_SIZE
  return false if font && variant == :mhtml
  true
end

- (Object) encoded_contents(asset_path) (private)

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



181
182
183
# File 'lib/jammit/compressor.rb', line 181

def encoded_contents(asset_path)
  Base64.encode64(File.read(asset_path)).gsub(/\n/, '')
end

- (Object) mime_type(asset_path) (private)

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



186
187
188
# File 'lib/jammit/compressor.rb', line 186

def mime_type(asset_path)
  EMBED_MIME_TYPES[File.extname(asset_path)]
end

- (Object) relative_path(absolute_path) (private)

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



162
163
164
# File 'lib/jammit/compressor.rb', line 162

def relative_path(absolute_path)
  File.join('../', absolute_path.sub(PUBLIC_ROOT, ''))
end

- (Object) rewrite_asset_path(asset_path, css_path, variant) (private)

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.



146
147
148
149
150
# File 'lib/jammit/compressor.rb', line 146

def rewrite_asset_path(asset_path, css_path, variant)
  public_path = absolute_path(asset_path, css_path)
  return "__EMBED__#{public_path}" if embeddable?(public_path, variant)
  asset_path.absolute? ? asset_path.to_s : relative_path(public_path)
end

- (Object) with_data_uris(css) (private)

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



121
122
123
124
125
# File 'lib/jammit/compressor.rb', line 121

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

- (Object) with_mhtml(css, asset_url) (private)

Re-write all enabled asset URLs in a stylesheet with the MHTML equivalent. The newlines (“\r\n”) in the following method are critical. Without them your MHTML will look identical, but won’t work.



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/jammit/compressor.rb', line 130

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.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