From c9b7bf4b59dedc639c2c68336afbe61df3ba7ead Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Fri, 10 Feb 2012 20:09:39 -0800 Subject: [PATCH] Extension that will pre-Gzip JS and CSS files. Gzipped assets can be served directly by Apache or Nginx with the proper configuration, and pre-zipping means that we can use a more agressive compression level at no CPU cost per request. --- middleman-more/features/gzip.feature | 18 +++++ middleman-more/fixtures/gzip-app/config.rb | 1 + .../fixtures/gzip-app/source/index.html | 0 .../gzip-app/source/javascripts/test.js | 1 + .../gzip-app/source/stylesheets/test.css | 1 + middleman-more/lib/middleman-more.rb | 7 +- .../middleman-more/extensions/gzip_assets.rb | 74 +++++++++++++++++++ 7 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 middleman-more/features/gzip.feature create mode 100644 middleman-more/fixtures/gzip-app/config.rb create mode 100644 middleman-more/fixtures/gzip-app/source/index.html create mode 100644 middleman-more/fixtures/gzip-app/source/javascripts/test.js create mode 100644 middleman-more/fixtures/gzip-app/source/stylesheets/test.css create mode 100644 middleman-more/lib/middleman-more/extensions/gzip_assets.rb diff --git a/middleman-more/features/gzip.feature b/middleman-more/features/gzip.feature new file mode 100644 index 00000000..f6378efd --- /dev/null +++ b/middleman-more/features/gzip.feature @@ -0,0 +1,18 @@ +Feature: GZIP assets during build + + Scenario: Built assets should be gzipped + Given a successfully built app at "gzip-app" + Then the following files should exist: + | build/javascripts/test.js.gz | + | build/stylesheets/test.css.gz | + | build/index.html | + When I run `file build/javascripts/test.js.gz` + Then the output should contain "gzip" + + Scenario: Preview server doesn't change + Given the Server is running at "gzip-app" + When I go to "/javascripts/test.js" + Then I should see "test_function" + When I go to "/stylesheets/test.css" + Then I should see "test_selector" + \ No newline at end of file diff --git a/middleman-more/fixtures/gzip-app/config.rb b/middleman-more/fixtures/gzip-app/config.rb new file mode 100644 index 00000000..48922364 --- /dev/null +++ b/middleman-more/fixtures/gzip-app/config.rb @@ -0,0 +1 @@ +activate :gzip_assets diff --git a/middleman-more/fixtures/gzip-app/source/index.html b/middleman-more/fixtures/gzip-app/source/index.html new file mode 100644 index 00000000..e69de29b diff --git a/middleman-more/fixtures/gzip-app/source/javascripts/test.js b/middleman-more/fixtures/gzip-app/source/javascripts/test.js new file mode 100644 index 00000000..840ce2db --- /dev/null +++ b/middleman-more/fixtures/gzip-app/source/javascripts/test.js @@ -0,0 +1 @@ +function test_function() {} diff --git a/middleman-more/fixtures/gzip-app/source/stylesheets/test.css b/middleman-more/fixtures/gzip-app/source/stylesheets/test.css new file mode 100644 index 00000000..ce253a59 --- /dev/null +++ b/middleman-more/fixtures/gzip-app/source/stylesheets/test.css @@ -0,0 +1 @@ +test_selector {} diff --git a/middleman-more/lib/middleman-more.rb b/middleman-more/lib/middleman-more.rb index 85cb4eea..bd992338 100644 --- a/middleman-more/lib/middleman-more.rb +++ b/middleman-more/lib/middleman-more.rb @@ -40,6 +40,9 @@ module Middleman # MinifyJavascript uses the YUI compressor to shrink JS files autoload :MinifyJavascript, "middleman-more/extensions/minify_javascript" + + # GZIP assets during build + autoload :GzipAssets, "middleman-more/extensions/gzip_assets" end # Setup renderers @@ -65,4 +68,6 @@ module Middleman ::Middleman::Extensions::MinifyJavascript } Extensions.register(:relative_assets) { ::Middleman::Extensions::RelativeAssets } -end \ No newline at end of file + Extensions.register(:gzip_assets) { + ::Middleman::Extensions::GzipAssets } +end diff --git a/middleman-more/lib/middleman-more/extensions/gzip_assets.rb b/middleman-more/lib/middleman-more/extensions/gzip_assets.rb new file mode 100644 index 00000000..011b0986 --- /dev/null +++ b/middleman-more/lib/middleman-more/extensions/gzip_assets.rb @@ -0,0 +1,74 @@ +require 'zlib' +require 'stringio' + +module Middleman::Extensions + + # This extension Gzips assets when building. + # Gzipped assets can be served directly by Apache or + # Nginx with the proper configuration, and pre-zipping means that we + # can use a more agressive compression level at no CPU cost per request. + module GzipAssets + class << self + def registered(app) + return unless app.inst.build? + + app.after_configuration do + # Register a reroute transform that adds .gz to asset paths + sitemap.reroute do |destination, page| + if %w(.js .css).include? page.ext + destination + '.gz' + else + destination + end + end + + use GzipRack + end + end + alias :included :registered + end + + # Rack middleware to GZip asset files + class GzipRack + + # Init + # @param [Class] app + # @param [Hash] options + def initialize(app, options={}) + @app = app + end + + # Rack interface + # @param [Rack::Environmemt] env + # @return [Array] + def call(env) + status, headers, response = @app.call(env) + + if env["PATH_INFO"].match(/\.(js|css).gz$/) + contents = case(response) + when String + response + when Array + response.join + when Rack::Response + response.body.join + when Rack::File + File.read(response.path) + end + + gzipped = "" + StringIO.open(gzipped) do |s| + gz = Zlib::GzipWriter.new(s, Zlib::BEST_COMPRESSION) + gz.write contents + gz.close + end + + headers["Content-Length"] = ::Rack::Utils.bytesize(gzipped).to_s + response = [gzipped] + end + + [status, headers, response] + end + end + end +end