Merge pull request #353 from bhollis/minify
Minify all JS and CSS via middleware
This commit is contained in:
commit
cd89e5ffa4
16 changed files with 347 additions and 80 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
"""
|
|
@ -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
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
:css
|
||||
body {
|
||||
test: style;
|
||||
good: deal;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
display: block;
|
||||
}
|
|
@ -1 +1 @@
|
|||
require "coffee-filter"
|
||||
require "coffee-filter"
|
||||
|
|
|
@ -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}}.
|
|
@ -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);
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
:sass
|
||||
body
|
||||
test: style
|
||||
good: deal
|
||||
%style(type="text/css")
|
||||
:sass
|
||||
body
|
||||
test: style
|
||||
good: deal
|
|
@ -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}}.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue