Skip to content

Commit c987f34

Browse files
committed
Script Loader: Introduce fetchpriority for Scripts and Script Modules.
* Allow scripts and script modules to be registered with a `fetchpriority` of `auto` (default), `high`, `low`: * When registering a script, add a `fetchpriority` arg to go alongside the `strategy` arg which was added for loading scripts with the `defer` and `async` loading strategies. See #12009. * For script modules, introduce an `$args` array parameter with a `fetchpriority` key to the `wp_register_script_module()`, and `wp_enqueue_script_module()` functions (and their respective underlying `WP_Script_Modules::register()` and `WP_Script_Modules::enqueue()` methods). This `$args` parameter corresponds with the same parameter used when registering non-module scripts. * Also for script modules, introduce `WP_Script_Modules::set_fetchpriority()` to override the `fetchpriority` for what was previously registered. * Emit a `_doing_it_wrong()` warning when an invalid `fetchpriority` value is used, and when `fetchpriority` is added to a script alias. * Include `fetchpriority` as an attribute on printed `SCRIPT` tags as well as on preload `LINK` tags for static script module dependencies. * Use a `fetchpriority` of `low` by default for: * Script modules used with the Interactivity API. For overriding this default in blocks, see [WordPress/gutenberg#71366 Gutenberg#71366]. * The `comment-reply` script. * Improve type checks and type hints. Developed in [#8815 GitHub PR], with [WordPress/gutenberg#70173 companion for Gutenberg]. Props westonruter, jonsurrell, swissspidy, luisherranz, kraftbj, audrasjb, dennysdionigi. Fixes #61734. git-svn-id: https://develop.svn.wordpress.org/trunk@60704 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 9b6c234 commit c987f34

File tree

8 files changed

+914
-157
lines changed

8 files changed

+914
-157
lines changed

