-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#42438 wp_preload_links implementation #2505
Changes from all commits
7e2f394
de8e6ea
1f84c8f
d15930a
8cabd1d
3bf691f
551f0c5
8761430
57d1232
b95d43d
bc134c6
fd96232
e1614b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3422,6 +3422,121 @@ function wp_resource_hints() { | |
} | ||
} | ||
|
||
/** | ||
* Prints resource preloads directives to browsers. | ||
* | ||
* Gives directive to browsers to preload specific resources that website will | ||
* need very soon, this ensures that they are available earlier and are less | ||
* likely to block the page's render. | ||
* | ||
* These performance improving indicators work by using `<link rel"preload">`. | ||
* | ||
* @link https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload | ||
* @link https://web.dev/preload-responsive-images/ | ||
* | ||
* @since 6.1.0 | ||
*/ | ||
function wp_preload_links() { | ||
/** | ||
* Filters domains and URLs for resource preloads. | ||
* | ||
* @since 6.1.0 | ||
* | ||
* @param array $urls { | ||
* Array of resources and their attributes, or URLs to print for resource preloads. | ||
* | ||
* @type array ...$0 { | ||
* Array of resource attributes. | ||
manuelRod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* @type string $href URL to include in resource preloads. Required. | ||
* @type string $as How the browser should treat the resource | ||
* (`script`, `style`, `image`, `document`, etc). | ||
* @type string $crossorigin Indicates the CORS policy of the specified resource. | ||
* @type string $type Type of the resource (`text/html`, `text/css`, etc). | ||
* @type string $media Accepts media types or media queries. Allows responsive preloading. | ||
* @type string $imagesizes Responsive source size to the source Set. | ||
* @type string $imagesrcset Responsive image sources to the source set. | ||
* } | ||
* } | ||
*/ | ||
$urls = apply_filters( 'wp_preload_links', array() ); | ||
|
||
if ( ! is_array( $urls ) ) { | ||
return; | ||
} | ||
|
||
$unique_urls = array(); | ||
foreach ( $urls as $url ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed to |
||
if ( ! is_array( $url ) ) { | ||
continue; | ||
} | ||
|
||
$atts = $url; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. renamed |
||
if ( isset( $url['href'] ) ) { | ||
$url = $url['href']; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't think resetting/reusing vars is a good idea, ever. I know, it comes from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed here |
||
if ( isset( $unique_urls[ $url ] ) ) { | ||
continue; | ||
} | ||
$unique_urls[ $url ] = $atts; | ||
// Media can use imagesrcset and not href. | ||
} elseif ( ! isset( $url['href'] ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The first condition seems redundant? We already know it is an array, so if an element is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed redundant |
||
&& ( 'image' === $url['as'] ) | ||
&& ( isset( $url['imagesrcset'] ) || isset( $url['imagesizes'] ) ) | ||
) { | ||
if ( isset( $unique_urls[ $url['imagesrcset'] ] ) ) { | ||
adamsilverstein marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue; | ||
} | ||
$unique_urls[ $url['imagesrcset'] ] = $atts; | ||
} else { | ||
continue; | ||
} | ||
|
||
foreach ( $unique_urls as $unique_atts ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uh, what's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed this to |
||
$html = ''; | ||
|
||
foreach ( $unique_atts as $attr => $value ) { | ||
/** | ||
* Ignore not supported attributes. | ||
*/ | ||
if ( ! is_scalar( $value ) | ||
manuelRod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|| ( | ||
! in_array( $attr, array( 'as', 'crossorigin', 'href', 'imagesrcset', 'imagesizes', 'type', 'media' ), true ) | ||
&& ! is_numeric( $attr ) | ||
) | ||
Comment on lines
+3502
to
+3505
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this can be split into several conditionals to make it easier to read? Seems it will be a lot better/simpler. |
||
) { | ||
|
||
continue; | ||
} | ||
|
||
// imagesrcset only usable when preloading image, ignore otherwise. | ||
if ( ( 'imagesrcset' === $attr ) && ( ! isset( $unique_atts['as'] ) || ( 'image' !== $unique_atts['as'] ) ) ) { | ||
continue; | ||
} | ||
|
||
// imagesizes only usable when preloading image and imagesrcset present, ignore otherwise. | ||
if ( ( 'imagesizes' === $attr ) | ||
&& ( ! isset( $unique_atts['as'] ) || ( 'image' !== $unique_atts['as'] ) || ! isset( $unique_atts['imagesrcset'] ) ) | ||
) { | ||
continue; | ||
} | ||
|
||
$value = ( 'href' === $attr ) ? esc_url( $value, array( 'http', 'https' ) ) : esc_attr( $value ); | ||
|
||
if ( ! is_string( $attr ) ) { | ||
$html .= " $value"; | ||
manuelRod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} else { | ||
$html .= " $attr='$value'"; | ||
} | ||
} | ||
} | ||
|
||
$html = trim( $html ); | ||
|
||
printf( "<link rel='preload' %s />\n", $html ); | ||
} | ||
|
||
} | ||
|
||
/** | ||
* Retrieves a list of unique hosts of all enqueued scripts and styles. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
<?php | ||
|
||
/** | ||
* @group general | ||
* @group template | ||
* @ticket 42438 | ||
* @covers ::wp_preload_links | ||
*/ | ||
class Tests_General_wpPreloadLinks extends WP_UnitTestCase { | ||
manuelRod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* @dataProvider provider_preload_links | ||
*/ | ||
public function test_preload_links( $expected, $urls ) { | ||
$callback = function () use ( $urls ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, seems There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. renamed |
||
return $urls; | ||
}; | ||
|
||
add_filter( 'wp_preload_links', $callback, 10 ); | ||
$actual = get_echo( 'wp_preload_links' ); | ||
remove_filter( 'wp_preload_links', $callback ); | ||
|
||
$this->assertSame( $expected, $actual ); | ||
} | ||
|
||
/** | ||
* Test provider for all preload link possible combinations. | ||
* | ||
* @return array[] | ||
*/ | ||
public function provider_preload_links() { | ||
return array( | ||
'basic_preload' => array( | ||
'expected' => "<link rel='preload' href='https://example.com/style.css' as='style' />\n", | ||
'urls' => array( | ||
array( | ||
'href' => 'https://example.com/style.css', | ||
'as' => 'style', | ||
), | ||
), | ||
), | ||
'multiple_links' => array( | ||
'expected' => "<link rel='preload' href='https://example.com/style.css' as='style' />\n" . | ||
"<link rel='preload' href='https://example.com/main.js' as='script' />\n", | ||
'urls' => array( | ||
array( | ||
'href' => 'https://example.com/style.css', | ||
'as' => 'style', | ||
), | ||
array( | ||
'href' => 'https://example.com/main.js', | ||
'as' => 'script', | ||
), | ||
), | ||
), | ||
'MIME_types' => array( | ||
'expected' => "<link rel='preload' href='https://example.com/style.css' as='style' />\n" . | ||
"<link rel='preload' href='https://example.com/video.mp4' as='video' type='video/mp4' />\n" . | ||
"<link rel='preload' href='https://example.com/main.js' as='script' />\n", | ||
'urls' => array( | ||
array( | ||
//Should ignore not valid attributes | ||
'not' => 'valid', | ||
'href' => 'https://example.com/style.css', | ||
'as' => 'style', | ||
), | ||
array( | ||
'href' => 'https://example.com/video.mp4', | ||
'as' => 'video', | ||
'type' => 'video/mp4', | ||
), | ||
array( | ||
'href' => 'https://example.com/main.js', | ||
'as' => 'script', | ||
), | ||
), | ||
), | ||
'CORS' => array( | ||
'expected' => "<link rel='preload' href='https://example.com/style.css' as='style' crossorigin='anonymous' />\n" . | ||
"<link rel='preload' href='https://example.com/video.mp4' as='video' type='video/mp4' />\n" . | ||
"<link rel='preload' href='https://example.com/main.js' as='script' />\n" . | ||
"<link rel='preload' href='https://example.com/font.woff2' as='font' type='font/woff2' crossorigin />\n", | ||
'urls' => array( | ||
array( | ||
'href' => 'https://example.com/style.css', | ||
'as' => 'style', | ||
'crossorigin' => 'anonymous', | ||
), | ||
array( | ||
'href' => 'https://example.com/video.mp4', | ||
'as' => 'video', | ||
'type' => 'video/mp4', | ||
), | ||
array( | ||
'href' => 'https://example.com/main.js', | ||
'as' => 'script', | ||
), | ||
array( | ||
//Should ignore not valid attributes. | ||
'ignore' => 'ignore', | ||
'href' => 'https://example.com/font.woff2', | ||
'as' => 'font', | ||
'type' => 'font/woff2', | ||
'crossorigin', | ||
), | ||
), | ||
), | ||
'media' => array( | ||
'expected' => "<link rel='preload' href='https://example.com/style.css' as='style' crossorigin='anonymous' />\n" . | ||
"<link rel='preload' href='https://example.com/video.mp4' as='video' type='video/mp4' />\n" . | ||
"<link rel='preload' href='https://example.com/main.js' as='script' />\n" . | ||
"<link rel='preload' href='https://example.com/font.woff2' as='font' type='font/woff2' crossorigin />\n" . | ||
"<link rel='preload' href='https://example.com/image-narrow.png' as='image' media='(max-width: 600px)' />\n" . | ||
"<link rel='preload' href='https://example.com/image-wide.png' as='image' media='(min-width: 601px)' />\n", | ||
'urls' => array( | ||
array( | ||
'href' => 'https://example.com/style.css', | ||
'as' => 'style', | ||
'crossorigin' => 'anonymous', | ||
), | ||
array( | ||
'href' => 'https://example.com/video.mp4', | ||
'as' => 'video', | ||
'type' => 'video/mp4', | ||
), | ||
// Duplicated href should be ignored. | ||
array( | ||
'href' => 'https://example.com/video.mp4', | ||
'as' => 'video', | ||
'type' => 'video/mp4', | ||
), | ||
array( | ||
'href' => 'https://example.com/main.js', | ||
'as' => 'script', | ||
), | ||
array( | ||
'href' => 'https://example.com/font.woff2', | ||
'as' => 'font', | ||
'type' => 'font/woff2', | ||
'crossorigin', | ||
), | ||
array( | ||
'href' => 'https://example.com/image-narrow.png', | ||
'as' => 'image', | ||
'media' => '(max-width: 600px)', | ||
), | ||
array( | ||
'href' => 'https://example.com/image-wide.png', | ||
'as' => 'image', | ||
'media' => '(min-width: 601px)', | ||
), | ||
|
||
), | ||
), | ||
'media_extra_attributes' => array( | ||
'expected' => "<link rel='preload' href='https://example.com/style.css' as='style' crossorigin='anonymous' />\n" . | ||
"<link rel='preload' href='https://example.com/video.mp4' as='video' type='video/mp4' />\n" . | ||
"<link rel='preload' href='https://example.com/main.js' as='script' />\n" . | ||
"<link rel='preload' href='https://example.com/font.woff2' as='font' type='font/woff2' crossorigin />\n" . | ||
"<link rel='preload' href='https://example.com/image-640.png' as='image' imagesrcset='640.png 640w, 800.png 800w, 1024.png 1024w' imagesizes='100vw' />\n" . | ||
"<link rel='preload' as='image' imagesrcset='640.png 640w, 800.png 800w, 1024.png 1024w' imagesizes='100vw' />\n" . | ||
"<link rel='preload' href='https://example.com/image-wide.png' as='image' media='(min-width: 601px)' />\n" . | ||
"<link rel='preload' href='https://example.com/image-800.png' as='image' imagesrcset='640.png 640w, 800.png 800w, 1024.png 1024w' />\n", | ||
'urls' => array( | ||
array( | ||
'href' => 'https://example.com/style.css', | ||
'as' => 'style', | ||
'crossorigin' => 'anonymous', | ||
), | ||
array( | ||
'href' => 'https://example.com/video.mp4', | ||
'as' => 'video', | ||
'type' => 'video/mp4', | ||
), | ||
array( | ||
'href' => 'https://example.com/main.js', | ||
'as' => 'script', | ||
), | ||
array( | ||
'href' => 'https://example.com/font.woff2', | ||
'as' => 'font', | ||
'type' => 'font/woff2', | ||
'crossorigin', | ||
), | ||
// imagesrcset only possible when using image, ignore. | ||
array( | ||
'href' => 'https://example.com/font.woff2', | ||
'as' => 'font', | ||
'type' => 'font/woff2', | ||
'imagesrcset' => '640.png 640w, 800.png 800w, 1024.png 1024w', | ||
), | ||
// imagesizes only possible when using image, ignore. | ||
array( | ||
'href' => 'https://example.com/font.woff2', | ||
'as' => 'font', | ||
'type' => 'font/woff2', | ||
'imagesizes' => '100vw', | ||
), | ||
// Duplicated href should be ignored. | ||
array( | ||
'href' => 'https://example.com/font.woff2', | ||
'as' => 'font', | ||
'type' => 'font/woff2', | ||
'crossorigin', | ||
), | ||
array( | ||
'href' => 'https://example.com/image-640.png', | ||
'as' => 'image', | ||
'imagesrcset' => '640.png 640w, 800.png 800w, 1024.png 1024w', | ||
'imagesizes' => '100vw', | ||
), | ||
// Omit href so that unsupporting browsers won't request a useless image. | ||
array( | ||
'as' => 'image', | ||
'imagesrcset' => '640.png 640w, 800.png 800w, 1024.png 1024w', | ||
'imagesizes' => '100vw', | ||
), | ||
// Duplicated imagesrcset should be ignored. | ||
array( | ||
'as' => 'image', | ||
'imagesrcset' => '640.png 640w, 800.png 800w, 1024.png 1024w', | ||
'imagesizes' => '100vw', | ||
), | ||
array( | ||
'href' => 'https://example.com/image-wide.png', | ||
'as' => 'image', | ||
'media' => '(min-width: 601px)', | ||
), | ||
// No href but not imagesrcset, should be ignored | ||
array( | ||
'as' => 'image', | ||
'media' => '(min-width: 601px)', | ||
), | ||
// imagesizes is optional | ||
array( | ||
'href' => 'https://example.com/image-800.png', | ||
'as' => 'image', | ||
'imagesrcset' => '640.png 640w, 800.png 800w, 1024.png 1024w', | ||
), | ||
// imagesizes should be ignored since imagesrcset not present. | ||
array( | ||
'href' => 'https://example.com/image-640.png', | ||
'as' => 'image', | ||
'imagesizes' => '100vw', | ||
), | ||
), | ||
), | ||
); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
urls seems mis-named? maybe
link_resources
orlink_attributes
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamsilverstein I used $url the same way wp_resource_hints uses it. Since both share a similar structure/naming, I didn't want to introduce many changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏼 I see, poor naming choice but ok!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. However imho in both places the name is no good, unfortunately. On top of that further down the var
$url
is reset/reused. Not sure that's a good choice, perhaps better to have different names?IMHO, best to make the names here descriptive, self-documenting and also fix the confusing naming in wp_resource_hints. Thinking both of @adamsilverstein's suggestions are better, maybe
link_attributes
makes a bit more sense.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we are renaming the function slightly based on your last comment to
wp_preload_resources
, thinking the variable name would be$preload_resources
. How does that sound?