Merge pull request #353 from bhollis/minify

Minify all JS and CSS via middleware
This commit is contained in:
Thomas Reynolds 2012-04-09 11:10:36 -07:00
commit cd89e5ffa4
16 changed files with 347 additions and 80 deletions

View file

@ -68,10 +68,14 @@ Then /^I should see '([^\']*)'$/ do |expected|
@browser.last_response.body.should include(expected)
end
Then /^I should see:$/ do |expected|
@browser.last_response.body.should include(expected)
end
Then /^I should not see "([^\"]*)"$/ do |expected|
@browser.last_response.body.should_not include(expected)
end
Then /^I should see "([^\"]*)" lines$/ do |lines|
@browser.last_response.body.chomp.split($/).length.should == lines.to_i
end
end

View file

@ -59,8 +59,9 @@ module Middleman::Templates
return unless options[:bundler]
template "shared/Gemfile.tt", File.join(location, "Gemfile")
say_status :run, "bundle install"
print `cd #{location} && "#{Gem.ruby}" -rubygems "#{Gem.bin_path('bundler', 'bundle')}" install`
inside(location) do
run('bundle install', :capture => true)
end
end
end
end

View file

@ -1,6 +1,9 @@
Feature: Minify CSS
In order reduce bytes sent to client and appease YSlow
Background:
Given current environment is "build"
Scenario: Rendering external css with the feature disabled
Given "minify_css" feature is "disabled"
And the Server is running at "minify-css-app"
@ -14,8 +17,51 @@ Feature: Minify CSS
When I go to "/stylesheets/site.css"
Then I should see "1" lines
And I should see "only screen and (device-width"
When I go to "/more-css/site.css"
Then I should see "1" lines
Scenario: Rendering external css with passthrough compressor
Given the Server is running at "passthrough-app"
When I go to "/stylesheets/site.css"
Then I should see "55" lines
Then I should see "55" lines
Scenario: Rendering inline css with the feature disabled
Given "minify_css" feature is "disabled"
And the Server is running at "minify-css-app"
When I go to "/inline-css.html"
Then I should see:
"""
<style type='text/css'>
/*<![CDATA[*/
body {
test: style;
good: deal;
}
/*]]>*/
</style>
"""
Scenario: Rendering inline css with a passthrough minifier
Given the Server is running at "passthrough-app"
When I go to "/inline-css.html"
Then I should see:
"""
<style type='text/css'>
body {
test: style;
good: deal; }
</style>
"""
Scenario: Rendering inline css with the feature enabled
Given "minify_css" feature is "enabled"
And the Server is running at "minify-css-app"
When I go to "/inline-css.html"
Then I should see:
"""
<style type='text/css'>
/*<![CDATA[*/
body{test:style;good:deal}
/*]]>*/
</style>
"""

View file

@ -8,24 +8,106 @@ Feature: Minify Javascript
Given "minify_javascript" feature is "disabled"
And the Server is running at "minify-js-app"
When I go to "/inline-js.html"
Then I should see "10" lines
Then I should see:
"""
<script type='text/javascript'>
//<![CDATA[
;(function() {
this;
should();
all.be();
on = { one: line };
})();
//]]>
</script>
<script>
;(function() {
this;
should();
too();
})();
</script>
<script type='text/javascript'>
//<!--
;(function() {
one;
line();
here();
})();
//-->
</script>
<script type='text/html'>
I'm a jQuery {{template}}.
</script>
"""
Scenario: Rendering inline js with a passthrough minifier
Given the Server is running at "passthrough-app"
When I go to "/inline-js.html"
Then I should see "11" lines
Then I should see:
"""
<script type='text/javascript'>
//<![CDATA[
;(function() {
this;
should();
all.be();
on = { one: line };
})();
//]]>
</script>
<script>
;(function() {
this;
should();
too();
})();
</script>
<script type='text/javascript'>
//<!--
;(function() {
one;
line();
here();
})();
//-->
</script>
<script type='text/html'>
I'm a jQuery {{template}}.
</script>
"""
Scenario: Rendering inline js with the feature enabled
Given "minify_javascript" feature is "enabled"
And the Server is running at "minify-js-app"
When I go to "/inline-js.html"
Then I should see "5" lines
Then I should see:
"""
<script type='text/javascript'>
//<![CDATA[
(function(){this,should(),all.be(),on={one:line}})();
//]]>
</script>
<script>
(function(){this,should(),too()})();
</script>
<script type='text/javascript'>
//<!--
(function(){one,line(),here()})();
//-->
</script>
<script type='text/html'>
I'm a jQuery {{template}}.
</script>
"""
Scenario: Rendering external js with the feature enabled
Given "minify_javascript" feature is "enabled"
And the Server is running at "minify-js-app"
When I go to "/javascripts/js_test.js"
Then I should see "1" lines
When I go to "/more-js/other.js"
Then I should see "1" lines
Scenario: Rendering external js with a passthrough minifier
And the Server is running at "passthrough-app"
@ -36,7 +118,7 @@ Feature: Minify Javascript
Given "minify_javascript" feature is "enabled"
And the Server is running at "minify-js-app"
When I go to "/inline-coffeescript.html"
Then I should see "5" lines
Then I should see "6" lines
Scenario: Rendering external js (coffeescript) with the feature enabled
Given "minify_javascript" feature is "enabled"
@ -47,9 +129,10 @@ Feature: Minify Javascript
Scenario: Rendering inline js (coffeescript) with a passthrough minifier
Given the Server is running at "passthrough-app"
When I go to "/inline-coffeescript.html"
Then I should see "17" lines
Then I should see "16" lines
Scenario: Rendering external js (coffeescript) with a passthrough minifier
And the Server is running at "passthrough-app"
When I go to "/javascripts/coffee_test.js"
Then I should see "11" lines
Then I should see "11" lines

