Permalink
Browse files

Add preload_link_tag helper.

This helper creates a link tag with preload keyword that allows to
browser to initiate early fetch of resources. Additionally this send
Early Hints if supported.

See 59a02fb
for more details about Early Hints.

Preload spec: https://w3c.github.io/preload/
  • Loading branch information...
guilleiguaran committed Nov 28, 2017
1 parent 055493c commit eb90b8bc86e758045a707cae43d11dab538ca6db
View
@@ -1,3 +1,11 @@
* Add `preload_link_tag` helper
This helper that allows to the browser to initiate early fetch of resources
(different to the specified in javascript_include_tag and stylesheet_link_tag).
Additionally, this sends Early Hints if supported by browser.
*Guillermo Iguaran*
## Rails 5.2.0.beta2 (November 28, 2017) ##
* No changes.
@@ -2,6 +2,8 @@
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/hash/keys"
require "active_support/core_ext/object/inclusion"
require "active_support/core_ext/object/try"
require "action_view/helpers/asset_url_helper"
require "action_view/helpers/tag_helper"
@@ -221,6 +223,67 @@ def favicon_link_tag(source = "favicon.ico", options = {})
}.merge!(options.symbolize_keys))
end
# Returns a link tag that browsers can use to preload the +source+.
# The +source+ can be the path of an resource managed by asset pipeline,
# a full path or an URI.
#
# ==== Options
#
# * <tt>:type</tt> - Override the auto-generated mime type, defaults to the mime type for +source+ extension.
# * <tt>:as</tt> - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type.
# * <tt>:crossorigin</tt> - Specify the crossorigin attribute, required to load cross-origin resources.
# * <tt>:nopush</tt> - Specify if the use of server push is not desired for the resource. Defaults to +false+.
#
# ==== Examples
#
# preload_link_tag("custom_theme.css")
# # => <link rel="preload" href="/assets/custom_theme.css" as="style" type="text/css" />
#
# preload_link_tag("/videos/video.webm")
# # => <link rel="preload" href="/videos/video.mp4" as="video" type="video/webm" />
#
# preload_link_tag(post_path(format: :json), as: "fetch")
# # => <link rel="preload" href="/posts.json" as="fetch" type="application/json" />
#
# preload_link_tag("worker.js", as: "worker")
# # => <link rel="preload" href="/assets/worker.js" as="worker" type="text/javascript" />
#
# preload_link_tag("//example.com/font.woff2")
# # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>
#
# preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials")
# # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />
#
# preload_link_tag("/media/audio.ogg", nopush: true)
# # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />
#
def preload_link_tag(source, options = {})
href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline))
extname = File.extname(source).downcase.delete(".")
mime_type = options.delete(:type) || Template::Types[extname].try(:to_s)
as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
crossorigin = options.delete(:crossorigin)
crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
nopush = options.delete(:nopush) || false
link_tag = tag.link({
rel: "preload",
href: href,
as: as_type,
type: mime_type,
crossorigin: crossorigin
}.merge!(options.symbolize_keys))
early_hints_link = "<#{href}>; rel=preload; as=#{as_type}"
early_hints_link += "; type=#{mime_type}" if mime_type
early_hints_link += "; crossorigin=#{crossorigin}" if crossorigin
early_hints_link += "; nopush" if nopush
request.send_early_hints("Link" => early_hints_link) if respond_to?(:request) && request
link_tag
end
# Returns an HTML image tag for the +source+. The +source+ can be a full
# path, a file or an Active Storage attachment.
#
@@ -417,6 +480,18 @@ def check_for_image_tag_errors(options)
raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
end
end
def resolve_link_as(extname, mime_type)
if extname == "js"
"script"
elsif extname == "css"
"style"
elsif extname == "vtt"
"track"
elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
type
end
end
end
end
end
@@ -214,6 +214,17 @@ def url_for(*args)
%(favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png') => %(<link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />)
}
PreloadLinkToTag = {
%(preload_link_tag '/styles/custom_theme.css') => %(<link rel="preload" href="/styles/custom_theme.css" as="style" type="text/css" />),
%(preload_link_tag '/videos/video.webm') => %(<link rel="preload" href="/videos/video.webm" as="video" type="video/webm" />),
%(preload_link_tag '/posts.json', as: 'fetch') => %(<link rel="preload" href="/posts.json" as="fetch" type="application/json" />),
%(preload_link_tag '/users', as: 'fetch', type: 'application/json') => %(<link rel="preload" href="/users" as="fetch" type="application/json" />),
%(preload_link_tag '//example.com/map?callback=initMap', as: 'fetch', type: 'application/javascript') => %(<link rel="preload" href="//example.com/map?callback=initMap" as="fetch" type="application/javascript" />),
%(preload_link_tag '//example.com/font.woff2') => %(<link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>),
%(preload_link_tag '//example.com/font.woff2', crossorigin: 'use-credentials') => %(<link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />),
%(preload_link_tag '/media/audio.ogg', nopush: true) => %(<link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />)
}
VideoPathToTag = {
%(video_path("xml")) => %(/videos/xml),
%(video_path("xml.ogg")) => %(/videos/xml.ogg),
@@ -535,6 +546,10 @@ def test_favicon_link_tag
FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
def test_preload_link_tag
PreloadLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
def test_video_path
VideoPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end

0 comments on commit eb90b8b

Please sign in to comment.