mirror of
https://github.com/gosticks/wordpress-develop.git
synced 2025-10-16 12:05:38 +00:00
Script loader: enable resource preloading with rel='preload'.
Add a `wp_preload_resources` filter that developers can use to add resource preloading. Preloading helps the browser discover and prioritize important resources earlier during the page load. This ensures that they are available earlier and are less likely to block the page's render. Props nico23, swissspidy, igrigorik, westonruter, azaozz, furi3r, aristath, spacedmonkey, peterwilsoncc, mihai2u, gziolo. Fixes #42438. git-svn-id: https://develop.svn.wordpress.org/trunk@53846 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
6b3a677ce2
commit
beabd7a8fb
@ -324,6 +324,7 @@ add_filter( 'rest_authentication_errors', 'rest_cookie_check_errors', 100 );
|
||||
add_action( 'wp_head', '_wp_render_title_tag', 1 );
|
||||
add_action( 'wp_head', 'wp_enqueue_scripts', 1 );
|
||||
add_action( 'wp_head', 'wp_resource_hints', 2 );
|
||||
add_action( 'wp_head', 'wp_preload_resources', 1 );
|
||||
add_action( 'wp_head', 'feed_links', 2 );
|
||||
add_action( 'wp_head', 'feed_links_extra', 3 );
|
||||
add_action( 'wp_head', 'rsd_link' );
|
||||
|
||||
@ -3423,6 +3423,120 @@ 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. Preload directives should not be used for
|
||||
* non-render-blocking elements, as then they would compete with the
|
||||
* render-blocking ones, slowing down the 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_resources() {
|
||||
/**
|
||||
* Filters domains and URLs for resource preloads.
|
||||
*
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @param array $preload_resources {
|
||||
* Array of resources and their attributes, or URLs to print for resource preloads.
|
||||
*
|
||||
* @type array ...$0 {
|
||||
* Array of resource attributes.
|
||||
*
|
||||
* @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.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
$preload_resources = apply_filters( 'wp_preload_resources', array() );
|
||||
|
||||
if ( ! is_array( $preload_resources ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$unique_resources = array();
|
||||
|
||||
// Parse the complete resource list and extract unique resources.
|
||||
foreach ( $preload_resources as $resource ) {
|
||||
if ( ! is_array( $resource ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes = $resource;
|
||||
if ( isset( $resource['href'] ) ) {
|
||||
$href = $resource['href'];
|
||||
if ( isset( $unique_resources[ $href ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$unique_resources[ $href ] = $attributes;
|
||||
// Media can use imagesrcset and not href.
|
||||
} elseif ( ( 'image' === $resource['as'] ) &&
|
||||
( isset( $resource['imagesrcset'] ) || isset( $resource['imagesizes'] ) )
|
||||
) {
|
||||
if ( isset( $unique_resources[ $resource['imagesrcset'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$unique_resources[ $resource['imagesrcset'] ] = $attributes;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Build and output the HTML for each unique resource.
|
||||
foreach ( $unique_resources as $unique_resource ) {
|
||||
$html = '';
|
||||
|
||||
foreach ( $unique_resource as $resource_key => $resource_value ) {
|
||||
if ( ! is_scalar( $resource_value ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore non-supported attributes.
|
||||
$non_supported_attributes = array( 'as', 'crossorigin', 'href', 'imagesrcset', 'imagesizes', 'type', 'media' );
|
||||
if ( ! in_array( $resource_key, $non_supported_attributes, true ) && ! is_numeric( $resource_key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// imagesrcset only usable when preloading image, ignore otherwise.
|
||||
if ( ( 'imagesrcset' === $resource_key ) && ( ! isset( $unique_resource['as'] ) || ( 'image' !== $unique_resource['as'] ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// imagesizes only usable when preloading image and imagesrcset present, ignore otherwise.
|
||||
if ( ( 'imagesizes' === $resource_key ) &&
|
||||
( ! isset( $unique_resource['as'] ) || ( 'image' !== $unique_resource['as'] ) || ! isset( $unique_resource['imagesrcset'] ) )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resource_value = ( 'href' === $resource_key ) ? esc_url( $resource_value, array( 'http', 'https' ) ) : esc_attr( $resource_value );
|
||||
|
||||
if ( ! is_string( $resource_key ) ) {
|
||||
$html .= " $resource_value";
|
||||
} else {
|
||||
$html .= " $resource_key='$resource_value'";
|
||||
}
|
||||
}
|
||||
$html = trim( $html );
|
||||
|
||||
printf( "<link rel='preload' %s />\n", $html );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of unique hosts of all enqueued scripts and styles.
|
||||
*
|
||||
|
||||
253
tests/phpunit/tests/general/wpPreloadLinks.php
Normal file
253
tests/phpunit/tests/general/wpPreloadLinks.php
Normal file
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @group general
|
||||
* @group template
|
||||
* @ticket 42438
|
||||
* @covers ::wp_preload_resources
|
||||
*/
|
||||
class Tests_General_wpPreloadLinks extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* @dataProvider data_preload_resources
|
||||
*
|
||||
* @ticket 42438
|
||||
*/
|
||||
public function test_preload_resources( $expected, $preload_resources ) {
|
||||
$callback = function () use ( $preload_resources ) {
|
||||
return $preload_resources;
|
||||
};
|
||||
|
||||
add_filter( 'wp_preload_resources', $callback, 10 );
|
||||
$actual = get_echo( 'wp_preload_resources' );
|
||||
remove_filter( 'wp_preload_resources', $callback );
|
||||
|
||||
$this->assertSame( $expected, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test provider for all preload link possible combinations.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_preload_resources() {
|
||||
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',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user