From 0d3030f28c47f86519ded80487f1ae33e1c80228 Mon Sep 17 00:00:00 2001 From: Thomas Reynolds Date: Mon, 25 Apr 2016 14:21:58 -0700 Subject: [PATCH] More WIP WIP --- middleman-core/features/asset_hash.feature | 17 ------- .../core_extensions/inline_url_rewriter.rb | 45 +++++++++++++++++++ .../middleman-core/extensions/asset_hash.rb | 40 +++++++---------- .../middleman-core/extensions/asset_host.rb | 25 ++++++----- .../extensions/relative_assets.rb | 6 +-- .../lib/middleman-core/sitemap/resource.rb | 28 ++++++++++-- .../lib/middleman-core/util/paths.rb | 4 +- .../lib/middleman-core/util/rack.rb | 1 + 8 files changed, 106 insertions(+), 60 deletions(-) diff --git a/middleman-core/features/asset_hash.feature b/middleman-core/features/asset_hash.feature index 19517e3b..6b92777c 100644 --- a/middleman-core/features/asset_hash.feature +++ b/middleman-core/features/asset_hash.feature @@ -188,23 +188,6 @@ Feature: Assets get file hashes appended to them and references to them are upda When I go to "/partials/" Then I should see 'href="../stylesheets/uses_partials-ec347271.css' - Scenario: The asset hash should change when a Rack-based filter changes - Given a fixture app "asset-hash-app" - And a file named "config.rb" with: - """ - activate :asset_hash - activate :relative_assets - activate :directory_indexes - require 'lib/middleware.rb' - use ::Middleware - """ - Given the Server is running at "asset-hash-app" - When I go to "/" - Then I should see 'href="stylesheets/site-5ad7def0.css' - When I go to "stylesheets/site-5ad7def0.css" - Then I should see 'background-image: url("../images/100px-5fd6fb90.jpg")' - Then I should see 'Added by Rack filter' - Scenario: Hashed-asset files are not produced for ignored paths Given a fixture app "asset-hash-app" And a file named "config.rb" with: diff --git a/middleman-core/lib/middleman-core/core_extensions/inline_url_rewriter.rb b/middleman-core/lib/middleman-core/core_extensions/inline_url_rewriter.rb index 43354e97..db84095c 100644 --- a/middleman-core/lib/middleman-core/core_extensions/inline_url_rewriter.rb +++ b/middleman-core/lib/middleman-core/core_extensions/inline_url_rewriter.rb @@ -5,6 +5,51 @@ require 'middleman-core/util' require 'middleman-core/contracts' module Middleman + class InlineURLRewriter + include Contracts + + attr_reader :filter_name + attr_reader :after_filter + + def initialize(filter_name, app, resource, options={}) + @filter_name = filter_name + @app = app + @resource = resource + @options = options + + @after_filter = @options.fetch(:after_filter, nil) + end + + Contract String => String + def execute_filter(body) + path = "/#{@resource.destination_path}" + dirpath = ::Pathname.new(File.dirname(path)) + + ::Middleman::Util.instrument 'inline_url_rewriter', path: path do + ::Middleman::Util.rewrite_paths(body, path, @options.fetch(:url_extensions), @app) do |asset_path| + uri = ::Middleman::Util.parse_uri(asset_path) + + relative_path = uri.host.nil? + full_asset_path = if relative_path + dirpath.join(asset_path).to_s + else + asset_path + end + + exts = @options.fetch(:url_extensions) + next unless exts.include?(::File.extname(asset_path)) + + next if @options.fetch(:ignore).any? { |r| ::Middleman::Util.should_ignore?(r, full_asset_path) } + + result = @options.fetch(:proc).call(asset_path, dirpath, path) + asset_path = result if result + + asset_path + end + end + end + end + module CoreExtensions class InlineURLRewriter < ::Middleman::Extension include Contracts diff --git a/middleman-core/lib/middleman-core/extensions/asset_hash.rb b/middleman-core/lib/middleman-core/extensions/asset_hash.rb index b0b955f9..fadf3e20 100644 --- a/middleman-core/lib/middleman-core/extensions/asset_hash.rb +++ b/middleman-core/lib/middleman-core/extensions/asset_hash.rb @@ -1,5 +1,4 @@ require 'middleman-core/util' -require 'middleman-core/rack' class Middleman::Extensions::AssetHash < ::Middleman::Extension option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for hashable assets.' @@ -10,9 +9,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension def initialize(app, options_hash={}, &block) super - require 'addressable/uri' require 'digest/sha1' - require 'rack/mock' # Allow specifying regexes to ignore, plus always ignore apple touch icons @ignore = Array(options.ignore) + [/^apple-touch-icon/] @@ -20,14 +17,6 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension # Exclude .ico from the default list because browsers expect it # to be named "favicon.ico" @exts = options.exts || (app.config[:asset_extensions] - %w(.ico)) - - app.rewrite_inline_urls id: :asset_hash, - url_extensions: @exts.sort.reverse, - source_extensions: options.sources, - ignore: @ignore, - rewrite_ignore: options.rewrite_ignore, - proc: method(:rewrite_url), - after: :asset_host end Contract String, Or[String, Pathname], Any => Maybe[String] @@ -41,11 +30,11 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension asset_path end + return unless asset_page = app.sitemap.find_resource_by_destination_path(full_asset_path) || app.sitemap.find_resource_by_path(full_asset_path) replacement_path = "/#{asset_page.destination_path}" replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path - replacement_path end @@ -53,9 +42,18 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension # @return Array Contract ResourceList => ResourceList def manipulate_resource_list(resources) - @rack_client ||= begin - rack_app = ::Middleman::Rack.new(app).to_app - ::Rack::MockRequest.new(rack_app) + resources.each do |r| + next unless r.destination_path.end_with?('/', *options.sources) + next if Array(options.rewrite_ignore || []).any? do |i| + ::Middleman::Util.path_match(i, "/#{r.destination_path}") + end + + r.filters << ::Middleman::InlineURLRewriter.new(:asset_hash, + app, + r, + url_extensions: @exts, + ignore: options.ignore, + proc: method(:rewrite_url)) end # Process resources in order: binary images and fonts, then SVG, then JS/CSS. @@ -81,15 +79,9 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension digest = if resource.binary? ::Digest::SHA1.file(resource.source_file).hexdigest[0..7] else - # Render through the Rack interface so middleware and mounted apps get a shot - response = @rack_client.get( - ::URI.escape(resource.destination_path), - 'bypass_inline_url_rewriter_asset_hash' => 'true' - ) - - raise "#{resource.path} should be in the sitemap!" unless response.status == 200 - - ::Digest::SHA1.hexdigest(response.body)[0..7] + # Render without asset hash + body = resource.render { |f| !f.respond_to?(:filter_name) || f.filter_name != :asset_hash } + ::Digest::SHA1.hexdigest(body)[0..7] end resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" } diff --git a/middleman-core/lib/middleman-core/extensions/asset_host.rb b/middleman-core/lib/middleman-core/extensions/asset_host.rb index 7cb83d07..b076299d 100644 --- a/middleman-core/lib/middleman-core/extensions/asset_host.rb +++ b/middleman-core/lib/middleman-core/extensions/asset_host.rb @@ -1,5 +1,3 @@ -require 'addressable/uri' - class Middleman::Extensions::AssetHost < ::Middleman::Extension option :host, nil, 'The asset host to use or a Proc to determine asset host', required: true option :exts, nil, 'List of extensions that get cache busters strings appended to them.' @@ -7,15 +5,22 @@ class Middleman::Extensions::AssetHost < ::Middleman::Extension option :ignore, [], 'Regexes of filenames to skip adding query strings to' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for host rewrites' - def initialize(app, options_hash={}, &block) - super + Contract ResourceList => ResourceList + def manipulate_resource_list(resources) + resources.each do |r| + next unless r.destination_path.end_with?('/', *options.sources) + next if Array(options.rewrite_ignore || []).any? do |i| + ::Middleman::Util.path_match(i, "/#{r.destination_path}") + end - app.rewrite_inline_urls id: :asset_host, - url_extensions: options.exts || app.config[:asset_extensions], - source_extensions: options.sources, - ignore: options.ignore, - rewrite_ignore: options.rewrite_ignore, - proc: method(:rewrite_url) + r.filters << ::Middleman::InlineURLRewriter.new(:asset_host, + app, + r, + after_filter: :asset_hash, + url_extensions: options.exts || app.config[:asset_extensions], + ignore: options.ignore, + proc: method(:rewrite_url)) + end end Contract String, Or[String, Pathname], Any => String diff --git a/middleman-core/lib/middleman-core/extensions/relative_assets.rb b/middleman-core/lib/middleman-core/extensions/relative_assets.rb index 853a7005..432072c3 100644 --- a/middleman-core/lib/middleman-core/extensions/relative_assets.rb +++ b/middleman-core/lib/middleman-core/extensions/relative_assets.rb @@ -11,9 +11,7 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension def initialize(app, options_hash={}, &block) super - if options[:helpers_only] - return - end + return if options[:helpers_only] app.rewrite_inline_urls id: :relative_assets, url_extensions: options.exts || app.config[:asset_extensions], @@ -53,7 +51,7 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension end def asset_path(kind, source, options={}) - super(kind, source, app.extensions[:relative_assets].mark_as_relative(super, options, current_resource)) + super(kind, source, app.extensions[:relative_assets].mark_as_relative(super, options, current_resource)) end end diff --git a/middleman-core/lib/middleman-core/sitemap/resource.rb b/middleman-core/lib/middleman-core/sitemap/resource.rb index baeb0f2b..e8b273dc 100644 --- a/middleman-core/lib/middleman-core/sitemap/resource.rb +++ b/middleman-core/lib/middleman-core/sitemap/resource.rb @@ -140,12 +140,32 @@ module Middleman # Render this resource # @return [String] - Contract Hash, Hash => String - def render(opts={}, locs={}) + # Contract Maybe[Hash], Maybe[Hash], Maybe[Proc] => String + def render(opts={}, locs={}, &block) body = render_without_filters(opts, locs) - @filters.reduce(body) do |output, filter| - if filter.respond_to?(:execute_filter) + return body if @filters.empty? + + sortable_filters = @filters.select { |f| f.respond_to?(:filter_name) }.sort do |a, b| + if b.after_filter == a.filter_name + 1 + else + -1 + end + end.reverse + + n = 0 + sorted_filters = @filters.sort_by do |m| + n += 1 + idx = sortable_filters.index(m) + + [idx.nil? ? 0 : idx, n] + end + + sorted_filters.reduce(body) do |output, filter| + if block_given? && !yield(filter) + output + elsif filter.respond_to?(:execute_filter) filter.execute_filter(output) elsif filter.respond_to?(:call) filter.call(output) diff --git a/middleman-core/lib/middleman-core/util/paths.rb b/middleman-core/lib/middleman-core/util/paths.rb index 26f29442..7aa223cc 100644 --- a/middleman-core/lib/middleman-core/util/paths.rb +++ b/middleman-core/lib/middleman-core/util/paths.rb @@ -30,8 +30,10 @@ module Middleman # Normalize a path to not include a leading slash # @param [String] path # @return [String] - Contract String => String + Contract Any => String def normalize_path(path) + return path unless path.is_a?(String) + # The tr call works around a bug in Ruby's Unicode handling ::URI.decode(path).sub(%r{^/}, '').tr('', '') end diff --git a/middleman-core/lib/middleman-core/util/rack.rb b/middleman-core/lib/middleman-core/util/rack.rb index 2459052b..ccce8c1b 100644 --- a/middleman-core/lib/middleman-core/util/rack.rb +++ b/middleman-core/lib/middleman-core/util/rack.rb @@ -22,6 +22,7 @@ module Middleman Contract String, String, ArrayOf[String], IsA['::Middleman::Application'], Proc => String def rewrite_paths(body, path, exts, app, &_block) + exts = exts.sort_by(&:length).reverse matcher = /([\'\"\(,]\s*|# sourceMappingURL=)([^\s\'\"\)\(>]+(#{::Regexp.union(exts)}))/ url_fn_prefix = 'url('