src/wp-includes/blocks.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,21 @@ function register_block_script_module_id( $metadata, $field_name, $index = 0 ) {
175175
$block_version = isset( $metadata['version'] ) ? $metadata['version'] : false;
176176
$module_version = isset( $module_asset['version'] ) ? $module_asset['version'] : $block_version;
177177

178+
// Blocks using the Interactivity API are server-side rendered, so they are by design not in the critical rendering path and should be deprioritized.
179+
$args = array();
180+
if (
181+
( isset( $metadata['supports']['interactivity'] ) && true === $metadata['supports']['interactivity'] ) ||
182+
( isset( $metadata['supports']['interactivity']['interactive'] ) && true === $metadata['supports']['interactivity']['interactive'] )
183+
) {
184+
$args['fetchpriority'] = 'low';
185+
}
186+
178187
wp_register_script_module(
179188
$module_id,
180189
$module_uri,
181190
$module_dependencies,
182-
$module_version
191+
$module_version,
192+
$args
183193
);
184194

185195
return $module_id;

src/wp-includes/class-wp-script-modules.php

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class WP_Script_Modules {
4646
* identifier has already been registered.
4747
*
4848
* @since 6.5.0
49+
* @since 6.9.0 Added the $args parameter.
4950
*
5051
* @param string $id The identifier of the script module. Should be unique. It will be used in the
5152
* final import map.
@@ -71,13 +72,18 @@ class WP_Script_Modules {
7172
* It is added to the URL as a query string for cache busting purposes. If $version
7273
* is set to false, the version number is the currently installed WordPress version.
7374
* If $version is set to null, no version is added.
75+
* @param array $args {
76+
* Optional. An array of additional args. Default empty array.
77+
*
78+
* @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
79+
* }
7480
*/
75-
public function register( string $id, string $src, array $deps = array(), $version = false ) {
81+
public function register( string $id, string $src, array $deps = array(), $version = false, array $args = array() ) {
7682
if ( ! isset( $this->registered[ $id ] ) ) {
7783
$dependencies = array();
7884
foreach ( $deps as $dependency ) {
7985
if ( is_array( $dependency ) ) {
80-
if ( ! isset( $dependency['id'] ) ) {
86+
if ( ! isset( $dependency['id'] ) || ! is_string( $dependency['id'] ) ) {
8187
_doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' );
8288
continue;
8389
}
@@ -95,13 +101,76 @@ public function register( string $id, string $src, array $deps = array(), $versi
95101
}
96102
}
97103

104+
$fetchpriority = 'auto';
105+
if ( isset( $args['fetchpriority'] ) ) {
106+
if ( $this->is_valid_fetchpriority( $args['fetchpriority'] ) ) {
107+
$fetchpriority = $args['fetchpriority'];
108+
} else {
109+
_doing_it_wrong(
110+
__METHOD__,
111+
sprintf(
112+
/* translators: 1: $fetchpriority, 2: $id */
113+
__( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ),
114+
is_string( $args['fetchpriority'] ) ? $args['fetchpriority'] : gettype( $args['fetchpriority'] ),
115+
$id
116+
),
117+
'6.9.0'
118+
);
119+
}
120+
}
121+
98122
$this->registered[ $id ] = array(
99-
'src' => $src,
100-
'version' => $version,
101-
'enqueue' => isset( $this->enqueued_before_registered[ $id ] ),
102-
'dependencies' => $dependencies,
123+
'src' => $src,
124+
'version' => $version,
125+
'enqueue' => isset( $this->enqueued_before_registered[ $id ] ),
126+
'dependencies' => $dependencies,
127+
'fetchpriority' => $fetchpriority,
128+
);
129+
}
130+
}
131+
132+
/**
133+
* Checks if the provided fetchpriority is valid.
134+
*
135+
* @since 6.9.0
136+
*
137+
* @param string|mixed $priority Fetch priority.
138+
* @return bool Whether valid fetchpriority.
139+
*/
140+
private function is_valid_fetchpriority( $priority ): bool {
141+
return in_array( $priority, array( 'auto', 'low', 'high' ), true );
142+
}
143+
144+
/**
145+
* Sets the fetch priority for a script module.
146+
*
147+
* @since 6.9.0
148+
*
149+
* @param string $id Script module identifier.
150+
* @param 'auto'|'low'|'high' $priority Fetch priority for the script module.
151+
* @return bool Whether setting the fetchpriority was successful.
152+
*/
153+
public function set_fetchpriority( string $id, string $priority ): bool {
154+
if ( ! isset( $this->registered[ $id ] ) ) {
155+
return false;
156+
}
157+
158+
if ( '' === $priority ) {
159+
$priority = 'auto';
160+
}
161+
162+
if ( ! $this->is_valid_fetchpriority( $priority ) ) {
163+
_doing_it_wrong(
164+
__METHOD__,
165+
/* translators: %s: Invalid fetchpriority. */
166+
sprintf( __( 'Invalid fetchpriority: %s' ), $priority ),
167+
'6.9.0'
103168
);
169+
return false;
104170
}
171+
172+
$this->registered[ $id ]['fetchpriority'] = $priority;
173+
return true;
105174
}
106175

107176
/**
@@ -111,6 +180,7 @@ public function register( string $id, string $src, array $deps = array(), $versi
111180
* will be registered.
112181
*
113182
* @since 6.5.0
183+
* @since 6.9.0 Added the $args parameter.
114184
*
115185
* @param string $id The identifier of the script module. Should be unique. It will be used in the
116186
* final import map.
@@ -136,12 +206,17 @@ public function register( string $id, string $src, array $deps = array(), $versi
136206
* It is added to the URL as a query string for cache busting purposes. If $version
137207
* is set to false, the version number is the currently installed WordPress version.
138208
* If $version is set to null, no version is added.
209+
* @param array $args {
210+
* Optional. An array of additional args. Default empty array.
211+
*
212+
* @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
213+
* }
139214
*/
140-
public function enqueue( string $id, string $src = '', array $deps = array(), $version = false ) {
215+
public function enqueue( string $id, string $src = '', array $deps = array(), $version = false, array $args = array() ) {
141216
if ( isset( $this->registered[ $id ] ) ) {
142217
$this->registered[ $id ]['enqueue'] = true;
143218
} elseif ( $src ) {
144-
$this->register( $id, $src, $deps, $version );
219+
$this->register( $id, $src, $deps, $version, $args );
145220
$this->registered[ $id ]['enqueue'] = true;
146221
} else {
147222
$this->enqueued_before_registered[ $id ] = true;
@@ -208,13 +283,15 @@ public function add_hooks() {
208283
*/
209284
public function print_enqueued_script_modules() {
210285
foreach ( $this->get_marked_for_enqueue() as $id => $script_module ) {
211-
wp_print_script_tag(
212-
array(
213-
'type' => 'module',
214-
'src' => $this->get_src( $id ),
215-
'id' => $id . '-js-module',
216-
)
286+
$args = array(
287+
'type' => 'module',
288+
'src' => $this->get_src( $id ),
289+
'id' => $id . '-js-module',
217290
);
291+
if ( 'auto' !== $script_module['fetchpriority'] ) {
292+
$args['fetchpriority'] = $script_module['fetchpriority'];
293+
}
294+
wp_print_script_tag( $args );
218295
}
219296
}
220297

@@ -231,9 +308,10 @@ public function print_script_module_preloads() {
231308
// Don't preload if it's marked for enqueue.
232309
if ( true !== $script_module['enqueue'] ) {
233310
echo sprintf(
234-
'<link rel="modulepreload" href="%s" id="%s">',
311+
'<link rel="modulepreload" href="%s" id="%s"%s>',
235312
esc_url( $this->get_src( $id ) ),
236-
esc_attr( $id . '-js-modulepreload' )
313+
esc_attr( $id . '-js-modulepreload' ),
314+
'auto' !== $script_module['fetchpriority'] ? sprintf( ' fetchpriority="%s"', esc_attr( $script_module['fetchpriority'] ) ) : ''
237315
);
238316
}
239317
}
@@ -278,7 +356,7 @@ private function get_import_map(): array {
278356
*
279357
* @since 6.5.0
280358
*
281-
* @return array[] Script modules marked for enqueue, keyed by script module identifier.
359+
* @return array<string, array> Script modules marked for enqueue, keyed by script module identifier.
282360
*/
283361
private function get_marked_for_enqueue(): array {
284362
$enqueued = array();

src/wp-includes/class-wp-scripts.php

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,9 @@ public function do_item( $handle, $group = false ) {
425425
if ( $intended_strategy ) {
426426
$attr['data-wp-strategy'] = $intended_strategy;
427427
}
428+
if ( isset( $obj->extra['fetchpriority'] ) && 'auto' !== $obj->extra['fetchpriority'] && $this->is_valid_fetchpriority( $obj->extra['fetchpriority'] ) ) {
429+
$attr['fetchpriority'] = $obj->extra['fetchpriority'];
430+
}
428431
$tag = $translations . $ie_conditional_prefix . $before_script;
429432
$tag .= wp_get_script_tag( $attr );
430433
$tag .= $after_script . $ie_conditional_suffix;
@@ -831,6 +834,35 @@ public function add_data( $handle, $key, $value ) {
831834
);
832835
return false;
833836
}
837+
} elseif ( 'fetchpriority' === $key ) {
838+
if ( empty( $value ) ) {
839+
$value = 'auto';
840+
}
841+
if ( ! $this->is_valid_fetchpriority( $value ) ) {
842+
_doing_it_wrong(
843+
__METHOD__,
844+
sprintf(
845+
/* translators: 1: $fetchpriority, 2: $handle */
846+
__( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ),
847+
is_string( $value ) ? $value : gettype( $value ),
848+
$handle
849+
),
850+
'6.9.0'
851+
);
852+
return false;
853+
} elseif ( ! $this->registered[ $handle ]->src ) {
854+
_doing_it_wrong(
855+
__METHOD__,
856+
sprintf(
857+
/* translators: 1: $fetchpriority, 2: $handle */
858+
__( 'Cannot supply a fetchpriority `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
859+
is_string( $value ) ? $value : gettype( $value ),
860+
$handle
861+
),
862+
'6.9.0'
863+
);
864+
return false;
865+
}
834866
}
835867
return parent::add_data( $handle, $key, $value );
836868
}
@@ -869,17 +901,29 @@ private function get_dependents( $handle ) {
869901
*
870902
* @since 6.3.0
871903
*
872-
* @param string $strategy The strategy to check.
904+
* @param string|mixed $strategy The strategy to check.
873905
* @return bool True if $strategy is one of the delayed strategies, otherwise false.
874906
*/
875-
private function is_delayed_strategy( $strategy ) {
907+
private function is_delayed_strategy( $strategy ): bool {
876908
return in_array(
877909
$strategy,
878910
$this->delayed_strategies,
879911
true
880912
);
881913
}
882914

915+
/**
916+
* Checks if the provided fetchpriority is valid.
917+
*
918+
* @since 6.9.0
919+
*
920+
* @param string|mixed $priority Fetch priority.
921+
* @return bool Whether valid fetchpriority.
922+
*/
923+
private function is_valid_fetchpriority( $priority ): bool {
924+
return in_array( $priority, array( 'auto', 'low', 'high' ), true );
925+
}
926+
883927
/**
884928
* Gets the best eligible loading strategy for a script.
885929
*

src/wp-includes/functions.wp-scripts.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) {
158158
* @since 2.1.0
159159
* @since 4.3.0 A return value was added.
160160
* @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
161+
* @since 6.9.0 The $fetchpriority parameter of type string was added to the $args parameter of type array.
161162
*
162163
* @param string $handle Name of the script. Should be unique.
163164
* @param string|false $src Full URL of the script, or path of the script relative to the WordPress root directory.
@@ -171,8 +172,9 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) {
171172
* Optional. An array of additional script loading strategies. Default empty array.
172173
* Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
173174
*
174-
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
175-
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
175+
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
176+
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
177+
* @type string $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
176178
* }
177179
* @return bool Whether the script has been registered. True on success, false on failure.
178180
*/
@@ -193,6 +195,9 @@ function wp_register_script( $handle, $src, $deps = array(), $ver = false, $args
193195
if ( ! empty( $args['strategy'] ) ) {
194196
$wp_scripts->add_data( $handle, 'strategy', $args['strategy'] );
195197
}
198+
if ( ! empty( $args['fetchpriority'] ) ) {
199+
$wp_scripts->add_data( $handle, 'fetchpriority', $args['fetchpriority'] );
200+
}
196201
return $registered;
197202
}
198203

@@ -339,6 +344,7 @@ function wp_deregister_script( $handle ) {
339344
*
340345
* @since 2.1.0
341346
* @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
347+
* @since 6.9.0 The $fetchpriority parameter of type string was added to the $args parameter of type array.
342348
*
343349
* @param string $handle Name of the script. Should be unique.
344350
* @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
@@ -352,8 +358,9 @@ function wp_deregister_script( $handle ) {
352358
* Optional. An array of additional script loading strategies. Default empty array.
353359
* Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
354360
*
355-
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
356-
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
361+
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
362+
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
363+
* @type string $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
357364
* }
358365
*/
359366
function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $args = array() ) {
@@ -378,6 +385,9 @@ function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $
378385
if ( ! empty( $args['strategy'] ) ) {
379386
$wp_scripts->add_data( $_handle[0], 'strategy', $args['strategy'] );
380387
}
388+
if ( ! empty( $args['fetchpriority'] ) ) {
389+
$wp_scripts->add_data( $_handle[0], 'fetchpriority', $args['fetchpriority'] );
390+
}
381391
}
382392

383393
$wp_scripts->enqueue( $handle );

src/wp-includes/script-loader.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,10 @@ function wp_default_scripts( $scripts ) {
10471047
did_action( 'init' ) && $scripts->localize( 'wp-plupload', 'pluploadL10n', $uploader_l10n );
10481048

10491049
$scripts->add( 'comment-reply', "/wp-includes/js/comment-reply$suffix.js", array(), false, 1 );
1050-
did_action( 'init' ) && $scripts->add_data( 'comment-reply', 'strategy', 'async' );
1050+
if ( did_action( 'init' ) ) {
1051+
$scripts->add_data( 'comment-reply', 'strategy', 'async' );
1052+
$scripts->add_data( 'comment-reply', 'fetchpriority', 'low' ); // In Chrome this is automatically low due to the async strategy, but in Firefox and Safari the priority is normal/medium.
1053+
}
10511054

10521055
$scripts->add( 'json2', "/wp-includes/js/json2$suffix.js", array(), '2015-05-03' );
10531056
did_action( 'init' ) && $scripts->add_data( 'json2', 'conditional', 'lt IE 8' );

0 commit comments

Comments
 (0)