Class: Jammit::Compressor
- Inherits:
-
Object
- Object
- Jammit::Compressor
- 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' => 'application/x-font-ttf', '.otf' => 'font/opentype', '.woff' => 'application/x-font-woff' }
- EMBED_EXTS =
Font extensions for which we allow embedding:
EMBED_MIME_TYPES.keys
- EMBED_FONTS =
['.ttf', '.otf', '.woff']
- MAX_IMAGE_SIZE =
(32k – padding) maximum length for data-uri assets (an IE8 limitation).
32700
- EMBED_DETECTOR =
CSS asset-embedding regexes for URL rewriting.
/url\(['"]?([^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
- EMBEDDABLE =
/[\A\/]embed\//
- EMBED_REPLACER =
/url\(__EMBED__(.+?)(\?\d+)?\)/
- MHTML_START =
MHTML file constants.
"/*\r\nContent-Type: multipart/related; boundary=\"MHTML_MARK\"\r\n\r\n"
- MHTML_SEPARATOR =
"--MHTML_MARK\r\n"
- MHTML_END =
"\r\n--MHTML_MARK--\r\n*/\r\n"
- JST_START =
JST file constants.
"(function(){"
- JST_END =
"})();"
- JAVASCRIPT_COMPRESSORS =
{ :jsmin => Jammit.javascript_compressors.include?(:jsmin) ? Jammit::JsminCompressor : nil, :yui => Jammit.javascript_compressors.include?(:yui) ? YUI::JavaScriptCompressor : nil, :closure => Jammit.javascript_compressors.include?(:closure) ? Closure::Compiler : nil, :uglifier => Jammit.javascript_compressors.include?(:uglifier) ? Jammit::Uglifier : nil }
- CSS_COMPRESSORS =
{ :cssmin => Jammit.css_compressors.include?(:cssmin) ? Jammit::CssminCompressor : nil, :yui => Jammit.css_compressors.include?(:yui) ? YUI::CssCompressor : nil, :sass => Jammit.css_compressors.include?(:sass) ? Jammit::SassCompressor : nil }
- JAVASCRIPT_DEFAULT_OPTIONS =
{ :jsmin => {}, :yui => {:munge => true}, :closure => {}, :uglifier => {:copyright => false} }
Instance Method Summary (collapse)
-
- (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.
-
- (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.
-
- (Object) compress_css(paths, variant = nil, asset_url = nil)
Concatenate and compress a list of CSS stylesheets.
-
- (Object) compress_js(paths)
Concatenate together a list of JavaScript paths, and pass them through the YUI Compressor (with munging enabled).
-
- (Object) concatenate(paths)
private
Concatenate together a list of asset files.
-
- (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.
-
- (Object) construct_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.
-
- (Boolean) 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”.
-
- (Object) encoded_contents(asset_path)
private
Return the Base64-encoded contents of an asset on a single line.
-
- (Object) find_base_path(paths)
private
Given a set of paths, find a common prefix path.
-
- (Compressor) initialize
constructor
CSS compression can be provided with YUI Compressor or sass.
-
- (Object) mime_type(asset_path)
private
Grab the mime-type of an asset, by filename.
-
- (Object) rails_asset_id(path)
private
Similar to the AssetTagHelper’s method of the same name, this will determine the correct asset id for a file.
-
- (Object) read_binary_file(path)
private
`File.read`, but in “binary” mode.
-
- (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.
-
- (Object) rewrite_asset_path(path, file_path)
private
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.
-
- (Object) template_name(path, base_path)
private
Determine the name of a JS template.
-
- (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.
-
- (Object) with_mhtml(css, asset_url)
private
Re-write all enabled asset URLs in a stylesheet with the MHTML equivalent.
Constructor Details
- (Compressor) initialize
CSS compression can be provided with YUI Compressor or sass. JS compression can be provided with YUI Compressor, Google Closure Compiler or UglifyJS.
67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/jammit/compressor.rb', line 67 def initialize if Jammit.javascript_compressors.include?(:yui) || Jammit.javascript_compressors.include?(:closure) || Jammit.css_compressors.include?(:yui) Jammit.check_java_version end css_flavor = Jammit.css_compressor || Jammit::DEFAULT_CSS_COMPRESSOR @css_compressor = CSS_COMPRESSORS[css_flavor].new(Jammit. || {}) js_flavor = Jammit.javascript_compressor || Jammit::DEFAULT_JAVASCRIPT_COMPRESSOR @options = JAVASCRIPT_DEFAULT_OPTIONS[js_flavor].merge(Jammit. || {}) @js_compressor = JAVASCRIPT_COMPRESSORS[js_flavor].new(@options) end |
Instance Method Details
- (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.
201 202 203 204 205 |
# File 'lib/jammit/compressor.rb', line 201 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 |
- (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.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/jammit/compressor.rb', line 110 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 |
- (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.
93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/jammit/compressor.rb', line 93 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 |
- (Object) compress_js(paths)
Concatenate together a list of JavaScript paths, and pass them through the YUI Compressor (with munging enabled). JST can optionally be included.
81 82 83 84 85 86 87 88 |
# File 'lib/jammit/compressor.rb', line 81 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 |
- (Object) concatenate(paths) (private)
Concatenate together a list of asset files.
256 257 258 |
# File 'lib/jammit/compressor.rb', line 256 def concatenate(paths) [paths].flatten.map {|p| read_binary_file(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.
153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/jammit/compressor.rb', line 153 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.(css_path)) is_url = URI.parse($1).absolute? is_url ? url : "url(#{construct_asset_path(ipath, cpath, variant)})" end end stylesheets.join("\n") end |
- (Object) construct_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.
192 193 194 195 196 197 |
# File 'lib/jammit/compressor.rb', line 192 def construct_asset_path(asset_path, css_path, variant) public_path = absolute_path(asset_path, css_path) return "__EMBED__#{public_path}" if (public_path, variant) source = asset_path.absolute? || ! Jammit.rewrite_relative_paths ? asset_path.to_s : relative_path(public_path) rewrite_asset_path(source, public_path) end |
- (Boolean) 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. Because we need to check the base64 length here, save it so that we don’t have to compute it again later.
233 234 235 236 237 238 239 240 241 |
# File 'lib/jammit/compressor.rb', line 233 def (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 |
- (Object) encoded_contents(asset_path) (private)
Return the Base64-encoded contents of an asset on a single line.
244 245 246 247 248 |
# File 'lib/jammit/compressor.rb', line 244 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 |
- (Object) find_base_path(paths) (private)
Given a set of paths, find a common prefix path.
129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/jammit/compressor.rb', line 129 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 |
- (Object) mime_type(asset_path) (private)
Grab the mime-type of an asset, by filename.
251 252 253 |
# File 'lib/jammit/compressor.rb', line 251 def mime_type(asset_path) EMBED_MIME_TYPES[File.extname(asset_path)] end |
- (Object) rails_asset_id(path) (private)
Similar to the AssetTagHelper’s method of the same name, this will determine the correct asset id for a file.
222 223 224 225 226 |
# File 'lib/jammit/compressor.rb', line 222 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 |
- (Object) read_binary_file(path) (private)
`File.read`, but in “binary” mode.
261 262 263 |
# File 'lib/jammit/compressor.rb', line 261 def read_binary_file(path) File.open(path, 'rb:UTF-8') {|f| f.read } 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.
209 210 211 |
# File 'lib/jammit/compressor.rb', line 209 def relative_path(absolute_path) File.join('../', absolute_path.sub(Jammit.public_root, '')) end |
- (Object) rewrite_asset_path(path, file_path) (private)
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.
215 216 217 218 |
# File 'lib/jammit/compressor.rb', line 215 def rewrite_asset_path(path, file_path) asset_id = rails_asset_id(file_path) (!asset_id || asset_id == '') ? path : "#{path}?#{asset_id}" end |
- (Object) template_name(path, base_path) (private)
Determine the name of a JS template. If there’s a common base path, use the namespaced prefix. Otherwise, simply use the filename.
144 145 146 147 |
# File 'lib/jammit/compressor.rb', line 144 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 |
- (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.
167 168 169 170 171 |
# File 'lib/jammit/compressor.rb', line 167 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.
176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/jammit/compressor.rb', line 176 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 |