require 'shellwords' require 'fileutils' require 'htmlcompressor' require 'uglifier' ## # Awestruct:Extensions:Minify is a transformer that minimizes JavaScript, CSS and HTML files. # The transform runs on the rendered stream before it's written to the output path. # # Minification is performed by the following libraries: # # uglifier (minifies javascript): http://github.com/lautis/uglifier # pngcrush (minifies PNG): http://pmt.sourceforge.net/pngcrush/ # # These commands must be available on your PATH in order to use them. # # This class is loaded as a transformer into the Awestruct pipeline. The # constructor accepts an array of symbols representing the file types to minimize. # # transformer Awestruct::Extensions::Minify.new # # This transform recognizes the following symbols: # # :js - JavaScript files with extension .js # :html - HTML files with extension .html # :png - PNG files with extension .png # # If no types are specified, the default value [:js] is used. # # In addition to registering the transformer in the pipeline, it must be enabled # by setting the following site property in _ext/config.yml: # # minify: true # # You can limit activation to one or more profiles: # # profiles: # production: # minify: true # # You can also configure the option arguments passed to the compressor programs. Here's # how you specify options arguments for the htmlcompressor command: # # minify_html_opts: # remove_intertag_spaces: true # compress_js: true # # Note that any hypen (-) must be represented as an underscore (_) in the configuration. module Awestruct module Extensions class Minify def initialize(types = [ :js ]) @types = types end def transform(site, page, input) if site.minify ext = File.extname(page.output_path) if !ext.empty? ext_sym = ext[1..-1].to_sym if @types.include?(ext_sym) case ext_sym when :html $LOG.debug "minifying html #{page.output_path}" if $LOG.debug? input = htmlcompressor(page, input, site.minify_html_opts) when :css # TODO: Figure out how to do this is sass / less and document it #print "minifying css #{page.output_path}" #input = yuicompressor_css(page, input) when :js $LOG.debug "minifying js #{page.output_path}" if $LOG.debug? input = js_compressor(page, input) when :png $LOG.debug "minifying png #{page.output_path}" if $LOG.debug? input = pngcrush(page, input) end end end end input end private class JSCompressor def compress( input ) Uglifier.new(:mangle => false).compile(input) end end def htmlcompressor(page, input, minify_html_opts) opts = minify_html_opts.nil? ? {}:minify_html_opts compressor(page, input, HtmlCompressor::Compressor.new(opts)) end def js_compressor(page, input) compressor(page, input, JSCompressor.new) end def compressor(page, input, compressor) output = compressor.compress input input_len = input.length output_len = output.length if input_len > output_len $LOG.debug " %d bytes -> %d bytes = %.1f%%" % [ input_len, output_len, 100 * output_len/input_len ] if $LOG.debug? output else $LOG.debug " no gain" if $LOG.debug? input end end def pngcrush(page, input) filename = page.source_path cmd = "pngcrush " + Shellwords.escape("#{filename}") + " /tmp/pngcrush" `#{cmd}` if $?.exitstatus != 0 raise "Failed to execute pngcrush: #{cmd}" end output = File.read('/tmp/pngcrush') input_len = File.stat(filename).size output_len = output.length if input_len > output_len $LOG.debug " %d bytes -> %d bytes = %.1f%%" % [ input_len, output_len, 100 * output_len/input_len ] if $LOG.debug? output else $LOG.debug " no gain" if $LOG.debug? input end end end end end