middleman/middleman-core/lib/middleman-core/extensions/asset_hash.rb
Adam Heath 389e3f5a8c Add prefix option to asset_hash (#1949)
This allows manually changing the filename so that fiel header changes
can be reflected on the CDN. E.g. if you turn on crossOrigin serving
(CORS) the asset hash doesn't change, but the CDN cache needs to be
broken in order to pickup the new header.
2016-07-02 17:55:25 -07:00

107 lines
3.8 KiB
Ruby

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.'
option :exts, nil, 'List of extensions that get asset hashes appended to them.'
option :ignore, [], 'Regexes of filenames to skip adding asset hashes to'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites'
option :prefix, '', 'Prefix for hash'
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/]
# 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]
def rewrite_url(asset_path, dirpath, _request_path)
uri = ::Middleman::Util.parse_uri(asset_path)
relative_path = !uri.path.start_with?('/')
full_asset_path = if relative_path
dirpath.join(asset_path).to_s
else
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
# Update the main sitemap resource list
# @return Array<Middleman::Sitemap::Resource>
Contract ResourceList => ResourceList
def manipulate_resource_list(resources)
@rack_client ||= begin
rack_app = ::Middleman::Rack.new(app).to_app
::Rack::MockRequest.new(rack_app)
end
# 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.
resources.sort_by do |a|
if %w(.svg .svgz).include? a.ext
0
elsif %w(.js .css).include? a.ext
1
else
-1
end
end.each(&method(:manipulate_single_resource))
end
Contract IsA['Middleman::Sitemap::Resource'] => Maybe[IsA['Middleman::Sitemap::Resource']]
def manipulate_single_resource(resource)
return unless @exts.include?(resource.ext)
return if ignored_resource?(resource)
return if resource.ignored?
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]
end
resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{options.prefix}#{digest}#{ext}" }
resource
end
Contract IsA['Middleman::Sitemap::Resource'] => Bool
def ignored_resource?(resource)
@ignore.any? do |ignore|
Middleman::Util.path_match(ignore, resource.destination_path)
end
end
end