diff --git a/middleman-core/lib/middleman-core/extensions/minify_css.rb b/middleman-core/lib/middleman-core/extensions/minify_css.rb index 067b25bf..1c932b97 100644 --- a/middleman-core/lib/middleman-core/extensions/minify_css.rb +++ b/middleman-core/lib/middleman-core/extensions/minify_css.rb @@ -1,5 +1,6 @@ require 'memoist' require 'middleman-core/contracts' +require 'rack/mime' # Minify CSS Extension class Middleman::Extensions::MinifyCss < ::Middleman::Extension @@ -12,14 +13,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension 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 ready - # Setup Rack middleware to minify CSS - app.use Rack, compressor: options[:compressor], - ignore: Array(options[:ignore]) + [/\.min\./], - inline: options[:inline], - content_types: options[:content_types], - inline_content_types: options[:inline_content_types] - end + INLINE_CSS_REGEX = /(]*>\s*(?:\/\*\*\/)?\s*<\/style>)/m class SassCompressor def self.compress(style, options={}) @@ -29,97 +23,61 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension end end - # Rack middleware to look for CSS and compress it - class Rack - extend Memoist - include Contracts - INLINE_CSS_REGEX = /(]*>\s*(?:\/\*\*\/)?\s*<\/style>)/m + def initialize(app, options_hash={}, &block) + super - # Init - # @param [Class] app - # @param [Hash] options - Contract RespondTo[:call], { - ignore: ArrayOf[PATH_MATCHER], - inline: Bool, - compressor: Or[Proc, RespondTo[:to_proc], RespondTo[:compress]] - } => Any - def initialize(app, options={}) - @app = app - @ignore = options.fetch(:ignore) - @inline = options.fetch(:inline) - - @compressor = options.fetch(:compressor) - @compressor = @compressor.to_proc if @compressor.respond_to? :to_proc - @compressor = @compressor.call if @compressor.is_a? Proc - @content_types = options[:content_types] - @inline_content_types = options[:inline_content_types] - end - - # Rack interface - # @param [Rack::Environmemt] env - # @return [Array] - def call(env) - status, headers, response = @app.call(env) - - 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] - end - - [status, headers, response] - end - - private - - # Whether the path should be ignored - # @param [String] path - # @return [Boolean] - def ignore?(path) - @ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) } - end - memoize :ignore? - - # 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 - memoize :minifiable? - - # 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 - memoize :minifiable_inline? - - # Minify the content - # @param [String] content - # @return [String] - def minify(content) - @compressor.compress(content) - end - memoize :minify - - # 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 - memoize :minify_inline + @ignore = Array(options[:ignore]) + [/\.min\./] + @compressor = options[:compressor] + @compressor = @compressor.to_proc if @compressor.respond_to? :to_proc + @compressor = @compressor.call if @compressor.is_a? Proc end + + Contract ResourceList => ResourceList + def manipulate_resource_list(resources) + resources.each do |r| + type = r.content_type.try(:slice, /^[^;]*/) + if options[:inline] && minifiable_inline?(type) + r.filters << method(:minify_inline) + elsif minifiable?(type) && !ignore?(r.destination_path) + r.filters << method(:minify) + end + end + end + + # Whether the path should be ignored + Contract String => Bool + def ignore?(path) + @ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) } + end + memoize :ignore? + + # Whether this type of content can be minified + Contract Maybe[String] => Bool + def minifiable?(content_type) + options[:content_types].include?(content_type) + end + memoize :minifiable? + + # Whether this type of content contains inline content that can be minified + Contract Maybe[String] => Bool + def minifiable_inline?(content_type) + options[:inline_content_types].include?(content_type) + end + memoize :minifiable_inline? + + # Minify the content + Contract String => String + def minify(content) + @compressor.compress(content) + end + memoize :minify + + # Detect and minify inline content + Contract String => String + def minify_inline(content) + content.gsub(INLINE_CSS_REGEX) do + $1 + minify($2) + $3 + end + end + memoize :minify_inline end diff --git a/middleman-core/lib/middleman-core/pipeline.rb b/middleman-core/lib/middleman-core/pipeline.rb deleted file mode 100644 index 49ee478b..00000000 --- a/middleman-core/lib/middleman-core/pipeline.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'forwardable' - -module Middleman - class Pipeline - extend Forwardable - attr_reader :filters - attr_reader :resource - - def_delegators :filters, :<<, :push, :unshift, :insert, :shift, :pop, :first, :clear - - def initialize(resource) - @resource = resource - @filters = [] - end - - def render(*args) - process resource.render *args - end - - def process(body) - filters.inject(body){ |body, app| app.call(body) } - end - end -end diff --git a/middleman-core/lib/middleman-core/rack.rb b/middleman-core/lib/middleman-core/rack.rb index 41e8f63a..293a2e43 100644 --- a/middleman-core/lib/middleman-core/rack.rb +++ b/middleman-core/lib/middleman-core/rack.rb @@ -109,7 +109,7 @@ module Middleman begin # Write out the contents of the page - res.write resource.pipeline.render({}, rack: { request: req }) + res.write resource.render({}, rack: { request: req }) # Valid content is a 200 status res.status = 200 diff --git a/middleman-core/lib/middleman-core/sitemap/resource.rb b/middleman-core/lib/middleman-core/sitemap/resource.rb index d5e4931a..baeb0f2b 100644 --- a/middleman-core/lib/middleman-core/sitemap/resource.rb +++ b/middleman-core/lib/middleman-core/sitemap/resource.rb @@ -3,7 +3,6 @@ require 'middleman-core/sitemap/extensions/traversal' require 'middleman-core/file_renderer' require 'middleman-core/template_renderer' require 'middleman-core/contracts' -require 'middleman-core/pipeline' module Middleman # Sitemap namespace @@ -40,8 +39,7 @@ module Middleman attr_reader :metadata attr_accessor :ignored - - attr_reader :pipeline + attr_accessor :filters # Initialize resource with parent store and URL # @param [Middleman::Sitemap::Store] store @@ -53,7 +51,7 @@ module Middleman @app = @store.app @path = path @ignored = false - @pipeline = Pipeline.new(self) + @filters = [] source = Pathname(source) if source && source.is_a?(String) @@ -144,6 +142,23 @@ module Middleman # @return [String] Contract Hash, Hash => String def render(opts={}, locs={}) + body = render_without_filters(opts, locs) + + @filters.reduce(body) do |output, filter| + if filter.respond_to?(:execute_filter) + filter.execute_filter(output) + elsif filter.respond_to?(:call) + filter.call(output) + else + output + end + end + end + + # Render this resource without content filters + # @return [String] + Contract Hash, Hash => String + def render_without_filters(opts={}, locs={}) return ::Middleman::FileRenderer.new(@app, file_descriptor[:full_path].to_s).template_data_for_file unless template? md = metadata @@ -155,7 +170,7 @@ module Middleman opts[:layout] = false if !opts.key?(:layout) && !@app.config.extensions_with_layout.include?(ext) renderer = ::Middleman::TemplateRenderer.new(@app, file_descriptor[:full_path].to_s) - renderer.render(locs, opts) + renderer.render(locs, opts).to_str end # A path without the directory index - so foo/index.html becomes