Permalink
Browse files

Merge pull request #31251 from rails/preload_link_tag

Add preload_link_tag helper.
  • Loading branch information...
guilleiguaran committed Nov 29, 2017
2 parents 02b5b1a + af10a39 commit 17661b8cb2591fda5370f7bf8df53ec78b0cdda4
View
@@ -1,3 +1,7 @@
* Register most popular audio/video/font mime types supported by modern browsers.
*Guillermo Iguaran*
* Fix optimized url helpers when using relative url root
Fixes #31220.
@@ -10,15 +10,33 @@
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "text/vcard", :vcf
Mime::Type.register "text/vtt", :vtt, %w(vtt)
Mime::Type.register "image/png", :png, [], %w(png)
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
Mime::Type.register "image/gif", :gif, [], %w(gif)
Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
Mime::Type.register "image/svg+xml", :svg
Mime::Type.register "image/webp", :webp, [], %w(webp)
Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
Mime::Type.register "audio/mpeg", :mpg, [], %w(mp1 mp2 mp3)
Mime::Type.register "audio/webm", :weba, [], %w(weba)
Mime::Type.register "audio/ogg", :ogg, [], %w(oga ogg spx opus)
Mime::Type.register "audio/aac", :acc, [], %w(aac)
Mime::Type.register "audio/mp4", :mp4, [], %w(m4a mpg4)
Mime::Type.register "audio/flac", :flac, [], %w(flac)
Mime::Type.register "video/webm", :webm, [], %w(webm)
Mime::Type.register "video/mp4", :mp4, [], %w(mp4 m4v)
Mime::Type.register "video/ogg", :ogv, [], %w(ogv)
Mime::Type.register "application/ogx", :ogx, [], %w(ogx)
Mime::Type.register "font/otf", :otf, [], %w(otf)
Mime::Type.register "font/ttf", :ttf, [], %w(ttf)
Mime::Type.register "font/woff", :woff, [], %w(woff)
Mime::Type.register "font/woff2", :woff2, [], %w(woff2)
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
Mime::Type.register "application/rss+xml", :rss
@@ -178,7 +178,7 @@ def test_send_file_headers_guess_type_from_extension
"image.jpg" => "image/jpeg",
"image.tif" => "image/tiff",
"image.gif" => "image/gif",
"movie.mpg" => "video/mpeg",
"movie.mp4" => "video/mp4",
"file.zip" => "application/zip",
"file.unk" => "application/octet-stream",
"zip" => "application/octet-stream"
@@ -30,28 +30,28 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse text with trailing star at the beginning" do
accept = "text/*, text/html, application/json, multipart/form-data"
expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:xml], Mime[:yaml], Mime[:json], Mime[:multipart_form]]
expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:vtt], Mime[:xml], Mime[:yaml], Mime[:json], Mime[:multipart_form]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
assert_equal expect.map(&:to_s), parsed.map(&:to_s)
end
test "parse text with trailing star in the end" do
accept = "text/html, application/json, multipart/form-data, text/*"
expect = [Mime[:html], Mime[:json], Mime[:multipart_form], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:xml], Mime[:yaml]]
expect = [Mime[:html], Mime[:json], Mime[:multipart_form], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:vtt], Mime[:xml], Mime[:yaml]]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
assert_equal expect.map(&:to_s), parsed.map(&:to_s)
end
test "parse text with trailing star" do
accept = "text/*"
expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:xml], Mime[:yaml], Mime[:json]]
expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:vtt], Mime[:xml], Mime[:yaml], Mime[:json]]
parsed = Mime::Type.parse(accept)
assert_equal expect.map(&:to_s).sort!, parsed.map(&:to_s).sort!
end
test "parse application with trailing star" do
accept = "application/*"
expect = [Mime[:html], Mime[:js], Mime[:xml], Mime[:rss], Mime[:atom], Mime[:yaml], Mime[:url_encoded_form], Mime[:json], Mime[:pdf], Mime[:zip], Mime[:gzip]]
expect = [Mime[:html], Mime[:js], Mime[:xml], Mime[:rss], Mime[:atom], Mime[:yaml], Mime[:url_encoded_form], Mime[:json], Mime[:pdf], Mime[:zip], Mime[:gzip], Mime[:ogx]]
parsed = Mime::Type.parse(accept)
assert_equal expect.map(&:to_s).sort!, parsed.map(&:to_s).sort!
end
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 17661b8

Please sign in to comment.