Use configurable content type for detection of minifiable content.
Squashed changes: - Prevent side effects of content type testing. - Test for inline minification in PHP files.
This commit is contained in:
parent
fbe71f51e4
commit
049dabbf15
12 changed files with 276 additions and 46 deletions
|
@ -12,6 +12,8 @@ Feature: Setting the right content type for files
|
|||
Then the content type should be "text/css"
|
||||
When I go to "/README"
|
||||
Then the content type should be "text/plain"
|
||||
When I go to "/index.php"
|
||||
Then the content type should be "text/php"
|
||||
|
||||
Scenario: Content type can be set explicitly via page or proxy or frontmatter
|
||||
Given a fixture app "content-type-app"
|
||||
|
@ -31,6 +33,7 @@ Feature: Setting the right content type for files
|
|||
When I go to "/override.html"
|
||||
Then the content type should be "text/neato"
|
||||
|
||||
@preserve_mime_types
|
||||
Scenario: Content types can be overridden with mime_type
|
||||
Given a fixture app "content-type-app"
|
||||
And a file named "config.rb" with:
|
||||
|
|
|
@ -26,6 +26,18 @@ Feature: Minify CSS
|
|||
When I go to "/stylesheets/report.css"
|
||||
Then I should see "p{border:1px solid #ff6600}"
|
||||
|
||||
Scenario: Rendering external css in a proxied resource
|
||||
Given a fixture app "minify-css-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
activate :minify_css
|
||||
proxy '/css-proxy', '/stylesheets/site.css', ignore: true
|
||||
"""
|
||||
And the Server is running at "minify-css-app"
|
||||
When I go to "/css-proxy"
|
||||
Then I should see "1" lines
|
||||
And I should see "only screen and (device-width"
|
||||
|
||||
Scenario: Rendering external css with passthrough compressor
|
||||
Given a fixture app "passthrough-app"
|
||||
And a file named "config.rb" with:
|
||||
|
@ -125,3 +137,53 @@ Feature: Minify CSS
|
|||
body{test:style;good:deal}
|
||||
</style>
|
||||
"""
|
||||
|
||||
Scenario: Rendering inline css in a PHP document
|
||||
Given a fixture app "minify-css-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
activate :minify_css, :inline => true
|
||||
"""
|
||||
And the Server is running at "minify-css-app"
|
||||
When I go to "/inline-css.php"
|
||||
Then I should see:
|
||||
"""
|
||||
<?='Hello'?>
|
||||
|
||||
<style>
|
||||
body{test:style;good:deal}
|
||||
</style>
|
||||
"""
|
||||
|
||||
Scenario: Rendering inline css in a proxied resource
|
||||
Given a fixture app "minify-css-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
activate :minify_css, :inline => true
|
||||
proxy '/inline-css-proxy', '/inline-css.html', ignore: true
|
||||
"""
|
||||
And the Server is running at "minify-css-app"
|
||||
When I go to "/inline-css-proxy"
|
||||
Then I should see:
|
||||
"""
|
||||
<style>
|
||||
body{test:style;good:deal}
|
||||
</style>
|
||||
"""
|
||||
|
||||
@preserve_mime_types
|
||||
Scenario: Configuring content types of resources to be minified
|
||||
Given a fixture app "minify-css-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
mime_type('.xcss', 'text/x-css')
|
||||
activate :minify_css, content_types: ['text/x-css'],
|
||||
inline: true,
|
||||
inline_content_types: ['text/html']
|
||||
"""
|
||||
And the Server is running at "minify-css-app"
|
||||
When I go to "/stylesheets/site.xcss"
|
||||
Then I should see "1" lines
|
||||
And I should see "only screen and (device-width"
|
||||
When I go to "/inline-css.php"
|
||||
Then I should see "8" lines
|
||||
|
|
|
@ -88,7 +88,7 @@ Feature: Minify Javascript
|
|||
</script>
|
||||
"""
|
||||
|
||||
Scenario: Rendering inline css with a passthrough minifier using activate-style compressor
|
||||
Scenario: Rendering inline JS with a passthrough minifier using activate-style compressor
|
||||
Given a fixture app "passthrough-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
|
@ -148,6 +148,42 @@ Feature: Minify Javascript
|
|||
</script>
|
||||
"""
|
||||
|
||||
Scenario: Rendering inline js in a PHP document
|
||||
Given a fixture app "minify-js-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
activate :minify_javascript, :inline => true
|
||||
"""
|
||||
And the Server is running at "minify-js-app"
|
||||
When I go to "/inline-js.php"
|
||||
Then I should see:
|
||||
"""
|
||||
<?='Hello'?>
|
||||
|
||||
<script>
|
||||
!function(){should(),all.be(),on={one:line}}();
|
||||
</script>
|
||||
<script type='text/javascript'>
|
||||
//<!--
|
||||
!function(){one,line(),here()}();
|
||||
//-->
|
||||
</script>
|
||||
<script type='text/html'>
|
||||
I'm a jQuery {{template}}.
|
||||
</script>
|
||||
"""
|
||||
|
||||
Scenario: Rendering inline js in a proxied resource
|
||||
Given a fixture app "minify-js-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
activate :minify_javascript, :inline => true
|
||||
proxy '/inline-js-proxy', '/inline-js.html', ignore: true
|
||||
"""
|
||||
And the Server is running at "minify-js-app"
|
||||
When I go to "/inline-js-proxy"
|
||||
Then I should see "14" lines
|
||||
|
||||
Scenario: Rendering external js with the feature enabled
|
||||
Given a fixture app "minify-js-app"
|
||||
And a file named "config.rb" with:
|
||||
|
@ -160,6 +196,17 @@ Feature: Minify Javascript
|
|||
When I go to "/more-js/other.js"
|
||||
Then I should see "1" lines
|
||||
|
||||
Scenario: Rendering external js in a proxied resource
|
||||
Given a fixture app "minify-js-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
activate :minify_javascript
|
||||
proxy '/js-proxy', '/javascripts/js_test.js', ignore: true
|
||||
"""
|
||||
And the Server is running at "minify-js-app"
|
||||
When I go to "/js-proxy"
|
||||
Then I should see "1" lines
|
||||
|
||||
Scenario: Rendering external js with a passthrough minifier
|
||||
And the Server is running at "passthrough-app"
|
||||
When I go to "/javascripts/js_test.js"
|
||||
|
|
7
middleman-core/features/support/preserve_mime_types.rb
Normal file
7
middleman-core/features/support/preserve_mime_types.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
Around('@preserve_mime_types') do |_scenario, block|
|
||||
mime_types = ::Rack::Mime::MIME_TYPES.clone
|
||||
|
||||
block.call
|
||||
|
||||
::Rack::Mime::MIME_TYPES.replace mime_types
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
<?="I'm a PHP file!"?>
|
|
@ -0,0 +1,8 @@
|
|||
<?='Hello'?>
|
||||
|
||||
<style>
|
||||
body {
|
||||
test: style;
|
||||
good: deal;
|
||||
}
|
||||
</style>
|
5
middleman-core/fixtures/minify-css-app/source/stylesheets/site.xcss.sass
Executable file
5
middleman-core/fixtures/minify-css-app/source/stylesheets/site.xcss.sass
Executable file
|
@ -0,0 +1,5 @@
|
|||
@import "compass/reset"
|
||||
|
||||
@media handheld, only screen and (device-width: 768px)
|
||||
body
|
||||
display: block
|
22
middleman-core/fixtures/minify-js-app/source/inline-js.php
Executable file
22
middleman-core/fixtures/minify-js-app/source/inline-js.php
Executable file
|
@ -0,0 +1,22 @@
|
|||
<?='Hello'?>
|
||||
|
||||
<script>
|
||||
;(function() {
|
||||
this;
|
||||
should();
|
||||
all.be();
|
||||
on = { one: line };
|
||||
})();
|
||||
</script>
|
||||
<script type='text/javascript'>
|
||||
//<!--
|
||||
;(function() {
|
||||
one;
|
||||
line();
|
||||
here();
|
||||
})();
|
||||
//-->
|
||||
</script>
|
||||
<script type='text/html'>
|
||||
I'm a jQuery {{template}}.
|
||||
</script>
|
|
@ -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);
|
||||
};
|
|
@ -24,6 +24,9 @@ module Middleman
|
|||
# Sourcemap format
|
||||
::Rack::Mime::MIME_TYPES['.map'] = 'application/json; charset=utf-8'
|
||||
|
||||
# Create a MIME type for PHP files (for detection by extensions)
|
||||
::Rack::Mime::MIME_TYPES['.php'] = 'text/php'
|
||||
|
||||
app.extend ClassMethods
|
||||
app.extend ServerMethods
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
|||
option :compressor, nil, 'Set the CSS compressor to use.'
|
||||
option :inline, false, 'Whether to minify CSS inline within HTML files'
|
||||
option :ignore, [], 'Patterns to avoid minifying'
|
||||
option :content_types, %w(text/css), 'Content types of resources that contain CSS'
|
||||
option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline CSS'
|
||||
|
||||
def initialize(app, options_hash={}, &block)
|
||||
super
|
||||
|
@ -16,7 +18,9 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
|||
# Setup Rack middleware to minify CSS
|
||||
app.use Rack, compressor: chosen_compressor,
|
||||
ignore: Array(options[:ignore]) + [/\.min\./],
|
||||
inline: options[:inline]
|
||||
inline: options[:inline],
|
||||
content_types: options[:content_types],
|
||||
inline_content_types: options[:inline_content_types]
|
||||
end
|
||||
|
||||
class SassCompressor
|
||||
|
@ -39,6 +43,8 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
|||
@compressor = options[:compressor]
|
||||
@ignore = options[:ignore]
|
||||
@inline = options[:inline]
|
||||
@content_types = options[:content_types]
|
||||
@inline_content_types = options[:inline_content_types]
|
||||
end
|
||||
|
||||
# Rack interface
|
||||
|
@ -47,19 +53,18 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
|||
def call(env)
|
||||
status, headers, response = @app.call(env)
|
||||
|
||||
if inline_html_content?(env['PATH_INFO'])
|
||||
minified = ::Middleman::Util.extract_response_text(response)
|
||||
minified.gsub!(INLINE_CSS_REGEX) do
|
||||
$1 << @compressor.compress($2) << $3
|
||||
end
|
||||
content_type = headers['Content-Type'].try(:slice, /^[^;]*/)
|
||||
path = env['PATH_INFO']
|
||||
|
||||
minified = if @inline && minifiable_inline?(content_type)
|
||||
minify_inline(::Middleman::Util.extract_response_text(response))
|
||||
elsif minifiable?(content_type) && !ignore?(path)
|
||||
minify(::Middleman::Util.extract_response_text(response))
|
||||
end
|
||||
|
||||
if minified
|
||||
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
|
||||
response = [minified]
|
||||
elsif standalone_css_content?(env['PATH_INFO'])
|
||||
minified_css = @compressor.compress(::Middleman::Util.extract_response_text(response))
|
||||
|
||||
headers['Content-Length'] = ::Rack::Utils.bytesize(minified_css).to_s
|
||||
response = [minified_css]
|
||||
end
|
||||
|
||||
[status, headers, response]
|
||||
|
@ -67,12 +72,41 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
|||
|
||||
private
|
||||
|
||||
def inline_html_content?(path)
|
||||
(path.end_with?('.html') || path.end_with?('.php')) && @inline
|
||||
# Whether the path should be ignored
|
||||
# @param [String] path
|
||||
# @return [Boolean]
|
||||
def ignore?(path)
|
||||
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
|
||||
end
|
||||
|
||||
def standalone_css_content?(path)
|
||||
path.end_with?('.css') && @ignore.none? { |ignore| Middleman::Util.path_match(ignore, path) }
|
||||
# Whether this type of content can be minified
|
||||
# @param [String, nil] content_type
|
||||
# @return [Boolean]
|
||||
def minifiable?(content_type)
|
||||
@content_types.include?(content_type)
|
||||
end
|
||||
|
||||
# Whether this type of content contains inline content that can be minified
|
||||
# @param [String, nil] content_type
|
||||
# @return [Boolean]
|
||||
def minifiable_inline?(content_type)
|
||||
@inline_content_types.include?(content_type)
|
||||
end
|
||||
|
||||
# Minify the content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify(content)
|
||||
@compressor.compress(content)
|
||||
end
|
||||
|
||||
# Detect and minify inline content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify_inline(content)
|
||||
content.gsub(INLINE_CSS_REGEX) do
|
||||
$1 + minify($2) + $3
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
|||
option :compressor, nil, 'Set the JS compressor to use.'
|
||||
option :inline, false, 'Whether to minify JS inline within HTML files'
|
||||
option :ignore, [], 'Patterns to avoid minifying'
|
||||
option :content_types, %w(application/javascript), 'Content types of resources that contain JS'
|
||||
option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline JS'
|
||||
|
||||
def initialize(app, options_hash={}, &block)
|
||||
super
|
||||
|
@ -16,14 +18,18 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
|||
::Uglifier.new
|
||||
end
|
||||
|
||||
# Setup Rack middleware to minify CSS
|
||||
# Setup Rack middleware to minify JS
|
||||
app.use Rack, compressor: chosen_compressor,
|
||||
ignore: Array(options[:ignore]) + [/\.min\./],
|
||||
inline: options[:inline]
|
||||
inline: options[:inline],
|
||||
content_types: options[:content_types],
|
||||
inline_content_types: options[:inline_content_types]
|
||||
end
|
||||
|
||||
# Rack middleware to look for JS and compress it
|
||||
class Rack
|
||||
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m
|
||||
|
||||
# Init
|
||||
# @param [Class] app
|
||||
# @param [Hash] options
|
||||
|
@ -32,6 +38,8 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
|||
@compressor = options[:compressor]
|
||||
@ignore = options[:ignore]
|
||||
@inline = options[:inline]
|
||||
@content_types = options[:content_types]
|
||||
@inline_content_types = options[:inline_content_types]
|
||||
end
|
||||
|
||||
# Rack interface
|
||||
|
@ -40,25 +48,18 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
|||
def call(env)
|
||||
status, headers, response = @app.call(env)
|
||||
|
||||
type = headers['Content-Type'].try(:slice, /^[^;]*/)
|
||||
path = env['PATH_INFO']
|
||||
|
||||
begin
|
||||
if @inline && (path.end_with?('.html') || path.end_with?('.php'))
|
||||
uncompressed_source = ::Middleman::Util.extract_response_text(response)
|
||||
minified = if @inline && minifiable_inline?(type)
|
||||
minify_inline(::Middleman::Util.extract_response_text(response))
|
||||
elsif minifiable?(type) && !ignore?(path)
|
||||
minify(::Middleman::Util.extract_response_text(response))
|
||||
end
|
||||
|
||||
minified = minify_inline_content(uncompressed_source)
|
||||
|
||||
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
|
||||
response = [minified]
|
||||
elsif path.end_with?('.js') && @ignore.none? { |ignore| Middleman::Util.path_match(ignore, path) }
|
||||
uncompressed_source = ::Middleman::Util.extract_response_text(response)
|
||||
minified = @compressor.compress(uncompressed_source)
|
||||
|
||||
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
|
||||
response = [minified]
|
||||
end
|
||||
rescue ExecJS::ProgramError => e
|
||||
warn "WARNING: Couldn't compress JavaScript in #{path}: #{e.message}"
|
||||
if minified
|
||||
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
|
||||
response = [minified]
|
||||
end
|
||||
|
||||
[status, headers, response]
|
||||
|
@ -66,19 +67,48 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
|||
|
||||
private
|
||||
|
||||
def minify_inline_content(uncompressed_source)
|
||||
uncompressed_source.gsub(/(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m) do |match|
|
||||
first = $1
|
||||
javascript = $2
|
||||
last = $3
|
||||
# Whether the path should be ignored
|
||||
# @param [String] path
|
||||
# @return [Boolean]
|
||||
def ignore?(path)
|
||||
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
|
||||
end
|
||||
|
||||
# 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)
|
||||
# Whether this type of content can be minified
|
||||
# @param [String, nil] content_type
|
||||
# @return [Boolean]
|
||||
def minifiable?(content_type)
|
||||
@content_types.include?(content_type)
|
||||
end
|
||||
|
||||
first << minified_js << last
|
||||
# Whether this type of content contains inline content that can be minified
|
||||
# @param [String, nil] content_type
|
||||
# @return [Boolean]
|
||||
def minifiable_inline?(content_type)
|
||||
@inline_content_types.include?(content_type)
|
||||
end
|
||||
|
||||
# Minify the content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify(content)
|
||||
@compressor.compress(content)
|
||||
rescue ExecJS::ProgramError => e
|
||||
warn "WARNING: Couldn't compress JavaScript in #{path}: #{e.message}"
|
||||
content
|
||||
end
|
||||
|
||||
# Detect and minify inline content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify_inline(content)
|
||||
content.gsub(INLINE_JS_REGEX) do |match|
|
||||
first, inline_content, last = $1, $2, $3
|
||||
|
||||
# Only compress script tags that contain JavaScript (as opposed to
|
||||
# something like jQuery templates, identified with a "text/html" type).
|
||||
if first.include?('<script>') || first.include?('text/javascript')
|
||||
first + minify(inline_content) + last
|
||||
else
|
||||
match
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue