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/tests/phpunit/tests/dependencies/scripts.php

    r60729 r60931  
    12331233            )
    12341234        );
     1235        // Note: All of these scripts have fetchpriority=high because the leaf dependent script has that fetch priority.
    12351236        $output    = get_echo( 'wp_print_scripts' );
    1236         $expected  = "<script type='text/javascript' src='/main-script-d4.js' id='main-script-d4-js' defer='defer' data-wp-strategy='defer'></script>\n";
    1237         $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";
    1238         $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";
     1237        $expected  = "<script type='text/javascript' src='/main-script-d4.js'        id='main-script-d4-js'        defer='defer' data-wp-strategy='defer' fetchpriority='high' data-wp-fetchpriority='auto'></script>\n";
     1238        $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";
     1239        $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";
    12391240        $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";
    12401241
     
    13421343        wp_register_script( 'alias', false, array(), null, array( 'fetchpriority' => 'low' ) );
    13431344        $this->assertArrayNotHasKey( 'fetchpriority', wp_scripts()->registered['alias']->extra );
     1345    }
     1346
     1347    /**
     1348     * Data provider.
     1349     *
     1350     * @return array<string, array{enqueues: string[], expected: string}>
     1351     */
     1352    public function data_provider_to_test_fetchpriority_bumping(): array {
     1353        return array(
     1354            'enqueue_bajo' => array(
     1355                'enqueues' => array( 'bajo' ),
     1356                'expected' => '<script fetchpriority="low" id="bajo-js" src="/bajo.js" type="text/javascript"></script>',
     1357            ),
     1358            'enqueue_auto' => array(
     1359                'enqueues' => array( 'auto' ),
     1360                'expected' => '
     1361                    <script type="text/javascript" src="/bajo.js" id="bajo-js" data-wp-fetchpriority="low"></script>
     1362                    <script type="text/javascript" src="/auto.js" id="auto-js"></script>
     1363                ',
     1364            ),
     1365            'enqueue_alto' => array(
     1366                'enqueues' => array( 'alto' ),
     1367                'expected' => '
     1368                    <script type="text/javascript" src="/bajo.js" id="bajo-js" fetchpriority="high" data-wp-fetchpriority="low"></script>
     1369                    <script type="text/javascript" src="/auto.js" id="auto-js" fetchpriority="high" data-wp-fetchpriority="auto"></script>
     1370                    <script type="text/javascript" src="/alto.js" id="alto-js" fetchpriority="high"></script>
     1371                ',
     1372            ),
     1373        );
     1374    }
     1375
     1376    /**
     1377     * Tests a higher fetchpriority on a dependent script module causes the fetchpriority of a dependency script module to be bumped.
     1378     *
     1379     * @ticket 61734
     1380     *
     1381     * @covers WP_Scripts::get_dependents
     1382     * @covers WP_Scripts::get_highest_fetchpriority_with_dependents
     1383     * @covers WP_Scripts::do_item
     1384     *
     1385     * @dataProvider data_provider_to_test_fetchpriority_bumping
     1386     */
     1387    public function test_fetchpriority_bumping( array $enqueues, string $expected ) {
     1388        wp_register_script( 'bajo', '/bajo.js', array(), null, array( 'fetchpriority' => 'low' ) );
     1389        wp_register_script( 'auto', '/auto.js', array( 'bajo' ), null, array( 'fetchpriority' => 'auto' ) );
     1390        wp_register_script( 'alto', '/alto.js', array( 'auto' ), null, array( 'fetchpriority' => 'high' ) );
     1391
     1392        foreach ( $enqueues as $enqueue ) {
     1393            wp_enqueue_script( $enqueue );
     1394        }
     1395
     1396        $actual = get_echo( 'wp_print_scripts' );
     1397        $this->assertEqualHTML( $expected, $actual, '<body>', "Snapshot:\n$actual" );
     1398    }
     1399
     1400    /**
     1401     * Tests bumping fetchpriority with complex dependency graph.
     1402     *
     1403     * @ticket 61734
     1404     * @link https://github.com/WordPress/wordpress-develop/pull/9770#issuecomment-3280065818
     1405     *
     1406     * @covers WP_Scripts::get_dependents
     1407     * @covers WP_Scripts::get_highest_fetchpriority_with_dependents
     1408     * @covers WP_Scripts::do_item
     1409     */
     1410    public function test_fetchpriority_bumping_a_to_z() {
     1411        wp_register_script( 'a', '/a.js', array( 'b' ), null, array( 'fetchpriority' => 'low' ) );
     1412        wp_register_script( 'b', '/b.js', array( 'c' ), null, array( 'fetchpriority' => 'auto' ) );
     1413        wp_register_script( 'c', '/c.js', array( 'd', 'e' ), null, array( 'fetchpriority' => 'auto' ) );
     1414        wp_register_script( 'd', '/d.js', array( 'z' ), null, array( 'fetchpriority' => 'high' ) );
     1415        wp_register_script( 'e', '/e.js', array(), null, array( 'fetchpriority' => 'auto' ) );
     1416
     1417        wp_register_script( 'x', '/x.js', array( 'd', 'y' ), null, array( 'fetchpriority' => 'high' ) );
     1418        wp_register_script( 'y', '/y.js', array( 'z' ), null, array( 'fetchpriority' => 'auto' ) );
     1419        wp_register_script( 'z', '/z.js', array(), null, array( 'fetchpriority' => 'auto' ) );
     1420
     1421        wp_enqueue_script( 'a' );
     1422        wp_enqueue_script( 'x' );
     1423
     1424        $actual   = get_echo( 'wp_print_scripts' );
     1425        $expected = '
     1426            <script type="text/javascript" src="/z.js" id="z-js" fetchpriority="high" data-wp-fetchpriority="auto"></script>
     1427            <script type="text/javascript" src="/d.js" id="d-js" fetchpriority="high"></script>
     1428            <script type="text/javascript" src="/e.js" id="e-js"></script>
     1429            <script type="text/javascript" src="/c.js" id="c-js"></script>
     1430            <script type="text/javascript" src="/b.js" id="b-js"></script>
     1431            <script type="text/javascript" src="/a.js" id="a-js" fetchpriority="low"></script>
     1432            <script type="text/javascript" src="/y.js" id="y-js" fetchpriority="high" data-wp-fetchpriority="auto"></script>
     1433            <script type="text/javascript" src="/x.js" id="x-js" fetchpriority="high"></script>
     1434        ';
     1435        $this->assertEqualHTML( $expected, $actual, '<body>', "Snapshot:\n$actual" );
     1436    }
     1437
     1438    /**
     1439     * Tests that printing a script without enqueueing has the same output as when it is enqueued.
     1440     *
     1441     * @ticket 61734
     1442     *
     1443     * @covers WP_Scripts::do_item
     1444     * @covers WP_Scripts::do_items
     1445     * @covers ::wp_default_scripts
     1446     *
     1447     * @dataProvider data_provider_enqueue_or_not_to_enqueue
     1448     */
     1449    public function test_printing_default_script_comment_reply_enqueued_or_not_enqueued( bool $enqueue ) {
     1450        $wp_scripts = wp_scripts();
     1451        wp_default_scripts( $wp_scripts );
     1452
     1453        $this->assertArrayHasKey( 'comment-reply', $wp_scripts->registered );
     1454        $wp_scripts->registered['comment-reply']->ver = null;
     1455        $this->assertArrayHasKey( 'fetchpriority', $wp_scripts->registered['comment-reply']->extra );
     1456        $this->assertSame( 'low', $wp_scripts->registered['comment-reply']->extra['fetchpriority'] );
     1457        $this->assertArrayHasKey( 'strategy', $wp_scripts->registered['comment-reply']->extra );
     1458        $this->assertSame( 'async', $wp_scripts->registered['comment-reply']->extra['strategy'] );
     1459        if ( $enqueue ) {
     1460            wp_enqueue_script( 'comment-reply' );
     1461            $markup = get_echo( array( $wp_scripts, 'do_items' ), array( false ) );
     1462        } else {
     1463            $markup = get_echo( array( $wp_scripts, 'do_items' ), array( array( 'comment-reply' ) ) );
     1464        }
     1465
     1466        $this->assertEqualHTML(
     1467            sprintf(
     1468                '<script type="text/javascript" src="%s" id="comment-reply-js" async="async" data-wp-strategy="async" fetchpriority="low"></script>',
     1469                includes_url( 'js/comment-reply.js' )
     1470            ),
     1471            $markup
     1472        );
     1473    }
     1474
     1475    /**
     1476     * Data provider for test_default_scripts_comment_reply_not_enqueued.
     1477     *
     1478     * @return array[]
     1479     */
     1480    public static function data_provider_enqueue_or_not_to_enqueue(): array {
     1481        return array(
     1482            'not_enqueued' => array(
     1483                false,
     1484            ),
     1485            'enqueued'     => array(
     1486                true,
     1487            ),
     1488        );
    13441489    }
    13451490
Note: See TracChangeset for help on using the changeset viewer.