Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ee14eb6
Script Modules: Bump fetchpriority for dependencies to be as high as …
westonruter Sep 6, 2025
0222ca9
Add missing phpdoc param
westonruter Sep 6, 2025
e2d4676
Explicitly set a11y module to have fetchpriority=low
westonruter Sep 6, 2025
c76ceb0
Add support for fetchpriority bumping in WP_Scripts
westonruter Sep 6, 2025
e652cbd
Account for whether a dependent script is actually enqueued
westonruter Sep 6, 2025
3dd0e0f
Add tests for complex dependency graphs
westonruter Sep 11, 2025
c59f440
Let fetchpriority of script module be determined be enqueued module
westonruter Sep 17, 2025
e453a53
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Sep 29, 2025
546e7a7
Stop iterating once highest priority found
westonruter Sep 29, 2025
a03337a
Simplify get_recursive_dependents() with better defensive coding
westonruter Sep 29, 2025
0b0f328
Merge branch 'trunk' into add/dependent-fetchpriority-harmony
westonruter Oct 8, 2025
334a82b
Better harmonize get_highest_fetchpriority implementations
westonruter Oct 8, 2025
cf809c8
Merge branch 'trunk' into add/dependent-fetchpriority-harmony
westonruter Oct 10, 2025
3f5ae8d
Update _WP_Dependency::$ver to allow null in addition to string|false
westonruter Oct 12, 2025
4dd633f
Account for directly printed scripts without enqueueing
westonruter Oct 12, 2025
95789ba
Ensure fetchpriority is processed for scripts without extra key set
westonruter Oct 12, 2025
86c13c9
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Oct 13, 2025
98abb51
Switch back to enqueue
westonruter Oct 13, 2025
eada641
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Oct 14, 2025
05e3173
Restore array_unique()
westonruter Oct 14, 2025
ed8a53f
Add test for wp_default_script_modules()
westonruter Oct 14, 2025
9d021ae
Test that high priority dependent of default script modules causes de…
westonruter Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add support for fetchpriority bumping in WP_Scripts
  • Loading branch information
