Skip to content

Conversation

@westonruter
Copy link
Member

@westonruter westonruter commented May 19, 2025

  • Add new fetchpriority argument to go alongside the loading strategy (async & defer) when registering scripts via wp_register_script()/wp_enqueue_script(). This is a follow-up to the script loading strategies introduced in Core-12009.
  • Add a new 5th$args parameter to wp_register_script_module()/wp_enqueue_script_module() (and their corresponding methods) to mirror the same 5th $args parameter used in wp_register_script()/wp_enqeue_script(). This $args parameter same fetchpriority argument introduced for non-script modules. (Note that module scripts have a defer loading strategy by default, so adding support for a strategy would only be useful for async. This is not in the scope of this PR.) This will apply the fetchpriority attribute both to the SCRIPT[type="module"] tags as well as the LINK[rel="modulepreload"] tags for static import dependencies.
  • A new WP_Script_Modules::set_fetchpriority( string $id, string $priority ) method is added which allows the priority of registered scripts to be changed.
  • The default fetchpriority value for both scripts and script modules is auto.
  • When the fetchpriority attribute is emitted on SCRIPT and LINK tags, it is omitted if the value is auto since this is the default value.
  • The comment-reply script is given an explicit fetchpriority of low. While the script is already registered with the async loading strategy which Chrome causes the resource to be downloaded with a low priority by default, this is not the case for Safari or Firefox which use a default medium/normal priority. So the explicit low priority reduces the chance that the loading of the comment-reply script will interfere with the loading of resources needed in the critical rendering path (e.g. an LCP image element).
  • Script modules used for the view module scripts for blocks with Interactivity API support are given an explicit fetchpriority of low. The very first requirement/goal defined for the Interactivity API was for server-side rendering, therefore blocks should not depend on the view module script for initial rendering.
  • Tests have been written for the changes both to scripts and script modules, but the tests for the latter have additional changes since deficiencies were discovered (e.g. duplicate array keys, and global functions were untested).

Note

If Gutenberg is active, you won't see any changes without WordPress/gutenberg#70173 also checked out.

Trac ticket: https://core.trac.wordpress.org/ticket/61734

Follow ups:

Related:

  • Core-56408: Blocks: Allow registering multiple items for all supported asset types

This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

Gemini Summary

This pull request introduces support for the fetchpriority attribute on scripts and script modules, allowing for more granular control over resource loading priority.

Key Changes:

  • fetchpriority for Scripts & Modules:

    • The wp_register_script(), wp_enqueue_script(), wp_register_script_module(), and wp_enqueue_script_module() functions now accept a fetchpriority key in their $args array.
    • Valid values are 'high', 'low', and 'auto'. Invalid values will trigger a _doing_it_wrong() notice.
    • The fetchpriority attribute is added to the corresponding <script> and <link rel="modulepreload"> tags.
  • Performance Optimizations:

    • Scripts for blocks using the Interactivity API are now registered with fetchpriority="low" by default, as they are not considered critical for the initial page render.
    • The comment-reply script is also now loaded with fetchpriority="low".
  • New API Methods:

    • A new wp_script_modules()->set_fetchpriority() method has been added to modify the fetch priority of an already-registered script module.
  • Testing:

    • Comprehensive PHPUnit tests have been added to validate the new fetchpriority functionality for both classic scripts and script modules, including tests for invalid values and edge cases.

This is a non-breaking, additive change that provides a new tool for performance optimization.

Commit Message

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:
  • Improve type checks and type hints.

Developed in: #8815
Companion PR: WordPress/gutenberg#70173

