Make WordPress Core


Ignore:
Timestamp:
10/14/2025 05:45:17 AM (3 months ago)
Author:
westonruter
Message:

Script Loader: Propagate fetchpriority from dependents to dependencies.

This introduces a "fetchpriority bumping" mechanism for both classic scripts (WP_Scripts) and script modules (WP_Script_Modules). When a script with a higher fetchpriority is enqueued, any of its dependencies will have their fetchpriority elevated to match that of the highest-priority dependent. This ensures that all assets in a critical dependency chain are loaded with the appropriate priority, preventing a high-priority script from being blocked by a low-priority dependency. This is similar to logic used in script loading strategies to ensure that a blocking dependent causes delayed (async/defer) dependencies to also become blocking. See #12009.

When a script's fetchpriority is escalated, its original, registered priority is added to the tag via a data-wp-fetchpriority attribute. This matches the addition of the data-wp-strategy parameter added when the resulting loading strategy does not match the original.

Developed in https://github.com/WordPress/wordpress-develop/pull/9770.

Follow-up to [60704].

Props westonruter, jonsurrell.
Fixes #61734.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-scripts.php

    r60722 r60931  
    128128     *
    129129     * @since 6.3.0
    130      * @var array
     130     * @var array<string, string[]>
    131131     */
    132132    private $dependents_map = array();
     
    440440            $attr['data-wp-strategy'] = $intended_strategy;
    441441        }
    442         if ( isset( $obj->extra['fetchpriority'] ) && 'auto' !== $obj->extra['fetchpriority'] && $this->is_valid_fetchpriority( $obj->extra['fetchpriority'] ) ) {
    443             $attr['fetchpriority'] = $obj->extra['fetchpriority'];
    444         }
     442
     443        // Determine fetchpriority.
     444        $original_fetchpriority = isset( $obj->extra['fetchpriority'] ) ? $obj->extra['fetchpriority'] : null;
     445        if ( null === $original_fetchpriority || ! $this->is_valid_fetchpriority( $original_fetchpriority ) ) {
     446            $original_fetchpriority = 'auto';
     447        }
     448        $actual_fetchpriority = $this->get_highest_fetchpriority_with_dependents( $handle );
     449        if ( null === $actual_fetchpriority ) {
     450            // If null, it's likely this script was not explicitly enqueued, so in this case use the original priority.
     451            $actual_fetchpriority = $original_fetchpriority;
     452        }
     453        if ( is_string( $actual_fetchpriority ) && 'auto' !== $actual_fetchpriority ) {
     454            $attr['fetchpriority'] = $actual_fetchpriority;
     455        }
     456        if ( $original_fetchpriority !== $actual_fetchpriority ) {
     457            $attr['data-wp-fetchpriority'] = $original_fetchpriority;
     458        }
     459
    445460        $tag  = $translations . $ie_conditional_prefix . $before_script;
    446461        $tag .= wp_get_script_tag( $attr );
     
    899914     * Gets all dependents of a script.
    900915     *
     916     * This is not recursive.
     917     *
    901918     * @since 6.3.0
    902919     *
     
    10501067
    10511068        return $eligible_strategies;
     1069    }
     1070
     1071    /**
     1072     * Gets the highest fetch priority for a given script and all of its dependent scripts.
     1073     *
     1074     * @since 6.9.0
     1075     * @see self::filter_eligible_strategies()
     1076     * @see WP_Script_Modules::get_highest_fetchpriority_with_dependents()
     1077     *
     1078     * @param string              $handle  Script module ID.
     1079     * @param array<string, true> $checked Optional. An array of already checked script handles, used to avoid recursive loops.
     1080     * @return string|null Highest fetch priority for the script and its dependents.
     1081     */
     1082    private function get_highest_fetchpriority_with_dependents( string $handle, array $checked = array() ): ?string {
     1083        // If there is a recursive dependency, return early.
     1084        if ( isset( $checked[ $handle ] ) ) {
     1085            return null;
     1086        }
     1087
     1088        // Mark this handle as checked to guard against infinite recursion.
     1089        $checked[ $handle ] = true;
     1090
     1091        // Abort if the script is not enqueued or a dependency of an enqueued script.
     1092        if ( ! $this->query( $handle, 'enqueued' ) ) {
     1093            return null;
     1094        }
     1095
     1096        $fetchpriority = $this->get_data( $handle, 'fetchpriority' );
     1097        if ( ! $this->is_valid_fetchpriority( $fetchpriority ) ) {
     1098            $fetchpriority = 'auto';
     1099        }
     1100
     1101        static $priorities   = array(
     1102            'low',
     1103            'auto',
     1104            'high',
     1105        );
     1106        $high_priority_index = count( $priorities ) - 1;
     1107
     1108        $highest_priority_index = (int) array_search( $fetchpriority, $priorities, true );
     1109        if ( $highest_priority_index !== $high_priority_index ) {
     1110            foreach ( $this->get_dependents( $handle ) as $dependent_handle ) {
     1111                $dependent_priority = $this->get_highest_fetchpriority_with_dependents( $dependent_handle, $checked );
     1112                if ( is_string( $dependent_priority ) ) {
     1113                    $highest_priority_index = max(
     1114                        $highest_priority_index,
     1115                        (int) array_search( $dependent_priority, $priorities, true )
     1116                    );
     1117                    if ( $highest_priority_index === $high_priority_index ) {
     1118                        break;
     1119                    }
     1120                }
     1121            }
     1122        }
     1123
     1124        return $priorities[ $highest_priority_index ];
    10521125    }
    10531126
Note: See TracChangeset for help on using the changeset viewer.