westonruter committed Sep 6, 2025
commit c76ceb09a87dfc0654e1874a53eec212699fe003
1 change: 1 addition & 0 deletions src/wp-includes/class-wp-script-modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ public function add_hooks() {
*
* @since 6.9.0
* @see WP_Scripts::filter_eligible_strategies()
* @see WP_Scripts::get_highest_fetchpriority_with_dependents()
*
* @param string $id Script module ID.
* @param array<string, true> $checked Optional. An array of already checked script IDs, used to avoid recursive loops.
Expand Down
71 changes: 69 additions & 2 deletions src/wp-includes/class-wp-scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,19 @@ public function do_item( $handle, $group = false ) {
if ( $intended_strategy ) {
$attr['data-wp-strategy'] = $intended_strategy;
}
if ( isset( $obj->extra['fetchpriority'] ) && 'auto' !== $obj->extra['fetchpriority'] && $this->is_valid_fetchpriority( $obj->extra['fetchpriority'] ) ) {
$attr['fetchpriority'] = $obj->extra['fetchpriority'];

if ( isset( $obj->extra['fetchpriority'] ) ) {
$original_fetchpriority = $obj->extra['fetchpriority'];
if ( ! $this->is_valid_fetchpriority( $original_fetchpriority ) ) {
$original_fetchpriority = 'auto';
}
$actual_fetchpriority = $this->get_highest_fetchpriority_with_dependents( $handle );
if ( is_string( $actual_fetchpriority ) && 'auto' !== $actual_fetchpriority ) {
$attr['fetchpriority'] = $actual_fetchpriority;
}
if ( $original_fetchpriority !== $actual_fetchpriority ) {
$attr['data-wp-fetchpriority'] = $original_fetchpriority;
}
}
$tag = $translations . $ie_conditional_prefix . $before_script;
$tag .= wp_get_script_tag( $attr );
Expand Down Expand Up @@ -1023,6 +1034,62 @@ private function filter_eligible_strategies( $handle, $eligible_strategies = nul
return $eligible_strategies;
}

/**
* Gets the highest fetch priority for a given script and all of its dependent scripts.
*
* @since 6.9.0
* @see self::filter_eligible_strategies()
* @see WP_Script_Modules::get_highest_fetchpriority_with_dependents()
*
* @param string $handle Script module ID.
* @param array<string, true> $checked Optional. An array of already checked script handles, used to avoid recursive loops.
* @return string|null Highest fetch priority for the script and its dependents.
*/
private function get_highest_fetchpriority_with_dependents( string $handle, array $checked = array() ): ?string {
// If there is a recursive dependency, return early.
if ( isset( $checked[ $handle ] ) ) {
return null;
}

// Mark this handle as checked to guard against infinite recursion.
$checked[ $handle ] = true;

// If the handle is not enqueued, don't filter anything and return.
if ( ! $this->query( $handle, 'enqueued' ) ) {
return null;
}

$fetchpriority = $this->get_data( $handle, 'fetchpriority' );
if ( ! $this->is_valid_fetchpriority( $fetchpriority ) ) {
$fetchpriority = 'auto';
}

static $priority_mapping = array(
'low' => 0,
'auto' => 1,
'high' => 2,
);

$highest_priority_index = $priority_mapping[ $fetchpriority ];

foreach ( $this->get_dependents( $handle ) as $dependent_handle ) {
$dependent_priority = $this->get_highest_fetchpriority_with_dependents( $dependent_handle, $checked );
if ( is_string( $dependent_priority ) ) {
$highest_priority_index = max(
$highest_priority_index,
$priority_mapping[ $dependent_priority ]
);
}
}

$highest_priority = array_search( $highest_priority_index, $priority_mapping, true );
Copy link
Member

Choose a reason for hiding this comment

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

This is the same functionality but implemented differently from in modules:

https://github.com/westonruter/wordpress-develop/blob/c59f440dfda0d1d23825e28e587bd5cfb15de45b/src/wp-includes/class-wp-script-modules.php#L296-L312

Notably, there the array_search happens in the loop to get a numeric value for fetchpriority comparison.

I'd like implementations to match.

There's also an opportunity to exit the loop as soon once a high priority is found (I commented similarly on the modules implementation).

Copy link
Member Author

Choose a reason for hiding this comment

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

How about 334a82b?

if ( is_string( $highest_priority ) ) {
return $highest_priority;
}

return null;
}

/**
* Gets data for inline scripts registered for a specific handle.
*
Expand Down
57 changes: 55 additions & 2 deletions tests/phpunit/tests/dependencies/scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -1172,8 +1172,8 @@ public function test_defer_with_async_dependent() {
);
$output = get_echo( 'wp_print_scripts' );
$expected = "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js' defer='defer' data-wp-strategy='defer'></script>\n";
$expected .= "<script type='text/javascript' src='/dependent-script-d4-1.js' id='dependent-script-d4-1-js' defer='defer' data-wp-strategy='defer'></script>\n";
$expected .= "<script type='text/javascript' src='/dependent-script-d4-2.js' id='dependent-script-d4-2-js' defer='defer' data-wp-strategy='async' fetchpriority='low'></script>\n";
$expected .= "<script type='text/javascript' src='/dependent-script-d4-1.js' id='dependent-script-d4-1-js' defer='defer' data-wp-strategy='defer' fetchpriority='high' data-wp-fetchpriority='auto'></script>\n";
$expected .= "<script type='text/javascript' src='/dependent-script-d4-2.js' id='dependent-script-d4-2-js' defer='defer' data-wp-strategy='async' fetchpriority='high' data-wp-fetchpriority='low'></script>\n";
$expected .= "<script type='text/javascript' src='/dependent-script-d4-3.js' id='dependent-script-d4-3-js' defer='defer' data-wp-strategy='defer' fetchpriority='high'></script>\n";

$this->assertEqualHTML( $expected, $output, '<body>', 'Scripts registered as defer but that have dependents that are async are expected to have said dependents deferred.' );
Expand Down Expand Up @@ -1281,6 +1281,59 @@ public function test_invalid_fetchpriority_on_alias() {
$this->assertArrayNotHasKey( 'fetchpriority', wp_scripts()->registered['alias']->extra );
}

/**
* Data provider.
*
* @return array<string, array{enqueues: string[], expected: string}>
*/
public function data_provider_to_test_fetchpriority_bumping(): array {
return array(
'enqueue_bajo' => array(
'enqueues' => array( 'bajo' ),
'expected' => '<script fetchpriority="low" id="bajo-js" src="/bajo.js" type="text/javascript"></script>',
),
'enqueue_auto' => array(
'enqueues' => array( 'auto' ),
'expected' => '
<script type="text/javascript" src="/bajo.js" id="bajo-js" data-wp-fetchpriority="low"></script>
<script type="text/javascript" src="/auto.js" id="auto-js"></script>
',
),
'enqueue_alto' => array(
'enqueues' => array( 'alto' ),
'expected' => '
<script type="text/javascript" src="/bajo.js" id="bajo-js" fetchpriority="high" data-wp-fetchpriority="low"></script>
<script type="text/javascript" src="/auto.js" id="auto-js" fetchpriority="high" data-wp-fetchpriority="auto"></script>
<script type="text/javascript" src="/alto.js" id="alto-js" fetchpriority="high"></script>
',
),
);
}

/**
* Tests a higher fetchpriority on a dependent script module causes the fetchpriority of a dependency script module to be bumped.
*
* @ticket 61734
*
* @covers WP_Scripts::get_dependents
* @covers WP_Scripts::get_highest_fetchpriority_with_dependents
* @covers WP_Scripts::do_item
*
* @dataProvider data_provider_to_test_fetchpriority_bumping
*/
public function test_fetchpriority_bumping( array $enqueues, string $expected ) {
wp_register_script( 'bajo', '/bajo.js', array(), null, array( 'fetchpriority' => 'low' ) );
wp_register_script( 'auto', '/auto.js', array( 'bajo' ), null, array( 'fetchpriority' => 'auto' ) );
wp_register_script( 'alto', '/alto.js', array( 'auto' ), null, array( 'fetchpriority' => 'high' ) );

foreach ( $enqueues as $enqueue ) {
wp_enqueue_script( $enqueue );
}

$actual = get_echo( 'wp_print_scripts' );
$this->assertEqualHTML( $expected, $actual, '<body>', "Snapshot:\n$actual" );
}

/**
* Tests that scripts registered as defer become blocking when their dependents chain are all blocking.
*
Expand Down