Props westonruter, jonsurrell, swissspidy, luisherranz, kraftbj, audrasjb, dennysdionigi.
Fixes #61734.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@westonruter westonruter changed the title Use fetchpriority=low by default for script modules and add ability to override Introduce fetchpriority for Scripts and Script Modules May 20, 2025
Co-authored-by: Pascal Birchler <swissspidy@git.wordpress.org>
Co-authored-by: Luis Herranz <luisherranz@git.wordpress.org>
Comment on lines 16 to 22
* @phpstan-type ScriptModule array{
* src: string,
* version: string|false|null,
* enqueue: bool,
* dependencies: array<array{ id: string, import: 'dynamic'|'static' }>,
* fetchpriority: 'auto'|'low'|'high',
* }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these @phpstan annotations for myself as a safeguard while developing this patch. If others don't want PHPStan annotations in core, I'll remove prior to commit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better consistency, I think it's better to remove these annotations prior to commit indeed, as this is not something used elsewhere in Core. Also I dunno how the docblock parser will handle these info but I think it's going to add a lot of noise in dockblocks displayed on the generated documentation (same goes for other related phpstan annotations)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 74a49e1

@westonruter westonruter marked this pull request as ready for review May 26, 2025 21:08
@github-actions
Copy link

github-actions bot commented May 26, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter, swissspidy, luisherranz, kraftbj, audrasjb, jonsurrell.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link

@kraftbj kraftbj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this for regular scripts and it worked as advertised. I did confirm that add_data checks for is_scalar down the chain so lack of type checking here isn't an issue. The Web API, per MDN, will default to auto if not present or if the value is not valid (low, high, auto).

Copy link
Contributor

@audrasjb audrasjb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left a comment about phpstan docblock annotations

Comment on lines 16 to 22
* @phpstan-type ScriptModule array{
* src: string,
* version: string|false|null,
* enqueue: bool,
* dependencies: array<array{ id: string, import: 'dynamic'|'static' }>,
* fetchpriority: 'auto'|'low'|'high',
* }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better consistency, I think it's better to remove these annotations prior to commit indeed, as this is not something used elsewhere in Core. Also I dunno how the docblock parser will handle these info but I think it's going to add a lot of noise in dockblocks displayed on the generated documentation (same goes for other related phpstan annotations)

@audrasjb
Copy link
Contributor

@westonruter if this is still on track for 6.8.2, note that the PR is conflicting against trunk ;)

$wp_scripts->add_data( $handle, 'strategy', $args['strategy'] );
}
if ( ! empty( $args['fetchpriority'] ) ) {
$wp_scripts->add_data( $handle, 'fetchpriority', $args['fetchpriority'] );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetchpriority is checked against an allowlist for modules. I don't see that for scripts. Did I overlook that or should it be included?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sirreal In the case of classic scripts, the fetchpriority is instead being specified when the scripts are registered. The only script which gets low is comment-reply:

$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.

There isn't any allowlist to default to low for non-module scripts since there is no generalizable pattern for being able to opt in like there is for script modules with the Interactivity API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More than default I'm wondering about accepting arbitrary values. It's not likely harmful, but perhaps _doing_it_wrong could be shown and the value discarded if an unsupported fetchpriority is provided.

I understand it's a well defined set of values: high | low | auto.

For example:

wp_enqueue_script( 'example', '/ex.js', array(), '' );
wp_scripts()->add_data( 'example', 'fetchpriority', 'silly' );

produces

<script src="/ex.js?ver=…" id="example-js" fetchpriority="silly"></script>

which is… silly 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. For async/defer, this is handled in the add_data() function:

if ( 'strategy' === $key ) {
if ( ! empty( $value ) && ! $this->is_delayed_strategy( $value ) ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1: $strategy, 2: $handle */
__( 'Invalid strategy `%1$s` defined for `%2$s` during script registration.' ),
$value,
$handle
),
'6.3.0'
);
return false;
} elseif ( ! $this->registered[ $handle ]->src && $this->is_delayed_strategy( $value ) ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1: $strategy, 2: $handle */
__( 'Cannot supply a strategy `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
$value,
$handle
),
'6.3.0'
);
return false;
}
}

This would make sense to include there as well for fetchpriority.

@westonruter westonruter requested a review from sirreal August 26, 2025 22:39
Copy link
Member

@sirreal sirreal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to provide an in-depth review early next week, but don't feel obliged to wait for me. I've only reviewed the changes superficially so far.

@sirreal sirreal self-requested a review August 27, 2025 11:52
Copy link
Member

@sirreal sirreal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes look good to me and work well in my testing.