View file

@ -0,0 +1,5 @@
:css
body {
test: style;
good: deal;
}

View file

@ -0,0 +1,3 @@
body {
display: block;
}

View file

@ -1 +1 @@
require "coffee-filter"
require "coffee-filter"

View file

@ -4,4 +4,25 @@
should();
all.be();
on = { one: line };
})();
})();
%script
:plain
;(function() {
this;
should();
too();
})();
%script(type="text/javascript")
:plain
//<!--
;(function() {
one;
line();
here();
})();
//-->
%script(type="text/html")
I'm a jQuery {{template}}.

View file

@ -0,0 +1,8 @@
var race;
var __slice = Array.prototype.slice;
race = function() {
var runners, winner;
winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
return print(winner, runners);
};

View file

@ -4,14 +4,14 @@ module ::PassThrough
end
end
set :js_compressor, ::PassThrough
set :css_compressor, ::PassThrough
activate :minify_javascript
activate :minify_css
set :js_compressor, ::PassThrough
set :css_compressor, ::PassThrough
with_layout false do
page "/inline-css.html"
page "/inline-js.html"
page "/inline-coffeescript.html"
end
end

View file

@ -1,4 +1,5 @@
:sass
body
test: style
good: deal
%style(type="text/css")
:sass
body
test: style
good: deal

View file

@ -4,4 +4,25 @@
should();
all.be();
on = { one: line };
})();
})();
%script
:plain
;(function() {
this;
should();
too();
})();
%script(type="text/javascript")
:plain
//<!--
;(function() {
one;
line();
here();
})();
//-->
%script(type="text/html")
I'm a jQuery {{template}}.

View file

@ -9,10 +9,6 @@ module Middleman::CoreExtensions::Sprockets
# Once registered
def registered(app)
# Default compression to off
app.set :js_compressor, false
app.set :css_compressor, false
# Add class methods to context
app.send :include, InstanceMethods
@ -70,28 +66,10 @@ module Middleman::CoreExtensions::Sprockets
end
end
# Remove old compressors
# Remove compressors, we handle these with middleware
unregister_bundle_processor 'application/javascript', :js_compressor
unregister_bundle_processor 'text/css', :css_compressor
# Register compressor from config
register_bundle_processor 'application/javascript', :js_compressor do |context, data|
if context.pathname.to_s =~ /\.min\./
data
else
app.js_compressor.compress(data)
end
end if app.js_compressor
# Register compressor from config
register_bundle_processor 'text/css', :css_compressor do |context, data|
if context.pathname.to_s =~ /\.min\./
data
else
app.css_compressor.compress(data)
end
end if app.css_compressor
# configure search paths
append_path app.js_dir
append_path app.css_dir

View file

