Make WordPress Core

Changeset 56933


Ignore:
Timestamp:
10/13/2023 06:44:12 PM (9 months ago)
Author:
westonruter
Message:

Script Loader: Move delayed head script to footer when there is a blocking footer dependent.

This prevents a performance regression when a blocking script is enqueued in the footer which depends on a delayed script in the head (with async or defer). In order to preserve the execution order, a delayed dependency must fall back to blocking when there is a blocking dependent. But since it was originally delayed (and thus executes similarly to a footer script), it does not need to be in the head and can be moved to the footer. This prevents blocking the critical rendering path.

Props adamsilverstein, westonruter, flixos90.
Fixes #59599.
See #12009.

Location:
trunk
Files:
2 edited

Legend:

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

    r56687 r56933  
    233233
    234234    /**
     235     * Checks whether all dependents of a given handle are in the footer.
     236     *
     237     * If there are no dependents, this is considered the same as if all dependents were in the footer.
     238     *
     239     * @since 6.4.0
     240     *
     241     * @param string $handle Script handle.
     242     * @return bool Whether all dependents are in the footer.
     243     */
     244    private function are_all_dependents_in_footer( $handle ) {
     245        foreach ( $this->get_dependents( $handle ) as $dep ) {
     246            if ( isset( $this->groups[ $dep ] ) && 0 === $this->groups[ $dep ] ) {
     247                return false;
     248            }
     249        }
     250        return true;
     251    }
     252
     253    /**
    235254     * Processes a script dependency.
    236255     *
     
    280299        if ( ! $this->is_delayed_strategy( $intended_strategy ) ) {
    281300            $intended_strategy = '';
     301        }
     302
     303        /*
     304         * Move this script to the footer if:
     305         * 1. The script is in the header group.
     306         * 2. The current output is the header.
     307         * 3. The intended strategy is delayed.
     308         * 4. The actual strategy is not delayed.
     309         * 5. All dependent scripts are in the footer.
     310         */
     311        if (
     312            0 === $group &&
     313            0 === $this->groups[ $handle ] &&
     314            $intended_strategy &&
     315            ! $this->is_delayed_strategy( $strategy ) &&
     316            $this->are_all_dependents_in_footer( $handle )
     317        ) {
     318            $this->in_footer[] = $handle;
     319            return false;
    282320        }
    283321
  • trunk/tests/phpunit/tests/dependencies/scripts.php

    r56748 r56933  
    30663066        add_theme_support( 'html5', array( 'script' ) );
    30673067    }
     3068
     3069    /**
     3070     * Test that a script is moved to the footer if it is made non-deferrable, was in the header and
     3071     * all scripts that depend on it are in the footer.
     3072     *
     3073     * @ticket 58599
     3074     *
     3075     * @dataProvider data_provider_script_move_to_footer
     3076     *
     3077     * @param callable $set_up             Test setup.
     3078     * @param string   $expected_header    Expected output for header.
     3079     * @param string   $expected_footer    Expected output for footer.
     3080     * @param string[] $expected_in_footer Handles expected to be in the footer.
     3081     * @param array    $expected_groups    Expected groups.
     3082     */
     3083    public function test_wp_scripts_move_to_footer( $set_up, $expected_header, $expected_footer, $expected_in_footer, $expected_groups ) {
     3084        $set_up();
     3085
     3086        // Get the header output.
     3087        ob_start();
     3088        wp_scripts()->do_head_items();
     3089        $header = ob_get_clean();
     3090
     3091        // Print a script in the body just to make sure it doesn't cause problems.
     3092        ob_start();
     3093        wp_print_scripts( array( 'jquery' ) );
     3094        ob_end_clean();
     3095
     3096        // Get the footer output.
     3097        ob_start();
     3098        wp_scripts()->do_footer_items();
     3099        $footer = ob_get_clean();
     3100
     3101        $this->assertEqualMarkup( $expected_header, $header, 'Expected header script markup to match.' );
     3102        $this->assertEqualMarkup( $expected_footer, $footer, 'Expected footer script markup to match.' );
     3103        $this->assertEqualSets( $expected_in_footer, wp_scripts()->in_footer, 'Expected to have the same handles for in_footer.' );
     3104        $this->assertEquals( $expected_groups, wp_scripts()->groups, 'Expected groups to match.' );
     3105    }
     3106
     3107    /**
     3108     * Data provider for test_wp_scripts_move_to_footer.
     3109     *
     3110     * @return array[]
     3111     */
     3112    public function data_provider_script_move_to_footer() {
     3113        return array(
     3114            'footer-blocking-dependent-of-defer-head-script' => array(
     3115                'set_up'             => static function () {
     3116                    wp_enqueue_script( 'script-a', 'https://example.com/script-a.js', array(), null, array( 'strategy' => 'defer' ) );
     3117                    wp_enqueue_script( 'script-b', 'https://example.com/script-b.js', array( 'script-a' ), null, array( 'in_footer' => true ) );
     3118                },
     3119                'expected_header'    => '',
     3120                'expected_footer'    => '
     3121                    <script type="text/javascript" src="https://example.com/script-a.js" id="script-a-js" data-wp-strategy="defer"></script>
     3122                    <script type="text/javascript" src="https://example.com/script-b.js" id="script-b-js"></script>
     3123                ',
     3124                'expected_in_footer' => array(
     3125                    'script-a',
     3126                    'script-b',
     3127                ),
     3128                'expected_groups'    => array(
     3129                    'script-a' => 0,
     3130                    'script-b' => 1,
     3131                    'jquery'   => 0,
     3132                ),
     3133            ),
     3134
     3135            'footer-blocking-dependent-of-async-head-script' => array(
     3136                'set_up'             => static function () {
     3137                    wp_enqueue_script( 'script-a', 'https://example.com/script-a.js', array(), null, array( 'strategy' => 'async' ) );
     3138                    wp_enqueue_script( 'script-b', 'https://example.com/script-b.js', array( 'script-a' ), null, array( 'in_footer' => true ) );
     3139                },
     3140                'expected_header'    => '',
     3141                'expected_footer'    => '
     3142                    <script type="text/javascript" src="https://example.com/script-a.js" id="script-a-js" data-wp-strategy="async"></script>
     3143                    <script type="text/javascript" src="https://example.com/script-b.js" id="script-b-js"></script>
     3144                ',
     3145                'expected_in_footer' => array(
     3146                    'script-a',
     3147                    'script-b',
     3148                ),
     3149                'expected_groups'    => array(
     3150                    'script-a' => 0,
     3151                    'script-b' => 1,
     3152                    'jquery'   => 0,
     3153                ),
     3154            ),
     3155
     3156            'head-blocking-dependent-of-delayed-head-script' => array(
     3157                'set_up'             => static function () {
     3158                    wp_enqueue_script( 'script-a', 'https://example.com/script-a.js', array(), null, array( 'strategy' => 'defer' ) );
     3159                    wp_enqueue_script( 'script-b', 'https://example.com/script-b.js', array( 'script-a' ), null, array( 'in_footer' => false ) );
     3160                },
     3161                'expected_header'    => '
     3162                    <script type="text/javascript" src="https://example.com/script-a.js" id="script-a-js" data-wp-strategy="defer"></script>
     3163                    <script type="text/javascript" src="https://example.com/script-b.js" id="script-b-js"></script>
     3164                ',
     3165                'expected_footer'    => '',
     3166                'expected_in_footer' => array(),
     3167                'expected_groups'    => array(
     3168                    'script-a' => 0,
     3169                    'script-b' => 0,
     3170                    'jquery'   => 0,
     3171                ),
     3172            ),
     3173
     3174            'delayed-footer-dependent-of-delayed-head-script' => array(
     3175                'set_up'             => static function () {
     3176                    wp_enqueue_script( 'script-a', 'https://example.com/script-a.js', array(), null, array( 'strategy' => 'defer' ) );
     3177                    wp_enqueue_script(
     3178                        'script-b',
     3179                        'https://example.com/script-b.js',
     3180                        array( 'script-a' ),
     3181                        null,
     3182                        array(
     3183                            'strategy'  => 'defer',
     3184                            'in_footer' => true,
     3185                        )
     3186                    );
     3187                },
     3188                'expected_header'    => '
     3189                    <script type="text/javascript" src="https://example.com/script-a.js" id="script-a-js" defer="defer" data-wp-strategy="defer"></script>
     3190                ',
     3191                'expected_footer'    => '
     3192                    <script type="text/javascript" src="https://example.com/script-b.js" id="script-b-js" defer="defer" data-wp-strategy="defer"></script>
     3193                ',
     3194                'expected_in_footer' => array(
     3195                    'script-b',
     3196                ),
     3197                'expected_groups'    => array(
     3198                    'script-a' => 0,
     3199                    'script-b' => 1,
     3200                    'jquery'   => 0,
     3201                ),
     3202            ),
     3203
     3204            'delayed-dependent-in-header-and-delayed-dependents-in-footer' => array(
     3205                'set_up'             => static function () {
     3206                    wp_enqueue_script( 'script-a', 'https://example.com/script-a.js', array(), null, array( 'strategy' => 'defer' ) );
     3207                    wp_enqueue_script(
     3208                        'script-b',
     3209                        'https://example.com/script-b.js',
     3210                        array( 'script-a' ),
     3211                        null,
     3212                        array(
     3213                            'strategy'  => 'defer',
     3214                            'in_footer' => false,
     3215                        )
     3216                    );
     3217                    wp_enqueue_script(
     3218                        'script-c',
     3219                        'https://example.com/script-c.js',
     3220                        array( 'script-a' ),
     3221                        null,
     3222                        array(
     3223                            'strategy'  => 'defer',
     3224                            'in_footer' => true,
     3225                        )
     3226                    );
     3227                    wp_enqueue_script(
     3228                        'script-d',
     3229                        'https://example.com/script-d.js',
     3230                        array( 'script-a' ),
     3231                        null,
     3232                        array(
     3233                            'strategy'  => 'defer',
     3234                            'in_footer' => true,
     3235                        )
     3236                    );
     3237                },
     3238                'expected_header'    => '
     3239                    <script type="text/javascript" src="https://example.com/script-a.js" id="script-a-js" defer="defer" data-wp-strategy="defer"></script>
     3240                    <script type="text/javascript" src="https://example.com/script-b.js" id="script-b-js" defer="defer" data-wp-strategy="defer"></script>
     3241                ',
     3242                'expected_footer'    => '
     3243                    <script type="text/javascript" src="https://example.com/script-c.js" id="script-c-js" defer="defer" data-wp-strategy="defer"></script>
     3244                    <script type="text/javascript" src="https://example.com/script-d.js" id="script-d-js" defer="defer" data-wp-strategy="defer"></script>
     3245                ',
     3246                'expected_in_footer' => array(
     3247                    'script-c',
     3248                    'script-d',
     3249                ),
     3250                'expected_groups'    => array(
     3251                    'script-a' => 0,
     3252                    'script-b' => 0,
     3253                    'script-c' => 1,
     3254                    'script-d' => 1,
     3255                    'jquery'   => 0,
     3256                ),
     3257            ),
     3258
     3259            'all-dependents-in-footer-with-one-blocking' => array(
     3260                'set_up'             => static function () {
     3261                    wp_enqueue_script( 'script-a', 'https://example.com/script-a.js', array(), null, array( 'strategy' => 'defer' ) );
     3262                    wp_enqueue_script(
     3263                        'script-b',
     3264                        'https://example.com/script-b.js',
     3265                        array( 'script-a' ),
     3266                        null,
     3267                        array(
     3268                            'strategy'  => 'defer',
     3269                            'in_footer' => true,
     3270                        )
     3271                    );
     3272                    wp_enqueue_script( 'script-c', 'https://example.com/script-c.js', array( 'script-a' ), null, true );
     3273                    wp_enqueue_script(
     3274                        'script-d',
     3275                        'https://example.com/script-d.js',
     3276                        array( 'script-a' ),
     3277                        null,
     3278                        array(
     3279                            'strategy'  => 'defer',
     3280                            'in_footer' => true,
     3281                        )
     3282                    );
     3283                },
     3284                'expected_header'    => '',
     3285                'expected_footer'    => '
     3286                    <script type="text/javascript" src="https://example.com/script-a.js" id="script-a-js" data-wp-strategy="defer"></script>
     3287                    <script type="text/javascript" src="https://example.com/script-b.js" id="script-b-js" defer="defer" data-wp-strategy="defer"></script>
     3288                    <script type="text/javascript" src="https://example.com/script-c.js" id="script-c-js"></script>
     3289                    <script type="text/javascript" src="https://example.com/script-d.js" id="script-d-js" defer="defer" data-wp-strategy="defer"></script>
     3290                ',
     3291                'expected_in_footer' => array(
     3292                    'script-a',
     3293                    'script-b',
     3294                    'script-c',
     3295                    'script-d',
     3296                ),
     3297                'expected_groups'    => array(
     3298                    'script-a' => 0,
     3299                    'script-b' => 1,
     3300                    'script-c' => 1,
     3301                    'script-d' => 1,
     3302                    'jquery'   => 0,
     3303
     3304                ),
     3305            ),
     3306
     3307            'blocking-dependents-in-head-and-footer'     => array(
     3308                'set_up'             => static function () {
     3309                    wp_enqueue_script( 'script-a', 'https://example.com/script-a.js', array(), null, array( 'strategy' => 'defer' ) );
     3310                    wp_enqueue_script(
     3311                        'script-b',
     3312                        'https://example.com/script-b.js',
     3313                        array( 'script-a' ),
     3314                        null,
     3315                        array(
     3316                            'strategy'  => 'defer',
     3317                            'in_footer' => false,
     3318                        )
     3319                    );
     3320                    wp_enqueue_script( 'script-c', 'https://example.com/script-c.js', array( 'script-a' ), null, true );
     3321                    wp_enqueue_script(
     3322                        'script-d',
     3323                        'https://example.com/script-d.js',
     3324                        array( 'script-a' ),
     3325                        null,
     3326                        array(
     3327                            'strategy'  => 'defer',
     3328                            'in_footer' => true,
     3329                        )
     3330                    );
     3331                },
     3332                'expected_header'    => '
     3333                    <script type="text/javascript" src="https://example.com/script-a.js" id="script-a-js" data-wp-strategy="defer"></script>
     3334                    <script type="text/javascript" src="https://example.com/script-b.js" id="script-b-js" defer="defer" data-wp-strategy="defer"></script>
     3335                ',
     3336                'expected_footer'    => '
     3337                    <script type="text/javascript" src="https://example.com/script-c.js" id="script-c-js"></script>
     3338                    <script type="text/javascript" src="https://example.com/script-d.js" id="script-d-js" defer="defer" data-wp-strategy="defer"></script>
     3339                ',
     3340                'expected_in_footer' => array(
     3341                    'script-c',
     3342                    'script-d',
     3343                ),
     3344                'expected_groups'    => array(
     3345                    'script-a' => 0,
     3346                    'script-b' => 0,
     3347                    'script-c' => 1,
     3348                    'script-d' => 1,
     3349                    'jquery'   => 0,
     3350                ),
     3351            ),
     3352
     3353        );
     3354    }
    30683355}
Note: See TracChangeset for help on using the changeset viewer.