From 049dabbf153e56cd56b3c2830ad31f2878e5c882 Mon Sep 17 00:00:00 2001 From: Andrew Kvalheim Date: Sun, 12 Oct 2014 15:44:16 -0700 Subject: [PATCH] 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. --- middleman-core/features/content_type.feature | 3 + middleman-core/features/minify_css.feature | 64 +++++++++++++- .../features/minify_javascript.feature | 49 ++++++++++- .../features/support/preserve_mime_types.rb | 7 ++ .../content-type-app/source/index.php | 1 + .../minify-css-app/source/inline-css.php | 8 ++ .../source/stylesheets/site.xcss.sass | 5 ++ .../minify-js-app/source/inline-js.php | 22 +++++ .../source/javascripts/js_test.xjs | 8 ++ .../middleman-core/core_extensions/request.rb | 3 + .../middleman-more/extensions/minify_css.rb | 64 ++++++++++---- .../extensions/minify_javascript.rb | 88 +++++++++++++------ 12 files changed, 276 insertions(+), 46 deletions(-) create mode 100644 middleman-core/features/support/preserve_mime_types.rb create mode 100644 middleman-core/fixtures/content-type-app/source/index.php create mode 100644 middleman-core/fixtures/minify-css-app/source/inline-css.php create mode 100755 middleman-core/fixtures/minify-css-app/source/stylesheets/site.xcss.sass create mode 100755 middleman-core/fixtures/minify-js-app/source/inline-js.php create mode 100644 middleman-core/fixtures/minify-js-app/source/javascripts/js_test.xjs diff --git a/middleman-core/features/content_type.feature b/middleman-core/features/content_type.feature index 175361d8..95aeadab 100644 --- a/middleman-core/features/content_type.feature +++ b/middleman-core/features/content_type.feature @@ -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: diff --git a/middleman-core/features/minify_css.feature b/middleman-core/features/minify_css.feature index 0c707861..29c3e8c6 100644 --- a/middleman-core/features/minify_css.feature +++ b/middleman-core/features/minify_css.feature @@ -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: @@ -124,4 +136,54 @@ Feature: Minify CSS - """ \ No newline at end of file + """ + + 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: + """ + + + + """ + + 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: + """ + + """ + + @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 diff --git a/middleman-core/features/minify_javascript.feature b/middleman-core/features/minify_javascript.feature index 1fb0e699..a8235368 100644 --- a/middleman-core/features/minify_javascript.feature +++ b/middleman-core/features/minify_javascript.feature @@ -88,7 +88,7 @@ Feature: Minify Javascript """ - 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 """ + 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: + """ + + + + + + """ + + 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" diff --git a/middleman-core/features/support/preserve_mime_types.rb b/middleman-core/features/support/preserve_mime_types.rb new file mode 100644 index 00000000..a791e49c --- /dev/null +++ b/middleman-core/features/support/preserve_mime_types.rb @@ -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 diff --git a/middleman-core/fixtures/content-type-app/source/index.php b/middleman-core/fixtures/content-type-app/source/index.php new file mode 100644 index 00000000..eb55fa96 --- /dev/null +++ b/middleman-core/fixtures/content-type-app/source/index.php @@ -0,0 +1 @@ + diff --git a/middleman-core/fixtures/minify-css-app/source/inline-css.php b/middleman-core/fixtures/minify-css-app/source/inline-css.php new file mode 100644 index 00000000..d978aafd --- /dev/null +++ b/middleman-core/fixtures/minify-css-app/source/inline-css.php @@ -0,0 +1,8 @@ + + + diff --git a/middleman-core/fixtures/minify-css-app/source/stylesheets/site.xcss.sass b/middleman-core/fixtures/minify-css-app/source/stylesheets/site.xcss.sass new file mode 100755 index 00000000..0cf9e7da --- /dev/null +++ b/middleman-core/fixtures/minify-css-app/source/stylesheets/site.xcss.sass @@ -0,0 +1,5 @@ +@import "compass/reset" + +@media handheld, only screen and (device-width: 768px) + body + display: block \ No newline at end of file diff --git a/middleman-core/fixtures/minify-js-app/source/inline-js.php b/middleman-core/fixtures/minify-js-app/source/inline-js.php new file mode 100755 index 00000000..4a7fd303 --- /dev/null +++ b/middleman-core/fixtures/minify-js-app/source/inline-js.php @@ -0,0 +1,22 @@ + + + + + diff --git a/middleman-core/fixtures/minify-js-app/source/javascripts/js_test.xjs b/middleman-core/fixtures/minify-js-app/source/javascripts/js_test.xjs new file mode 100644 index 00000000..f6a9406c --- /dev/null +++ b/middleman-core/fixtures/minify-js-app/source/javascripts/js_test.xjs @@ -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); +}; diff --git a/middleman-core/lib/middleman-core/core_extensions/request.rb b/middleman-core/lib/middleman-core/core_extensions/request.rb index 612001e1..cbef8d83 100644 --- a/middleman-core/lib/middleman-core/core_extensions/request.rb +++ b/middleman-core/lib/middleman-core/core_extensions/request.rb @@ -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 diff --git a/middleman-core/lib/middleman-more/extensions/minify_css.rb b/middleman-core/lib/middleman-more/extensions/minify_css.rb index 91cbf29a..61954a66 100644 --- a/middleman-core/lib/middleman-more/extensions/minify_css.rb +++ b/middleman-core/lib/middleman-more/extensions/minify_css.rb @@ -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 diff --git a/middleman-core/lib/middleman-more/extensions/minify_javascript.rb b/middleman-core/lib/middleman-more/extensions/minify_javascript.rb index d51d864f..df020765 100644 --- a/middleman-core/lib/middleman-more/extensions/minify_javascript.rb +++ b/middleman-core/lib/middleman-more/extensions/minify_javascript.rb @@ -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 = /(]*>\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(/(]*>\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 =~ /