@ -53,18 +53,7 @@ module Middleman::Extensions
dirpath = Pathname.new(File.dirname(path))
if path =~ /(^\/$)|(\.(htm|html|php|css|js)$)/
body = case(response)
when String
response
when Array
response.join
when Rack::Response
response.body.join
when Rack::File
File.read(response.path)
else
response.to_s
end
body = extract_response_text(response)
if body
# TODO: This regex will change some paths in plan HTML (not in a tag) - is that OK?
@ -90,6 +79,23 @@ module Middleman::Extensions
end
[status, headers, response]
end
private
def extract_response_text(response)
case(response)
when String
response
when Array
response.join
when Rack::Response
response.body.join
when Rack::File
File.read(response.path)
else
response.to_s
end
end
end
end

View file

@ -9,18 +9,84 @@ module Middleman::Extensions
# Once registered
def registered(app)
# Tell Sprockets to use the built in CSSMin
app.set :css_compressor, false
app.after_configuration do
if !css_compressor
unless respond_to?(:css_compressor) && css_compressor
require "middleman-more/extensions/minify_css/rainpress"
set :css_compressor, ::Rainpress
end
# Setup Rack to watch for inline JS
use InlineCSSRack, :compressor => css_compressor
end
end
alias :included :registered
end
end
# Rack middleware to look for JS in HTML and compress it
class InlineCSSRack
# Init
# @param [Class] app
# @param [Hash] options
def initialize(app, options={})
@app = app
@compressor = options[:compressor]
end
# Rack interface
# @param [Rack::Environmemt] env
# @return [Array]
def call(env)
status, headers, response = @app.call(env)
path = env["PATH_INFO"]
if path.end_with?('.html') || path.end_with?('.php')
uncompressed_source = extract_response_text(response)
minified = uncompressed_source.gsub(/(<style[^>]*?>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m) do |match|
first = $1
css = $2
last = $3
minified_css = @compressor.compress(css)
first << minified_css << last
end
headers["Content-Length"] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
elsif path.end_with?('.css') && path !~ /\.min\./
uncompressed_source = extract_response_text(response)
minified_css = @compressor.compress(uncompressed_source)
headers["Content-Length"] = ::Rack::Utils.bytesize(minified_css).to_s
response = [minified_css]
end
[status, headers, response]
end
private
def extract_response_text(response)
case(response)
when String
response
when Array
response.join
when Rack::Response
response.body.join
when Rack::File
File.read(response.path)
else
response.to_s
end
end
end
# Register extension
# register :minify_css, MinifyCss
end
end

View file

@ -9,12 +9,11 @@ module Middleman::Extensions
# Once registered
def registered(app)
app.set :js_compressor, false
# Once config is parsed
app.after_configuration do
# Tell sprockets which compressor to use
if !js_compressor
unless respond_to?(:js_compressor) && js_compressor
require 'uglifier'
set :js_compressor, ::Uglifier.new
end
@ -43,35 +42,60 @@ module Middleman::Extensions
def call(env)
status, headers, response = @app.call(env)
if env["PATH_INFO"].match(/\.html$/)
uncompressed_source = case(response)
when String
response
when Array
response.join
when Rack::Response
response.body.join
when Rack::File
File.read(response.path)
end
path = env["PATH_INFO"]
minified = uncompressed_source.gsub(/(<scri.*?\/\/<!\[CDATA\[\n)(.*?)(\/\/\]\].*?<\/script>)/m) do |m|
if path.end_with?('.html') || path.end_with?('.php')
uncompressed_source = extract_response_text(response)
minified = uncompressed_source.gsub(/(<script[^>]*?>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m) do |match|
first = $1
uncompressed_source = $2
javascript = $2
last = $3
minified_js = @compressor.compress(uncompressed_source)
first << minified_js << "\n" << last
# Only compress script tags that contain JavaScript (as opposed
# to something like jQuery templates, identified with a "text/html"
# type.
if first =~ /<script>/ || first.include?('text/javascript')
minified_js = @compressor.compress(javascript)
first << minified_js << last
else
match
end
end
headers["Content-Length"] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
elsif path.end_with?('.js') && path !~ /\.min\./
uncompressed_source = extract_response_text(response)
minified_js = @compressor.compress(uncompressed_source)
headers["Content-Length"] = ::Rack::Utils.bytesize(minified_js).to_s
response = [minified_js]
end
[status, headers, response]
end
private
def extract_response_text(response)
case(response)
when String
response
when Array
response.join
when Rack::Response
response.body.join
when Rack::File
File.read(response.path)
else
response.to_s
end
end
end
end
# Register extension
# register :minify_javascript, MinifyJavascript
end
end