Switch AssetHost to be a new-style extension. Setup extensions app scope earlier. Add supports_multiple_instances flag for extensions.
This commit is contained in:
parent
c2e2839b79
commit
b12a7bff3d
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -8,6 +8,7 @@ pkg
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
docs
|
docs
|
||||||
.rbenv-*
|
.rbenv-*
|
||||||
|
.ruby-version
|
||||||
.*.swp
|
.*.swp
|
||||||
build
|
build
|
||||||
doc
|
doc
|
||||||
|
@ -15,4 +16,4 @@ doc
|
||||||
tmp
|
tmp
|
||||||
Makefile
|
Makefile
|
||||||
.mm-pid-*
|
.mm-pid-*
|
||||||
.idea
|
.idea
|
||||||
|
|
|
@ -37,6 +37,7 @@ module Middleman
|
||||||
class << self
|
class << self
|
||||||
# @private
|
# @private
|
||||||
def registered(app)
|
def registered(app)
|
||||||
|
app.define_hook :initialized
|
||||||
app.define_hook :after_configuration
|
app.define_hook :after_configuration
|
||||||
app.define_hook :before_configuration
|
app.define_hook :before_configuration
|
||||||
app.define_hook :build_config
|
app.define_hook :build_config
|
||||||
|
@ -68,19 +69,15 @@ module Middleman
|
||||||
# @param [Hash] options Per-extension options hash
|
# @param [Hash] options Per-extension options hash
|
||||||
# @return [void]
|
# @return [void]
|
||||||
def register(extension, options={}, &block)
|
def register(extension, options={}, &block)
|
||||||
if extension.instance_of? Module
|
extend extension
|
||||||
extend extension
|
if extension.respond_to?(:registered)
|
||||||
if extension.respond_to?(:registered)
|
if extension.method(:registered).arity === 1
|
||||||
if extension.method(:registered).arity === 1
|
extension.registered(self, &block)
|
||||||
extension.registered(self, &block)
|
else
|
||||||
else
|
extension.registered(self, options, &block)
|
||||||
extension.registered(self, options, &block)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
extension
|
|
||||||
elsif extension.instance_of?(Class) && extension.ancestors.include?(::Middleman::Extension)
|
|
||||||
extension.new(self, options, &block)
|
|
||||||
end
|
end
|
||||||
|
extension
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -105,7 +102,22 @@ module Middleman
|
||||||
logger.error "== Unknown Extension: #{ext}"
|
logger.error "== Unknown Extension: #{ext}"
|
||||||
else
|
else
|
||||||
logger.debug "== Activating: #{ext}"
|
logger.debug "== Activating: #{ext}"
|
||||||
extensions[ext] = self.class.register(ext_module, options, &block)
|
|
||||||
|
if ext_module.instance_of? Module
|
||||||
|
extensions[ext] = self.class.register(ext_module, options, &block)
|
||||||
|
elsif ext_module.instance_of?(Class) && ext_module.ancestors.include?(::Middleman::Extension)
|
||||||
|
if ext_module.supports_multiple_instances?
|
||||||
|
extensions[ext] ||= {}
|
||||||
|
key = "instance_#{extensions[ext].keys.length}"
|
||||||
|
extensions[ext][key] = ext_module.new(self.class, options, &block)
|
||||||
|
else
|
||||||
|
if extensions[ext]
|
||||||
|
logger.error "== #{ext} already activated. Overwriting."
|
||||||
|
end
|
||||||
|
|
||||||
|
extensions[ext] = ext_module.new(self.class, options, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -141,6 +153,8 @@ module Middleman
|
||||||
instance_eval File.read(local_config), local_config, 1
|
instance_eval File.read(local_config), local_config, 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
run_hook :initialized
|
||||||
|
|
||||||
run_hook :build_config if build?
|
run_hook :build_config if build?
|
||||||
run_hook :development_config if development?
|
run_hook :development_config if development?
|
||||||
|
|
||||||
|
@ -148,7 +162,13 @@ module Middleman
|
||||||
|
|
||||||
logger.debug "Loaded extensions:"
|
logger.debug "Loaded extensions:"
|
||||||
self.extensions.each do |ext,_|
|
self.extensions.each do |ext,_|
|
||||||
logger.debug "== Extension: #{ext}"
|
if ext.is_a?(Hash)
|
||||||
|
ext.each do |k,_|
|
||||||
|
logger.debug "== Extension: #{k}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
logger.debug "== Extension: #{ext}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require "active_support/core_ext/class/attribute"
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
|
|
||||||
module Extensions
|
module Extensions
|
||||||
|
@ -102,6 +104,8 @@ module Middleman
|
||||||
end
|
end
|
||||||
|
|
||||||
class Extension
|
class Extension
|
||||||
|
class_attribute :supports_multiple_instances, :instance_reader => false, :instance_writer => false
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def config
|
def config
|
||||||
@_config ||= ::Middleman::Configuration::ConfigurationManager.new
|
@_config ||= ::Middleman::Configuration::ConfigurationManager.new
|
||||||
|
@ -125,10 +129,11 @@ module Middleman
|
||||||
yield @options if block_given?
|
yield @options if block_given?
|
||||||
|
|
||||||
ext = self
|
ext = self
|
||||||
klass.after_configuration do
|
klass.initialized do
|
||||||
ext.app = self
|
ext.app = self
|
||||||
ext.after_configuration
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
klass.after_configuration(&method(:after_configuration))
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_configuration
|
def after_configuration
|
||||||
|
|
|
@ -1,7 +1,56 @@
|
||||||
Feature: Alternate between multiple asset hosts
|
Feature: Alternate between multiple asset hosts
|
||||||
In order to speed up page loading
|
In order to speed up page loading
|
||||||
|
|
||||||
Scenario: Rendering html with the feature enabled
|
Scenario: Set single host globally
|
||||||
Given the Server is running at "asset-host-app"
|
Given a fixture app "asset-host-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
activate :asset_host
|
||||||
|
set :asset_host, "http://assets1.example.com"
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
When I go to "/asset_host.html"
|
When I go to "/asset_host.html"
|
||||||
|
Then I should see "http://assets1"
|
||||||
|
When I go to "/stylesheets/asset_host.css"
|
||||||
|
Then I should see "http://assets1"
|
||||||
|
|
||||||
|
Scenario: Set proc host globally
|
||||||
|
Given a fixture app "asset-host-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
activate :asset_host
|
||||||
|
set :asset_host do |asset|
|
||||||
|
"http://assets%d.example.com" % (asset.hash % 4)
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
|
When I go to "/asset_host.html"
|
||||||
|
Then I should see "http://assets"
|
||||||
|
When I go to "/stylesheets/asset_host.css"
|
||||||
|
Then I should see "http://assets"
|
||||||
|
|
||||||
|
Scenario: Set single host with inline-option
|
||||||
|
Given a fixture app "asset-host-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
activate :asset_host, :host => "http://assets1.example.com"
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
|
When I go to "/asset_host.html"
|
||||||
|
Then I should see "http://assets1"
|
||||||
|
When I go to "/stylesheets/asset_host.css"
|
||||||
|
Then I should see "http://assets1"
|
||||||
|
|
||||||
|
Scenario: Set proc host with inline-option
|
||||||
|
Given a fixture app "asset-host-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
activate :asset_host, :host => Proc.new { |asset|
|
||||||
|
"http://assets%d.example.com" % (asset.hash % 4)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
|
When I go to "/asset_host.html"
|
||||||
|
Then I should see "http://assets"
|
||||||
|
When I go to "/stylesheets/asset_host.css"
|
||||||
Then I should see "http://assets"
|
Then I should see "http://assets"
|
|
@ -1,7 +0,0 @@
|
||||||
Feature: Alternate between multiple asset hosts
|
|
||||||
In order to speed up page loading
|
|
||||||
|
|
||||||
Scenario: Rendering css with the feature enabled
|
|
||||||
Given the Server is running at "asset-host-app"
|
|
||||||
When I go to "/stylesheets/asset_host.css"
|
|
||||||
Then I should see "http://assets"
|
|
|
@ -1,6 +0,0 @@
|
||||||
set :layout, false
|
|
||||||
|
|
||||||
activate :asset_host
|
|
||||||
set :asset_host do |asset|
|
|
||||||
"http://assets%d.example.com" % (asset.hash % 4)
|
|
||||||
end
|
|
|
@ -83,7 +83,7 @@ module Middleman
|
||||||
# to avoid browser caches failing to update to your new content.
|
# to avoid browser caches failing to update to your new content.
|
||||||
Middleman::Extensions.register(:asset_hash) do
|
Middleman::Extensions.register(:asset_hash) do
|
||||||
require "middleman-more/extensions/asset_hash"
|
require "middleman-more/extensions/asset_hash"
|
||||||
Middleman::Extensions::AssetHash::Extension
|
Middleman::Extensions::AssetHash
|
||||||
end
|
end
|
||||||
|
|
||||||
# AssetHost allows you to setup multiple domains to host your static
|
# AssetHost allows you to setup multiple domains to host your static
|
||||||
|
|
|
@ -52,8 +52,14 @@ module Middleman
|
||||||
# No line-comments in test mode (changing paths mess with sha1)
|
# No line-comments in test mode (changing paths mess with sha1)
|
||||||
compass_config.line_comments = false if ENV["TEST"]
|
compass_config.line_comments = false if ENV["TEST"]
|
||||||
|
|
||||||
if config.defines_setting?(:asset_host) && config[:asset_host].is_a?(Proc)
|
if extensions[:asset_host] && asset_host = extensions[:asset_host].host
|
||||||
compass_config.asset_host(&config[:asset_host])
|
if asset_host.is_a?(Proc)
|
||||||
|
compass_config.asset_host(&asset_host)
|
||||||
|
else
|
||||||
|
compass_config.asset_host do |asset|
|
||||||
|
asset_host
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,119 +1,105 @@
|
||||||
module Middleman
|
module Middleman
|
||||||
module Extensions
|
module Extensions
|
||||||
module AssetHash
|
class AssetHash < ::Middleman::Extension
|
||||||
class Extension < ::Middleman::Extension
|
option :exts, %w(.jpg .jpeg .png .gif .js .css .otf .woff .eot .ttf .svg), "List of extensions that get asset hashes appended to them."
|
||||||
option :exts, %w(.jpg .jpeg .png .gif .js .css .otf .woff .eot .ttf .svg), "List of extensions that get asset hashes appended to them."
|
option :ignore, [], "Regexes of filenames to skip adding asset hashes to"
|
||||||
option :ignore, [], "Regexes of filenames to skip adding asset hashes to"
|
|
||||||
|
|
||||||
def initialize(app, options_hash={})
|
def initialize(app, options_hash={})
|
||||||
super
|
super
|
||||||
|
|
||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
require 'rack/test'
|
require 'rack/test'
|
||||||
require 'uri'
|
require 'uri'
|
||||||
end
|
|
||||||
|
|
||||||
def after_configuration
|
|
||||||
# Allow specifying regexes to ignore, plus always ignore apple touch icons
|
|
||||||
ignore = Array(options.ignore) + [/^apple-touch-icon/]
|
|
||||||
|
|
||||||
app.sitemap.register_resource_list_manipulator(
|
|
||||||
:asset_hash,
|
|
||||||
AssetHashManager.new(app, options.exts, ignore)
|
|
||||||
)
|
|
||||||
|
|
||||||
app.use Middleware, :exts => options.exts, :middleman_app => app, :ignore => ignore
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Central class for managing asset_hash extension
|
def after_configuration
|
||||||
class AssetHashManager
|
# Allow specifying regexes to ignore, plus always ignore apple touch icons
|
||||||
def initialize(app, exts, ignore)
|
@ignore = Array(options.ignore) + [/^apple-touch-icon/]
|
||||||
@app = app
|
|
||||||
@exts = exts
|
|
||||||
@ignore = ignore
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update the main sitemap resource list
|
app.sitemap.register_resource_list_manipulator(:asset_hash, self)
|
||||||
# @return [void]
|
|
||||||
def manipulate_resource_list(resources)
|
|
||||||
# Process resources in order: binary images and fonts, then SVG, then JS/CSS.
|
|
||||||
# This is so by the time we get around to the text files (which may reference
|
|
||||||
# images and fonts) the static assets' hashes are already calculated.
|
|
||||||
rack_client = ::Rack::Test::Session.new(@app.class.to_rack_app)
|
|
||||||
resources.sort_by do |a|
|
|
||||||
if %w(.svg).include? a.ext
|
|
||||||
0
|
|
||||||
elsif %w(.js .css).include? a.ext
|
|
||||||
1
|
|
||||||
else
|
|
||||||
-1
|
|
||||||
end
|
|
||||||
end.each do |resource|
|
|
||||||
next unless @exts.include? resource.ext
|
|
||||||
next if @ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) }
|
|
||||||
|
|
||||||
# Render through the Rack interface so middleware and mounted apps get a shot
|
app.use Middleware, :exts => options.exts, :middleman_app => app, :ignore => @ignore
|
||||||
response = rack_client.get(URI.escape(resource.destination_path), {}, { "bypass_asset_hash" => "true" })
|
end
|
||||||
raise "#{resource.path} should be in the sitemap!" unless response.status == 200
|
|
||||||
|
|
||||||
digest = Digest::SHA1.hexdigest(response.body)[0..7]
|
# Update the main sitemap resource list
|
||||||
|
# @return [void]
|
||||||
resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
|
def manipulate_resource_list(resources)
|
||||||
|
# Process resources in order: binary images and fonts, then SVG, then JS/CSS.
|
||||||
|
# This is so by the time we get around to the text files (which may reference
|
||||||
|
# images and fonts) the static assets' hashes are already calculated.
|
||||||
|
rack_client = ::Rack::Test::Session.new(@app.class.to_rack_app)
|
||||||
|
resources.sort_by do |a|
|
||||||
|
if %w(.svg).include? a.ext
|
||||||
|
0
|
||||||
|
elsif %w(.js .css).include? a.ext
|
||||||
|
1
|
||||||
|
else
|
||||||
|
-1
|
||||||
end
|
end
|
||||||
|
end.each do |resource|
|
||||||
|
next unless options.exts.include? resource.ext
|
||||||
|
next if @ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) }
|
||||||
|
|
||||||
|
# Render through the Rack interface so middleware and mounted apps get a shot
|
||||||
|
response = rack_client.get(URI.escape(resource.destination_path), {}, { "bypass_asset_hash" => "true" })
|
||||||
|
raise "#{resource.path} should be in the sitemap!" unless response.status == 200
|
||||||
|
|
||||||
|
digest = Digest::SHA1.hexdigest(response.body)[0..7]
|
||||||
|
|
||||||
|
resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# The asset hash middleware is responsible for rewriting references to
|
# The asset hash middleware is responsible for rewriting references to
|
||||||
# assets to include their new, hashed name.
|
# assets to include their new, hashed name.
|
||||||
class Middleware
|
class Middleware
|
||||||
def initialize(app, options={})
|
def initialize(app, options={})
|
||||||
@rack_app = app
|
@rack_app = app
|
||||||
@exts = options[:exts]
|
@exts = options[:exts]
|
||||||
@ignore = options[:ignore]
|
@ignore = options[:ignore]
|
||||||
@exts_regex_text = @exts.map {|e| Regexp.escape(e) }.join('|')
|
@exts_regex_text = @exts.map {|e| Regexp.escape(e) }.join('|')
|
||||||
@middleman_app = options[:middleman_app]
|
@middleman_app = options[:middleman_app]
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
status, headers, response = @rack_app.call(env)
|
status, headers, response = @rack_app.call(env)
|
||||||
|
|
||||||
# We don't want to use this middleware when rendering files to figure out their hash!
|
# We don't want to use this middleware when rendering files to figure out their hash!
|
||||||
return [status, headers, response] if env["bypass_asset_hash"] == 'true'
|
return [status, headers, response] if env["bypass_asset_hash"] == 'true'
|
||||||
|
|
||||||
path = @middleman_app.full_path(env["PATH_INFO"])
|
path = @middleman_app.full_path(env["PATH_INFO"])
|
||||||
dirpath = Pathname.new(File.dirname(path))
|
dirpath = Pathname.new(File.dirname(path))
|
||||||
|
|
||||||
if path =~ /(^\/$)|(\.(htm|html|php|css|js)$)/
|
if path =~ /(^\/$)|(\.(htm|html|php|css|js)$)/
|
||||||
body = ::Middleman::Util.extract_response_text(response)
|
body = ::Middleman::Util.extract_response_text(response)
|
||||||
|
|
||||||
if body
|
if body
|
||||||
# TODO: This regex will change some paths in plan HTML (not in a tag) - is that OK?
|
# TODO: This regex will change some paths in plan HTML (not in a tag) - is that OK?
|
||||||
body.gsub! /([=\'\"\(]\s*)([^\s\'\"\)]+(#{@exts_regex_text}))/ do |match|
|
body.gsub!(/([=\'\"\(]\s*)([^\s\'\"\)]+(#{@exts_regex_text}))/) do |match|
|
||||||
opening_character = $1
|
opening_character = $1
|
||||||
asset_path = $2
|
asset_path = $2
|
||||||
|
|
||||||
relative_path = Pathname.new(asset_path).relative?
|
relative_path = Pathname.new(asset_path).relative?
|
||||||
|
|
||||||
asset_path = dirpath.join(asset_path).to_s if relative_path
|
asset_path = dirpath.join(asset_path).to_s if relative_path
|
||||||
|
|
||||||
if @ignore.any? { |r| asset_path.match(r) }
|
if @ignore.any? { |r| asset_path.match(r) }
|
||||||
match
|
match
|
||||||
elsif asset_page = @middleman_app.sitemap.find_resource_by_path(asset_path)
|
elsif asset_page = @middleman_app.sitemap.find_resource_by_path(asset_path)
|
||||||
replacement_path = "/#{asset_page.destination_path}"
|
replacement_path = "/#{asset_page.destination_path}"
|
||||||
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
|
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
|
||||||
|
|
||||||
"#{opening_character}#{replacement_path}"
|
"#{opening_character}#{replacement_path}"
|
||||||
else
|
else
|
||||||
match
|
match
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
status, headers, response = Rack::Response.new(body, status, headers).finish
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
status, headers, response = Rack::Response.new(body, status, headers).finish
|
||||||
end
|
end
|
||||||
[status, headers, response]
|
|
||||||
end
|
end
|
||||||
|
[status, headers, response]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,24 +3,19 @@ module Middleman
|
||||||
module Extensions
|
module Extensions
|
||||||
|
|
||||||
# Asset Host module
|
# Asset Host module
|
||||||
module AssetHost
|
class AssetHost < ::Middleman::Extension
|
||||||
|
option :host, nil, 'The asset host to use, or false for no asset host, or a Proc to determine asset host'
|
||||||
|
|
||||||
# Setup extension
|
def initialize(app, options_hash={}, &block)
|
||||||
class << self
|
super
|
||||||
|
|
||||||
# Once registered
|
# Backwards compatible API
|
||||||
def registered(app, options={})
|
app.config.define_setting :asset_host, nil, 'The asset host to use, or false for no asset host, or a Proc to determine asset host'
|
||||||
app.config.define_setting :asset_host, false, 'The asset host to use, or false for no asset host, or a Proc to determine asset host'
|
app.send :include, InstanceMethods
|
||||||
|
end
|
||||||
|
|
||||||
if options[:host]
|
def host
|
||||||
config[:asset_host] = options[:host]
|
app.config[:asset_host] || options[:host]
|
||||||
end
|
|
||||||
|
|
||||||
# Include methods
|
|
||||||
app.send :include, InstanceMethods
|
|
||||||
end
|
|
||||||
|
|
||||||
alias :included :registered
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Asset Host Instance Methods
|
# Asset Host Instance Methods
|
||||||
|
@ -32,13 +27,15 @@ module Middleman
|
||||||
# @param [String] prefix
|
# @param [String] prefix
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def asset_url(path, prefix="")
|
def asset_url(path, prefix="")
|
||||||
original_output = super
|
controller = extensions[:asset_host]
|
||||||
return original_output unless config[:asset_host]
|
|
||||||
|
|
||||||
asset_prefix = if config[:asset_host].is_a?(Proc)
|
original_output = super
|
||||||
config[:asset_host].call(original_output)
|
return original_output unless controller.host
|
||||||
elsif config[:asset_host].is_a?(String)
|
|
||||||
config[:asset_host]
|
asset_prefix = if controller.host.is_a?(Proc)
|
||||||
|
controller.host.call(original_output)
|
||||||
|
elsif controller.host.is_a?(String)
|
||||||
|
controller.host
|
||||||
end
|
end
|
||||||
|
|
||||||
File.join(asset_prefix, original_output)
|
File.join(asset_prefix, original_output)
|
||||||
|
|
Loading…
Reference in a new issue