lib/jammit/compressor.rb in secobarbital-jammit-0.5.0.1 vs lib/jammit/compressor.rb in secobarbital-jammit-0.6.0.1

- old
+ new

@@ -14,29 +14,30 @@ '.jpeg' => 'image/jpeg', '.gif' => 'image/gif', '.tif' => 'image/tiff', '.tiff' => 'image/tiff', '.ttf' => 'font/truetype', - '.otf' => 'font/opentype' + '.otf' => 'font/opentype', + '.woff' => 'font/woff' } # Font extensions for which we allow embedding: EMBED_EXTS = EMBED_MIME_TYPES.keys - EMBED_FONTS = ['.ttf', '.otf'] + EMBED_FONTS = ['.ttf', '.otf', '.woff'] - # 32k maximum size for embeddable images (an IE8 limitation). - MAX_IMAGE_SIZE = 32768 + # (32k - padding) maximum length for data-uri assets (an IE8 limitation). + MAX_IMAGE_SIZE = 32700 # CSS asset-embedding regexes for URL rewriting. EMBED_DETECTOR = /url\(['"]?([^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/ EMBEDDABLE = /[\A\/]embed\// - EMBED_REPLACER = /url\(__EMBED__([^\s)]+)(\?\d+)?\)/ + EMBED_REPLACER = /url\(__EMBED__(.+?)(\?\d+)?\)/ # MHTML file constants. - MHTML_START = "/*\r\nContent-Type: multipart/related; boundary=\"JAMMIT_MHTML_SEPARATOR\"\r\n\r\n" - MHTML_SEPARATOR = "--JAMMIT_MHTML_SEPARATOR\r\n" - MHTML_END = "*/\r\n" + MHTML_START = "/*\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 file constants. JST_START = "(function(){" JST_END = "})();" @@ -58,11 +59,11 @@ end # Concatenate together a list of JavaScript paths, and pass them through the # YUI Compressor (with munging enabled). JST can optionally be included. def compress_js(paths) - if (jst_paths = paths.grep(JST_EXT)).empty? + 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 @@ -70,10 +71,11 @@ # Concatenate and compress a list of CSS stylesheets. When compressing a # :datauri or :mhtml variant, post-processes the result to embed # referenced assets. 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) @@ -86,31 +88,56 @@ # 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. def compile_jst(paths) - namespace = Jammit.template_namespace - compiled = paths.grep(JST_EXT).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}');" + 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(/\n/, '').gsub("'", '\\\\\'') + name = template_name(path, base_path) + "#{namespace}['#{name}'] = #{Jammit.template_function}('#{contents}');" end - compiler = Jammit.include_jst_script ? File.read(DEFAULT_JST_SCRIPT) : ''; + 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 private + # Given a set of paths, find a common prefix path. + 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 + + # Determine the name of a JS template. If there's a common base path, use + # the namespaced prefix. Otherwise, simply use the filename. + 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 + # 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. def concatenate_and_tag_assets(paths, variant=nil) stylesheets = [paths].flatten.map do |css_path| - File.read(css_path).gsub(EMBED_DETECTOR) do |url| + 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 @@ -167,11 +194,11 @@ # 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. def rewrite_asset_path(path, file_path) asset_id = rails_asset_id(file_path) - asset_id.blank? ? path : "#{path}?#{asset_id}" + (!asset_id || asset_id == '') ? path : "#{path}?#{asset_id}" end # Similar to the AssetTagHelper's method of the same name, this will # determine the correct asset id for a file. def rails_asset_id(path) @@ -179,37 +206,43 @@ return asset_id if asset_id File.exists?(path) ? File.mtime(path).to_i.to_s : '' end # 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. + # 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. 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 unless font || encoded_contents(asset_path).length < MAX_IMAGE_SIZE return false if font && variant == :mhtml - true + return true end # Return the Base64-encoded contents of an asset on a single line. def encoded_contents(asset_path) - data = File.open(asset_path, 'rb'){|f| f.read } - Base64.encode64(data).gsub(/\n/, '') + 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 # Grab the mime-type of an asset, by filename. def mime_type(asset_path) EMBED_MIME_TYPES[File.extname(asset_path)] end # Concatenate together a list of asset files. def concatenate(paths) - [paths].flatten.map {|p| File.read(p) }.join("\n") + [paths].flatten.map {|p| read_binary_file(p) }.join("\n") end + # `File.read`, but in "binary" mode. + def read_binary_file(path) + File.open(path, 'rb') {|f| f.read } + end end end