I would like to check for valid fetchpriority in scripts as discussed in https://github.com/WordPress/wordpress-develop/pull/8815/files#r2314439414, but otherwise this seems ready to merge.

@westonruter westonruter requested a review from sirreal September 2, 2025 19:15
@westonruter
Copy link
Member Author

@sirreal Thank you for the review! I've added checks for valid fetchpriority along with tests to ensure the expected behavior.

Comment on lines +1350 to +1355
private function get_registered_script_modules( WP_Script_Modules $script_modules ): array {
$reflection_class = new ReflectionClass( $script_modules );
$registered_property = $reflection_class->getProperty( 'registered' );
$registered_property->setAccessible( true );
return $registered_property->getValue( $script_modules );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to do some work on https://core.trac.wordpress.org/ticket/60597 to open modules and not require reflection 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. It would be nice if there were more getters and setters, similar to WP_Scripts.

Copy link
Member

@sirreal sirreal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a quick review of the recent changes and they look good. I did not retest.

A final minor note on data providers which I believe should be static. I think this is ready 👍

Co-authored-by: Jon Surrell <sirreal@users.noreply.github.com>
pento pushed a commit that referenced this pull request Sep 3, 2025
* 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
@github-actions
Copy link

github-actions bot commented Sep 3, 2025

A commit was made that fixes the Trac ticket referenced in the description of this pull request.

SVN changeset: 60704
GitHub commit: c987f34

This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.

@github-actions github-actions bot closed this Sep 3, 2025
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Sep 3, 2025
* 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 [WordPress/wordpress-develop#8815 GitHub PR], with [WordPress/gutenberg#70173 companion for Gutenberg].

Props westonruter, jonsurrell, swissspidy, luisherranz, kraftbj, audrasjb, dennysdionigi.
Fixes #61734.

Built from https://develop.svn.wordpress.org/trunk@60704


git-svn-id: http://core.svn.wordpress.org/trunk@60040 1a063a9b-81f0-0310-95a4-ce76da25c4cd
github-actions bot pushed a commit to platformsh/wordpress-performance that referenced this pull request Sep 3, 2025
* 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 [WordPress/wordpress-develop#8815 GitHub PR], with [WordPress/gutenberg#70173 companion for Gutenberg].

Props westonruter, jonsurrell, swissspidy, luisherranz, kraftbj, audrasjb, dennysdionigi.
Fixes #61734.

Built from https://develop.svn.wordpress.org/trunk@60704


git-svn-id: https://core.svn.wordpress.org/trunk@60040 1a063a9b-81f0-0310-95a4-ce76da25c4cd
@westonruter
Copy link
Member Author

I just started trying using Gemini to code review patches. I hadn't done it for this PR, so I'm doing so retroactively:

git show c987f34948491544fcc1e5a49af193532bb4ec54 | gemini -m "gemini-2.5-pro" -p "Review these changes to WordPress core according to WordPress coding standards. Anything amiss?"

Output:

This is an excellent patch. The introduction of the fetchpriority attribute is well-implemented, the code is clean, and the accompanying tests are thorough and robust. The refactoring of the script modules test suite to use the WP_HTML_Tag_Processor and data providers is a significant improvement.

The changes adhere to the WordPress coding standards regarding documentation, naming conventions, and code style.

I found one very minor point that could be improved for consistency:

In src/wp-includes/class-wp-script-modules.php, a check was updated to validate the type of a dependency ID:

//...
if ( is_array( $dependency ) ) {
    if ( ! isset( $dependency['id'] ) || ! is_string( $dependency['id'] ) ) {
        _doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' );
        continue;
    }
//...

The _doing_it_wrong() message, Missing required id key..., was not updated to reflect the new ! is_string() check. It now covers cases where the key is present but not a string (i.e., invalid), not just missing. A more accurate message might be: Missing or invalid required "id" key in dependency array entry.

This is a minor detail in an otherwise outstanding commit. The implementation is solid.

jonnynews pushed a commit to spacedmonkey/wordpress-develop that referenced this pull request Sep 24, 2025
* 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 [WordPress#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants