Make WordPress Core

Changeset 61587


Ignore:
Timestamp:
02/04/2026 01:41:25 AM (7 months ago)
Author:
westonruter
Message:

Script Loader: Allow classic scripts to depend on script modules.

This allows classic scripts to declare dependencies on script modules by passing module_dependencies in the $args param for wp_register_script() or wp_enqueue_script(). The WP_Script_Modules::get_import_map() method is updated to traverse the dependency tree of all enqueued classic scripts to find any associated script module dependencies and include them in the importmap, enabling dynamic imports of modules within classic scripts.

A _wp_scripts_add_args_data() helper function is introduced to consolidate argument validation and processing for wp_register_script() and wp_enqueue_script(), reducing code duplication. This function validates that the $args array only contains recognized keys (strategy, in_footer, fetchpriority, module_dependencies) and triggers a _doing_it_wrong() notice for any unrecognized keys. Similarly, WP_Scripts::add_data() is updated to do early type checking for the data passed to $args. The script modules in module_dependencies may be referenced by a module ID string or by an array that has an id key, following the same pattern as dependencies in WP_Script_Modules.

When a script module is added to the module_dependencies for a classic script, but it does not exist at the time the importmap is printed, a _doing_it_wrong() notice is emitted.

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

Follow-up to [61323].

Props sirreal, westonruter.
See #64229.
Fixes #61500.

Location:
trunk
Files:
6 edited

Legend:

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

    r61542 r61587  
    8989         * @since 6.9.0 Added the $args parameter.
    9090         *
    91          * @param string                     $id       The identifier of the script module. Should be unique. It will be used in the
    92          *                                             final import map.
    93          * @param string                     $src      Optional. Full URL of the script module, or path of the script module relative
    94          *                                             to the WordPress root directory. If it is provided and the script module has
    95          *                                             not been registered yet, it will be registered.
    96          * @param array<string|array>        $deps     {
    97          *                                                 Optional. List of dependencies.
    98          *
    99          *                                                 @type string|array ...$0 {
    100          *                                                     An array of script module identifiers of the dependencies of this script
    101          *                                                     module. The dependencies can be strings or arrays. If they are arrays,
    102          *                                                     they need an `id` key with the script module identifier, and can contain
    103          *                                                     an `import` key with either `static` or `dynamic`. By default,
    104          *                                                     dependencies that don't contain an `import` key are considered static.
    105          *
    106          *                                                     @type string $id     The script module identifier.
    107          *                                                     @type string $import Optional. Import type. May be either `static` or
    108          *                                                                          `dynamic`. Defaults to `static`.
    109          *                                                 }
    110          *                                             }
    111          * @param string|false|null          $version Optional. String specifying the script module version number. Defaults to false.
    112          *                                             It is added to the URL as a query string for cache busting purposes. If $version
    113          *                                             is set to false, the version number is the currently installed WordPress version.
    114          *                                             If $version is set to null, no version is added.
    115          * @param array<string, string|bool> $args     {
     91         * @param string                              $id      The identifier of the script module. Should be unique. It will be used in the
     92         *                                                     final import map.
     93         * @param string                              $src     Optional. Full URL of the script module, or path of the script module relative
     94         *                                                     to the WordPress root directory. If it is provided and the script module has
     95         *                                                     not been registered yet, it will be registered.
     96         * @param array<string|array<string, string>> $deps    {
     97         *                                                         Optional. List of dependencies.
     98         *
     99         *                                                         @type string|array<string, string> ...$0 {
     100         *                                                             An array of script module identifiers of the dependencies of this script
     101         *                                                             module. The dependencies can be strings or arrays. If they are arrays,
     102         *                                                             they need an `id` key with the script module identifier, and can contain
     103         *                                                             an `import` key with either `static` or `dynamic`. By default,
     104         *                                                             dependencies that don't contain an `import` key are considered static.
     105         *
     106         *                                                             @type string $id     The script module identifier.
     107         *                                                             @type string $import Optional. Import type. May be either `static` or
     108         *                                                                                  `dynamic`. Defaults to `static`.
     109         *                                                         }
     110         *                                                     }
     111         * @param string|false|null                   $version Optional. String specifying the script module version number. Defaults to false.
     112         *                                                     It is added to the URL as a query string for cache busting purposes. If $version
     113         *                                                     is set to false, the version number is the currently installed WordPress version.
     114         *                                                     If $version is set to null, no version is added.
     115         * @param array<string, string|bool>          $args    {
    116116         *     Optional. An array of additional args. Default empty array.
    117117         *
     
    261261         * @since 6.9.0 Added the $args parameter.
    262262         *
    263          * @param string                     $id       The identifier of the script module. Should be unique. It will be used in the
    264          *                                             final import map.
    265          * @param string                     $src      Optional. Full URL of the script module, or path of the script module relative
    266          *                                             to the WordPress root directory. If it is provided and the script module has
    267          *                                             not been registered yet, it will be registered.
    268          * @param array<string|array>        $deps     {
    269          *                                                 Optional. List of dependencies.
    270          *
    271          *                                                 @type string|array ...$0 {
    272          *                                                     An array of script module identifiers of the dependencies of this script
    273          *                                                     module. The dependencies can be strings or arrays. If they are arrays,
    274          *                                                     they need an `id` key with the script module identifier, and can contain
    275          *                                                     an `import` key with either `static` or `dynamic`. By default,
    276          *                                                     dependencies that don't contain an `import` key are considered static.
    277          *
    278          *                                                     @type string $id     The script module identifier.
    279          *                                                     @type string $import Optional. Import type. May be either `static` or
    280          *                                                                          `dynamic`. Defaults to `static`.
    281          *                                                 }
    282          *                                             }
    283          * @param string|false|null          $version Optional. String specifying the script module version number. Defaults to false.
    284          *                                             It is added to the URL as a query string for cache busting purposes. If $version
    285          *                                             is set to false, the version number is the currently installed WordPress version.
    286          *                                             If $version is set to null, no version is added.
    287          * @param array<string, string|bool> $args     {
     263         * @param string                              $id      The identifier of the script module. Should be unique. It will be used in the
     264         *                                                     final import map.
     265         * @param string                              $src     Optional. Full URL of the script module, or path of the script module relative
     266         *                                                     to the WordPress root directory. If it is provided and the script module has
     267         *                                                     not been registered yet, it will be registered.
     268         * @param array<string|array<string, string>> $deps    {
     269         *                                                         Optional. List of dependencies.
     270         *
     271         *                                                         @type string|array<string, string> ...$0 {
     272         *                                                             An array of script module identifiers of the dependencies of this script
     273         *                                                             module. The dependencies can be strings or arrays. If they are arrays,
     274         *                                                             they need an `id` key with the script module identifier, and can contain
     275         *                                                             an `import` key with either `static` or `dynamic`. By default,
     276         *                                                             dependencies that don't contain an `import` key are considered static.
     277         *
     278         *                                                             @type string $id     The script module identifier.
     279         *                                                             @type string $import Optional. Import type. May be either `static` or
     280         *                                                                                  `dynamic`. Defaults to `static`.
     281         *                                                         }
     282         *                                                     }
     283         * @param string|false|null                   $version Optional. String specifying the script module version number. Defaults to false.
     284         *                                                     It is added to the URL as a query string for cache busting purposes. If $version
     285         *                                                     is set to false, the version number is the currently installed WordPress version.
     286         *                                                     If $version is set to null, no version is added.
     287         * @param array<string, string|bool>          $args    {
    288288         *     Optional. An array of additional args. Default empty array.
    289289         *
     
    534534         *
    535535         * @since 6.5.0
     536         * @since 7.0.0 Script module dependencies ('module_dependencies') of classic scripts are now included.
     537         *
     538         * @global WP_Scripts $wp_scripts
    536539         *
    537540         * @return array<string, array<string, string>> Array with an `imports` key mapping to an array of script module
     
    539542         */
    540543        private function get_import_map(): array {
     544                global $wp_scripts;
     545
    541546                $imports = array();
    542                 foreach ( array_keys( $this->get_dependencies( $this->queue ) ) as $id ) {
     547
     548                // Identify script modules that are dependencies of classic scripts.
     549                $classic_script_module_dependencies = array();
     550                if ( $wp_scripts instanceof WP_Scripts ) {
     551                        $handles = array_merge(
     552                                $wp_scripts->queue,
     553                                $wp_scripts->to_do,
     554                                $wp_scripts->done
     555                        );
     556
     557                        $processed = array();
     558                        while ( ! empty( $handles ) ) {
     559                                $handle = array_pop( $handles );
     560                                if ( isset( $processed[ $handle ] ) || ! isset( $wp_scripts->registered[ $handle ] ) ) {
     561                                        continue;
     562                                }
     563                                $processed[ $handle ] = true;
     564
     565                                $module_dependencies = $wp_scripts->get_data( $handle, 'module_dependencies' );
     566                                if ( is_array( $module_dependencies ) ) {
     567                                        $missing_module_dependencies = array();
     568                                        foreach ( $module_dependencies as $module ) {
     569                                                if ( is_string( $module ) ) {
     570                                                        $id = $module;
     571                                                } elseif ( is_array( $module ) && isset( $module['id'] ) && is_string( $module['id'] ) ) {
     572                                                        $id = $module['id'];
     573                                                } else {
     574                                                        // Invalid module dependency was supplied by direct manipulation of the extra data.
     575                                                        // Normally, this error scenario would be caught when WP_Scripts::add_data() is called.
     576                                                        continue;
     577                                                }
     578
     579                                                if ( ! isset( $this->registered[ $id ] ) ) {
     580                                                        $missing_module_dependencies[] = $id;
     581                                                } else {
     582                                                        $classic_script_module_dependencies[] = $id;
     583                                                }
     584                                        }
     585
     586                                        if ( count( $missing_module_dependencies ) > 0 ) {
     587                                                _doing_it_wrong(
     588                                                        'WP_Scripts::add_data',
     589                                                        sprintf(
     590                                                                /* translators: 1: Script handle, 2: 'module_dependencies', 3: List of missing dependency IDs. */
     591                                                                __( 'The script with the handle "%1$s" was enqueued with script module dependencies ("%2$s") that are not registered: %3$s.' ),
     592                                                                $handle,
     593                                                                'module_dependencies',
     594                                                                implode( wp_get_list_item_separator(), $missing_module_dependencies )
     595                                                        ),
     596                                                        '7.0.0'
     597                                                );
     598                                        }
     599                                }
     600
     601                                foreach ( $wp_scripts->registered[ $handle ]->deps as $dep ) {
     602                                        if ( ! isset( $processed[ $dep ] ) ) {
     603                                                $handles[] = $dep;
     604                                        }
     605                                }
     606                        }
     607                }
     608
     609                // Note: the script modules in $this->queue are not included in the importmap because they get printed as scripts.
     610                $ids = array_unique(
     611                        array_merge(
     612                                $classic_script_module_dependencies,
     613                                array_keys( $this->get_dependencies( array_merge( $this->queue, $classic_script_module_dependencies ) ) )
     614                        )
     615                );
     616                foreach ( $ids as $id ) {
    543617                        $src = $this->get_src( $id );
    544618                        if ( '' !== $src ) {
  • trunk/src/wp-includes/class-wp-scripts.php

    r61542 r61587  
    921921                                return false;
    922922                        }
     923                } elseif ( 'module_dependencies' === $key ) {
     924                        if ( ! is_array( $value ) ) {
     925                                _doing_it_wrong(
     926                                        __METHOD__,
     927                                        sprintf(
     928                                                /* translators: 1: 'module_dependencies', 2: Script handle. */
     929                                                __( 'The value for "%1$s" must be an array for the "%2$s" script.' ),
     930                                                'module_dependencies',
     931                                                $handle
     932                                        ),
     933                                        '7.0.0'
     934                                );
     935                                return false;
     936                        }
     937
     938                        $sanitized_value = array();
     939                        $has_invalid_ids = false;
     940                        foreach ( $value as $module ) {
     941                                if (
     942                                        is_string( $module ) ||
     943                                        ( is_array( $module ) && isset( $module['id'] ) && is_string( $module['id'] ) )
     944                                ) {
     945                                        $sanitized_value[] = $module;
     946                                } else {
     947                                        $has_invalid_ids = true;
     948                                }
     949                        }
     950
     951                        if ( $has_invalid_ids ) {
     952                                _doing_it_wrong(
     953                                        __METHOD__,
     954                                        sprintf(
     955                                                /* translators: 1: Script handle, 2: 'module_dependencies' */
     956                                                __( 'The script handle "%1$s" has one or more of its script module dependencies ("%2$s") which are invalid.' ),
     957                                                $handle,
     958                                                'module_dependencies'
     959                                        ),
     960                                        '7.0.0'
     961                                );
     962                        }
     963
     964                        $value = $sanitized_value;
    923965                }
    924966                return parent::add_data( $handle, $key, $value );
  • trunk/src/wp-includes/functions.wp-scripts.php

    r61402 r61587  
    6767                '3.3.0'
    6868        );
     69}
     70
     71/**
     72 * Adds the data for the recognized args and warns for unrecognized args.
     73 *
     74 * @ignore
     75 * @since 7.0.0
     76 *
     77 * @param WP_Scripts $wp_scripts WP_Scripts instance.
     78 * @param string     $handle     Script handle.
     79 * @param array      $args       Array of extra args for the script.
     80 */
     81function _wp_scripts_add_args_data( WP_Scripts $wp_scripts, string $handle, array $args ) {
     82        $allowed_keys = array( 'strategy', 'in_footer', 'fetchpriority', 'module_dependencies' );
     83        $unknown_keys = array_diff( array_keys( $args ), $allowed_keys );
     84        if ( ! empty( $unknown_keys ) ) {
     85                $trace         = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 );
     86                $function_name = ( $trace[1]['class'] ?? '' ) . ( $trace[1]['type'] ?? '' ) . $trace[1]['function'];
     87                _doing_it_wrong(
     88                        $function_name,
     89                        sprintf(
     90                                /* translators: 1: $args, 2: List of unrecognized keys, 3: List of supported keys. */
     91                                __( 'Unrecognized key(s) in the %1$s param: %2$s. Supported keys: %3$s' ),
     92                                '$args',
     93                                implode( wp_get_list_item_separator(), $unknown_keys ),
     94                                implode( wp_get_list_item_separator(), $allowed_keys )
     95                        ),
     96                        '7.0.0'
     97                );
     98        }
     99
     100        if ( ! empty( $args['in_footer'] ) ) {
     101                $wp_scripts->add_data( $handle, 'group', 1 );
     102        }
     103        if ( ! empty( $args['strategy'] ) ) {
     104                $wp_scripts->add_data( $handle, 'strategy', $args['strategy'] );
     105        }
     106        if ( ! empty( $args['fetchpriority'] ) ) {
     107                $wp_scripts->add_data( $handle, 'fetchpriority', $args['fetchpriority'] );
     108        }
     109        if ( ! empty( $args['module_dependencies'] ) ) {
     110                $wp_scripts->add_data( $handle, 'module_dependencies', $args['module_dependencies'] );
     111        }
    69112}
    70113
     
    160203 * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
    161204 * @since 6.9.0 The $fetchpriority parameter of type string was added to the $args parameter of type array.
    162  *
    163  * @param string                          $handle Name of the script. Should be unique.
    164  * @param string|false                    $src    Full URL of the script, or path of the script relative to the WordPress root directory.
    165  *                                                If source is set to false, script is an alias of other scripts it depends on.
    166  * @param string[]                        $deps   Optional. An array of registered script handles this script depends on. Default empty array.
    167  * @param string|bool|null                $ver    Optional. String specifying script version number, if it has one, which is added to the URL
    168  *                                                as a query string for cache busting purposes. If version is set to false, a version
    169  *                                                number is automatically added equal to current installed WordPress version.
    170  *                                                If set to null, no version is added.
    171  * @param array<string, string|bool>|bool $args   {
    172  *     Optional. An array of additional script loading strategies. Default empty array.
     205 * @since 7.0.0 The $module_dependencies parameter of type string[] was added to the $args parameter of type array.
     206 *
     207 * @param string                                                              $handle Name of the script. Should be unique.
     208 * @param string|false                                                        $src    Full URL of the script, or path of the script relative to the WordPress root directory.
     209 *                                                                                    If source is set to false, script is an alias of other scripts it depends on.
     210 * @param string[]                                                            $deps   Optional. An array of registered script handles this script depends on. Default empty array.
     211 * @param string|bool|null                                                    $ver    Optional. String specifying script version number, if it has one, which is added to the URL
     212 *                                                                                    as a query string for cache busting purposes. If version is set to false, a version
     213 *                                                                                    number is automatically added equal to current installed WordPress version.
     214 *                                                                                    If set to null, no version is added.
     215 * @param array<string, string|bool|array<string|array<string, string>>>|bool $args   {
     216 *     Optional. An array of extra args for the script. Default empty array.
    173217 *     Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
    174218 *
    175  *     @type string    $strategy      Optional. If provided, may be either 'defer' or 'async'.
    176  *     @type bool      $in_footer     Optional. Whether to print the script in the footer. Default 'false'.
    177  *     @type string    $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
     219 *     @type string                              $strategy            Optional. If provided, may be either 'defer' or 'async'.
     220 *     @type bool                                $in_footer           Optional. Whether to print the script in the footer. Default 'false'.
     221 *     @type string                              $fetchpriority       Optional. The fetch priority for the script. Default 'auto'.
     222 *     @type array<string|array<string, string>> $module_dependencies Optional. IDs for module dependencies loaded via dynamic import. Default empty array.
     223 *                                                                    For the full data format, see the `$deps` param of {@see wp_register_script_module()}.
    178224 * }
    179225 * @return bool Whether the script has been registered. True on success, false on failure.
     
    190236
    191237        $registered = $wp_scripts->add( $handle, $src, $deps, $ver );
    192         if ( ! empty( $args['in_footer'] ) ) {
    193                 $wp_scripts->add_data( $handle, 'group', 1 );
    194         }
    195         if ( ! empty( $args['strategy'] ) ) {
    196                 $wp_scripts->add_data( $handle, 'strategy', $args['strategy'] );
    197         }
    198         if ( ! empty( $args['fetchpriority'] ) ) {
    199                 $wp_scripts->add_data( $handle, 'fetchpriority', $args['fetchpriority'] );
    200         }
     238        _wp_scripts_add_args_data( $wp_scripts, $handle, $args );
     239
    201240        return $registered;
    202241}
     
    346385 * @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
    347386 * @since 6.9.0 The $fetchpriority parameter of type string was added to the $args parameter of type array.
    348  *
    349  * @param string                          $handle Name of the script. Should be unique.
    350  * @param string                          $src    Full URL of the script, or path of the script relative to the WordPress root directory.
    351  *                                                Default empty.
    352  * @param string[]                        $deps   Optional. An array of registered script handles this script depends on. Default empty array.
    353  * @param string|bool|null                $ver    Optional. String specifying script version number, if it has one, which is added to the URL
    354  *                                                as a query string for cache busting purposes. If version is set to false, a version
    355  *                                                number is automatically added equal to current installed WordPress version.
    356  *                                                If set to null, no version is added.
    357  * @param array<string, string|bool>|bool $args   {
    358  *     Optional. An array of additional script loading strategies. Default empty array.
     387 * @since 7.0.0 The $module_dependencies parameter of type string[] was added to the $args parameter of type array.
     388 *
     389 * @param string                                                              $handle Name of the script. Should be unique.
     390 * @param string                                                              $src    Full URL of the script, or path of the script relative to the WordPress root directory.
     391 *                                                                                    Default empty.
     392 * @param string[]                                                            $deps   Optional. An array of registered script handles this script depends on. Default empty array.
     393 * @param string|bool|null                                                    $ver    Optional. String specifying script version number, if it has one, which is added to the URL
     394 *                                                                                    as a query string for cache busting purposes. If version is set to false, a version
     395 *                                                                                    number is automatically added equal to current installed WordPress version.
     396 *                                                                                    If set to null, no version is added.
     397 * @param array<string, string|bool|array<string|array<string, string>>>|bool $args {
     398 *     Optional. An array of extra args for the script. Default empty array.
    359399 *     Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
    360400 *
    361  *     @type string    $strategy      Optional. If provided, may be either 'defer' or 'async'.
    362  *     @type bool      $in_footer     Optional. Whether to print the script in the footer. Default 'false'.
    363  *     @type string    $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
     401 *     @type string                              $strategy            Optional. If provided, may be either 'defer' or 'async'.
     402 *     @type bool                                $in_footer           Optional. Whether to print the script in the footer. Default 'false'.
     403 *     @type string                              $fetchpriority       Optional. The fetch priority for the script. Default 'auto'.
     404 *     @type array<string|array<string, string>> $module_dependencies Optional. IDs for module dependencies loaded via dynamic import. Default empty array.
     405 *                                                                    For the full data format, see the `$deps` param of {@see wp_register_script_module()}.
    364406 * }
    365407 */
     
    380422                        $wp_scripts->add( $_handle[0], $src, $deps, $ver );
    381423                }
    382                 if ( ! empty( $args['in_footer'] ) ) {
    383                         $wp_scripts->add_data( $_handle[0], 'group', 1 );
    384                 }
    385                 if ( ! empty( $args['strategy'] ) ) {
    386                         $wp_scripts->add_data( $_handle[0], 'strategy', $args['strategy'] );
    387                 }
    388                 if ( ! empty( $args['fetchpriority'] ) ) {
    389                         $wp_scripts->add_data( $_handle[0], 'fetchpriority', $args['fetchpriority'] );
     424                if ( ! empty( $args ) ) {
     425                        _wp_scripts_add_args_data( $wp_scripts, $_handle[0], $args );
    390426                }
    391427        }
  • trunk/src/wp-includes/script-modules.php

    r61492 r61587  
    3838 * @since 6.9.0 Added the $args parameter.
    3939 *
    40  * @param string                     $id      The identifier of the script module. Should be unique. It will be used in the
    41  *                                            final import map.
    42  * @param string                     $src     Optional. Full URL of the script module, or path of the script module relative
    43  *                                            to the WordPress root directory. If it is provided and the script module has
    44  *                                            not been registered yet, it will be registered.
    45  * @param array<string|array>        $deps    {
    46  *                                                Optional. List of dependencies.
    47  *
    48  *                                                @type string|array ...$0 {
    49  *                                                    An array of script module identifiers of the dependencies of this script
    50  *                                                    module. The dependencies can be strings or arrays. If they are arrays,
    51  *                                                    they need an `id` key with the script module identifier, and can contain
    52  *                                                    an `import` key with either `static` or `dynamic`. By default,
    53  *                                                    dependencies that don't contain an `import` key are considered static.
    54  *
    55  *                                                    @type string $id     The script module identifier.
    56  *                                                    @type string $import Optional. Import type. May be either `static` or
    57  *                                                                         `dynamic`. Defaults to `static`.
    58  *                                                }
    59  *                                            }
    60  * @param string|false|null          $version Optional. String specifying the script module version number. Defaults to false.
    61  *                                            It is added to the URL as a query string for cache busting purposes. If $version
    62  *                                            is set to false, the version number is the currently installed WordPress version.
    63  *                                            If $version is set to null, no version is added.
    64  * @param array<string, string|bool> $args    {
     40 * @param string                              $id      The identifier of the script module. Should be unique. It will be used in the
     41 *                                                     final import map.
     42 * @param string                              $src     Optional. Full URL of the script module, or path of the script module relative
     43 *                                                     to the WordPress root directory. If it is provided and the script module has
     44 *                                                     not been registered yet, it will be registered.
     45 * @param array<string|array<string, string>> $deps    {
     46 *                                                         Optional. List of dependencies.
     47 *
     48 *                                                         @type string|array<string, string> ...$0 {
     49 *                                                             An array of script module identifiers of the dependencies of this script
     50 *                                                             module. The dependencies can be strings or arrays. If they are arrays,
     51 *                                                             they need an `id` key with the script module identifier, and can contain
     52 *                                                             an `import` key with either `static` or `dynamic`. By default,
     53 *                                                             dependencies that don't contain an `import` key are considered static.
     54 *
     55 *                                                             @type string $id     The script module identifier.
     56 *                                                             @type string $import Optional. Import type. May be either `static` or
     57 *                                                                                  `dynamic`. Defaults to `static`.
     58 *                                                         }
     59 *                                                     }
     60 * @param string|false|null                   $version Optional. String specifying the script module version number. Defaults to false.
     61 *                                                     It is added to the URL as a query string for cache busting purposes. If $version
     62 *                                                     is set to false, the version number is the currently installed WordPress version.
     63 *                                                     If $version is set to null, no version is added.
     64 * @param array<string, string|bool>          $args    {
    6565 *     Optional. An array of additional args. Default empty array.
    6666 *
     
    8282 * @since 6.9.0 Added the $args parameter.
    8383 *
    84  * @param string                     $id      The identifier of the script module. Should be unique. It will be used in the
    85  *                                            final import map.
    86  * @param string                     $src     Optional. Full URL of the script module, or path of the script module relative
    87  *                                            to the WordPress root directory. If it is provided and the script module has
    88  *                                            not been registered yet, it will be registered.
    89  * @param array<string|array>        $deps    {
    90  *                                                Optional. List of dependencies.
    91  *
    92  *                                                @type string|array ...$0 {
    93  *                                                    An array of script module identifiers of the dependencies of this script
    94  *                                                    module. The dependencies can be strings or arrays. If they are arrays,
    95  *                                                    they need an `id` key with the script module identifier, and can contain
    96  *                                                    an `import` key with either `static` or `dynamic`. By default,
    97  *                                                    dependencies that don't contain an `import` key are considered static.
    98  *
    99  *                                                    @type string $id     The script module identifier.
    100  *                                                    @type string $import Optional. Import type. May be either `static` or
    101  *                                                                         `dynamic`. Defaults to `static`.
    102  *                                                }
    103  *                                            }
    104  * @param string|false|null          $version Optional. String specifying the script module version number. Defaults to false.
    105  *                                            It is added to the URL as a query string for cache busting purposes. If $version
    106  *                                            is set to false, the version number is the currently installed WordPress version.
    107  *                                            If $version is set to null, no version is added.
    108  * @param array<string, string|bool> $args    {
     84 * @param string                              $id      The identifier of the script module. Should be unique. It will be used in the
     85 *                                                     final import map.
     86 * @param string                              $src     Optional. Full URL of the script module, or path of the script module relative
     87 *                                                     to the WordPress root directory. If it is provided and the script module has
     88 *                                                     not been registered yet, it will be registered.
     89 * @param array<string|array<string, string>> $deps    {
     90 *                                                         Optional. List of dependencies.
     91 *
     92 *                                                         @type string|array<string, string> ...$0 {
     93 *                                                             An array of script module identifiers of the dependencies of this script
     94 *                                                             module. The dependencies can be strings or arrays. If they are arrays,
     95 *                                                             they need an `id` key with the script module identifier, and can contain
     96 *                                                             an `import` key with either `static` or `dynamic`. By default,
     97 *                                                             dependencies that don't contain an `import` key are considered static.
     98 *
     99 *                                                             @type string $id     The script module identifier.
     100 *                                                             @type string $import Optional. Import type. May be either `static` or
     101 *                                                                                  `dynamic`. Defaults to `static`.
     102 *                                                         }
     103 *                                                     }
     104 * @param string|false|null                   $version Optional. String specifying the script module version number. Defaults to false.
     105 *                                                     It is added to the URL as a query string for cache busting purposes. If $version
     106 *                                                     is set to false, the version number is the currently installed WordPress version.
     107 *                                                     If $version is set to null, no version is added.
     108 * @param array<string, string|bool>          $args    {
    109109 *     Optional. An array of additional args. Default empty array.
    110110 *
  • trunk/tests/phpunit/tests/dependencies/scripts.php

    r61539 r61587  
    107107
    108108        /**
     109         * Tests that scripts trigger _doing_it_wrong for unrecognized keys in the $args array.
     110         *
     111         * @ticket 63486
     112         *
     113         * @covers ::wp_register_script
     114         * @covers ::wp_enqueue_script
     115         * @covers ::_wp_scripts_add_args_data
     116         *
     117         * @dataProvider data_unrecognized_keys_in_args
     118         *
     119         * @param string $function_name Function name to call.
     120         * @param array  $args          Arguments to pass to the function.
     121         * @param string $expected_msg  Expected error message substring.
     122         */
     123        public function test_unrecognized_keys_in_args( string $function_name, array $args, string $expected_msg ) {
     124                $this->setExpectedIncorrectUsage( $function_name );
     125
     126                call_user_func_array( $function_name, $args );
     127
     128                $this->assertStringContainsString(
     129                        $expected_msg,
     130                        $this->caught_doing_it_wrong[ $function_name ]
     131                );
     132        }
     133
     134        /**
     135         * Data provider for test_unrecognized_keys_in_args.
     136         *
     137         * @return array<string, array{function_name: string, args: array, expected_msg: string}>
     138         */
     139        public function data_unrecognized_keys_in_args(): array {
     140                return array(
     141                        'register_script' => array(
     142                                'function_name' => 'wp_register_script',
     143                                'args'          => array(
     144                                        'unrecognized-key-register',
     145                                        '/script.js',
     146                                        array(),
     147                                        null,
     148                                        array(
     149                                                'unrecognized_key' => 'value',
     150                                                'another_bad_key'  => 'value',
     151                                        ),
     152                                ),
     153                                'expected_msg'  => 'Unrecognized key(s) in the $args param: unrecognized_key, another_bad_key. Supported keys: strategy, in_footer, fetchpriority, module_dependencies',
     154                        ),
     155                        'enqueue_script'  => array(
     156                                'function_name' => 'wp_enqueue_script',
     157                                'args'          => array(
     158                                        'unrecognized-key-enqueue',
     159                                        '/script.js',
     160                                        array(),
     161                                        null,
     162                                        array(
     163                                                'strategy'            => 'defer',
     164                                                'in_footer'           => true,
     165                                                'fetchpriority'       => 'high',
     166                                                'module_dependencies' => array( 'foo' ),
     167                                                'invalid_key'         => 'bar',
     168                                        ),
     169                                ),
     170                                'expected_msg'  => 'Unrecognized key(s) in the $args param: invalid_key. Supported keys: strategy, in_footer, fetchpriority, module_dependencies',
     171                        ),
     172                );
     173        }
     174
     175        /**
    109176         * Test versioning
    110177         *
     
    12761343                wp_register_script( 'alias', false, array(), null, array( 'fetchpriority' => 'low' ) );
    12771344                $this->assertArrayNotHasKey( 'fetchpriority', wp_scripts()->registered['alias']->extra );
     1345        }
     1346
     1347        /**
     1348         * Tests validation of module_dependencies in WP_Scripts::add_data().
     1349         *
     1350         * @ticket 61500
     1351         *
     1352         * @covers WP_Scripts::add_data
     1353         *
     1354         * @dataProvider data_add_data_module_dependencies_validation
     1355         *
     1356         * @param mixed      $data     Data to add.
     1357         * @param string     $message  Expected error message.
     1358         * @param bool       $expected Expected return value.
     1359         * @param array|null $stored   Expected stored value.
     1360         */
     1361        public function test_add_data_module_dependencies_validation( $data, string $message, bool $expected, ?array $stored ) {
     1362                wp_register_script( 'test-script', '/test.js' );
     1363
     1364                $expected_incorrect_usage = 'WP_Scripts::add_data';
     1365                $this->setExpectedIncorrectUsage( $expected_incorrect_usage );
     1366
     1367                $this->assertSame( $expected, wp_scripts()->add_data( 'test-script', 'module_dependencies', $data ) );
     1368                $this->assertStringContainsString( $message, $this->caught_doing_it_wrong[ $expected_incorrect_usage ] );
     1369
     1370                if ( null === $stored ) {
     1371                        $this->assertFalse( wp_scripts()->get_data( 'test-script', 'module_dependencies' ) );
     1372                } else {
     1373                        $this->assertSame( $stored, wp_scripts()->get_data( 'test-script', 'module_dependencies' ) );
     1374                }
     1375        }
     1376
     1377        /**
     1378         * Data provider.
     1379         *
     1380         * @return array<string, array{data: mixed, message: string, expected: bool, stored: array<string|array<string, string>>|null}>
     1381         */
     1382        public function data_add_data_module_dependencies_validation(): array {
     1383                return array(
     1384                        'non-array' => array(
     1385                                'data'     => 'not-an-array',
     1386                                'message'  => 'The value for "module_dependencies" must be an array',
     1387                                'expected' => false,
     1388                                'stored'   => null,
     1389                        ),
     1390                        'bad-items' => array(
     1391                                'data'     => array( 'valid', 123, true, array(), array( 'id' => 'valid2' ) ),
     1392                                'message'  => 'has one or more of its script module dependencies ("module_dependencies") which are invalid',
     1393                                'expected' => true,
     1394                                'stored'   => array( 'valid', array( 'id' => 'valid2' ) ),
     1395                        ),
     1396                );
    12781397        }
    12791398
  • trunk/tests/phpunit/tests/script-modules/wpScriptModules.php

    r61519 r61587  
    1212class Tests_Script_Modules_WpScriptModules extends WP_UnitTestCase {
    1313
    14         /**
    15          * @var WP_Script_Modules
    16          */
    17         protected $original_script_modules;
    18 
    19         /**
    20          * @var string
    21          */
    22         protected $original_wp_version;
    23 
    24         /**
    25          * Instance of WP_Script_Modules.
    26          *
    27          * @var WP_Script_Modules
    28          */
    29         protected $script_modules;
     14        protected WP_Script_Modules $original_script_modules;
     15
     16        protected string $original_wp_version;
     17
     18        protected ?WP_Scripts $original_wp_scripts;
     19
     20        protected WP_Script_Modules $script_modules;
    3021
    3122        /**
     
    3324         */
    3425        public function set_up() {
    35                 global $wp_script_modules, $wp_version;
     26                global $wp_script_modules, $wp_scripts, $wp_version;
    3627                parent::set_up();
    3728                $this->original_script_modules = $wp_script_modules;
    3829                $this->original_wp_version     = $wp_version;
     30                $this->original_wp_scripts     = $wp_scripts ?? null;
    3931                $wp_script_modules             = null;
    4032                $this->script_modules          = wp_script_modules();
     33
     34                $wp_scripts                  = new WP_Scripts();
     35                $wp_scripts->default_version = get_bloginfo( 'version' );
    4136        }
    4237
     
    4540         */
    4641        public function tear_down() {
    47                 global $wp_script_modules, $wp_version;
    4842                parent::tear_down();
     43                global $wp_script_modules, $wp_scripts, $wp_version;
    4944                $wp_script_modules = $this->original_script_modules;
    5045                $wp_version        = $this->original_wp_version;
     46                $wp_scripts        = $this->original_wp_scripts;
    5147        }
    5248
     
    19871983
    19881984        /**
     1985         * Tests that script modules identified as dependencies of classic scripts are included in the import map.
     1986         *
     1987         * @ticket 61500
     1988         *
     1989         * @covers WP_Script_Modules::get_import_map
     1990         */
     1991        public function test_included_module_appears_in_importmap() {
     1992                $this->script_modules->register( 'dependency', '/dep.js' );
     1993                $this->script_modules->register( 'example', '/example.js', array( 'dependency' ) );
     1994                $this->script_modules->register( 'example2', '/example2.js' );
     1995
     1996                // Nothing printed now.
     1997                $this->assertSame( array(), $this->get_enqueued_script_modules(), 'Initial enqueued script modules was wrong.' );
     1998                $this->assertSame( array(), $this->get_preloaded_script_modules(), 'Initial module preloads was wrong.' );
     1999                $this->assertSame( array(), $this->get_import_map(), 'Initial import map was wrong.' );
     2000
     2001                // Enqueuing a script with a module dependency should add it to the import map.
     2002                wp_enqueue_script(
     2003                        'classic',
     2004                        '/classic.js',
     2005                        array( 'classic-dependency' ),
     2006                        false,
     2007                        array(
     2008                                'module_dependencies' => array(
     2009                                        'example',
     2010                                        array(
     2011                                                'id' => 'example2',
     2012                                        ),
     2013                                ),
     2014                        )
     2015                );
     2016
     2017                $this->assertSame( array(), $this->get_enqueued_script_modules(), 'Final enqueued script modules was wrong.' );
     2018                $this->assertSame( array(), $this->get_preloaded_script_modules(), 'Final module preloads was wrong.' );
     2019                $this->assertEqualSets(
     2020                        array( 'example', 'example2', 'dependency' ),
     2021                        array_keys( $this->get_import_map() ),
     2022                        'Import map keys were wrong.'
     2023                );
     2024        }
     2025
     2026        /**
     2027         * Tests that dynamic dependencies of enqueued script modules are included in the import map.
     2028         *
     2029         * @ticket 61500
     2030         *
     2031         * @covers WP_Script_Modules::get_import_map
     2032         */
     2033        public function test_import_map_includes_dynamic_dependencies_of_enqueued_modules() {
     2034                $this->script_modules->register( 'dependency-of-enqueued', '/dependency-of-enqueued.js' );
     2035                $this->script_modules->enqueue(
     2036                        'enqueued',
     2037                        '/enqueued.js',
     2038                        array(
     2039                                array(
     2040                                        'id'     => 'dependency-of-enqueued',
     2041                                        'import' => 'dynamic',
     2042                                ),
     2043                        )
     2044                );
     2045
     2046                $enqueued = $this->get_enqueued_script_modules();
     2047                $this->assertCount( 1, $enqueued, 'Enqueue count was wrong.' );
     2048                $this->assertArrayHasKey( 'enqueued', $enqueued, 'Missing "enqueued" script module enqueue.' );
     2049                $this->assertCount( 0, $this->get_preloaded_script_modules(), 'Module preload count was wrong.' );
     2050                $this->assertEqualSets(
     2051                        array( 'dependency-of-enqueued' ),
     2052                        array_keys( $this->get_import_map() ),
     2053                        'Import map keys were wrong.'
     2054                );
     2055        }
     2056
     2057        /**
     2058         * Tests that script module dependencies of enqueued classic scripts (including transitive ones) are included in the import map.
     2059         *
     2060         * @ticket 61500
     2061         *
     2062         * @covers WP_Script_Modules::get_import_map
     2063         */
     2064        public function test_import_map_includes_dependencies_of_classic_scripts_recursive() {
     2065                $this->script_modules->register( 'classic-transitive-dependency', '/classic-transitive-dependency.js' );
     2066                $this->script_modules->register( 'dependency-of-not-enqueued', '/dependency-of-not-enqueued.js' );
     2067                $this->script_modules->register( 'not-enqueued', '/not-enqueued.js', array( 'dependency-of-not-enqueued' ) );
     2068
     2069                // Enqueuing a script with a module dependency should add it to the import map.
     2070                wp_register_script(
     2071                        'classic-transitive-dep',
     2072                        '/classic-transitive-dep.js',
     2073                        array(),
     2074                        false,
     2075                        array(
     2076                                'module_dependencies' => array( 'classic-transitive-dependency' ),
     2077                        )
     2078                );
     2079                wp_enqueue_script(
     2080                        'classic',
     2081                        '/classic.js',
     2082                        array( 'classic-transitive-dep' ),
     2083                        false,
     2084                        array(
     2085                                'module_dependencies' => array( 'not-enqueued' ),
     2086                        )
     2087                );
     2088
     2089                $enqueued = $this->get_enqueued_script_modules();
     2090                $this->assertCount( 0, $enqueued, 'Enqueue count was wrong.' );
     2091                $this->assertCount( 0, $this->get_preloaded_script_modules(), 'Module preload count was wrong.' );
     2092                $this->assertEqualSets(
     2093                        array(
     2094                                'classic-transitive-dependency',
     2095                                'not-enqueued',
     2096                                'dependency-of-not-enqueued',
     2097                        ),
     2098                        array_keys( $this->get_import_map() ),
     2099                        'Import map keys were wrong.'
     2100                );
     2101        }
     2102
     2103        /**
     2104         * Tests that WP_Scripts emits a _doing_it_wrong() notice for missing script module dependencies.
     2105         *
     2106         * @ticket 61500
     2107         * @ticket 64229
     2108         * @covers WP_Script_Modules::get_import_map
     2109         */
     2110        public function test_wp_scripts_doing_it_wrong_for_missing_script_module_dependencies() {
     2111                $expected_incorrect_usage = 'WP_Scripts::add_data';
     2112                $this->setExpectedIncorrectUsage( $expected_incorrect_usage );
     2113
     2114                wp_enqueue_script(
     2115                        'registered-dep',
     2116                        '/registered-dep.js',
     2117                        array(),
     2118                        null,
     2119                        array(
     2120                                'module_dependencies' => array( 'does-not-exist' ),
     2121                        )
     2122                );
     2123
     2124                $import_map = $this->get_import_map();
     2125                $this->assertSame( array(), $import_map, 'Expected importmap to be empty.' );
     2126                $markup = get_echo( 'wp_print_scripts' );
     2127
     2128                /*
     2129                 * In the future, we may want to have missing script module dependencies for classic scripts to cause the
     2130                 * classic script to not be printed. This would align the behavior with script modules that have missing
     2131                 * script module dependencies, and classic scripts that have missing classic script dependencies. Nevertheless,
     2132                 * since script module dependencies rely on dynamic imports, the dependency may not be as strong. This means
     2133                 * the classic script may still work or have a fallback in case the script module fails to dynamically import.
     2134                 * This same change could be made for script modules as well, where if a script module has a missing dynamic
     2135                 * script module dependency, this might similarly not be sufficient reason to omit printing the dependent script module.
     2136                 */
     2137                $this->assertStringContainsString( 'registered-dep.js', $markup, 'Expected script to be present, even though it has a missing script module dependency.' );
     2138
     2139                $this->assertArrayHasKey(
     2140                        $expected_incorrect_usage,
     2141                        $this->caught_doing_it_wrong,
     2142                        "Expected $expected_incorrect_usage to trigger a _doing_it_wrong() notice for missing dependency."
     2143                );
     2144
     2145                $this->assertStringContainsString(
     2146                        'The script with the handle "registered-dep" was enqueued with script module dependencies ("module_dependencies") that are not registered: does-not-exist',
     2147                        $this->caught_doing_it_wrong[ $expected_incorrect_usage ],
     2148                        'Expected _doing_it_wrong() notice to indicate missing script module dependencies for enqueued script.'
     2149                );
     2150        }
     2151
     2152        /**
    19892153         * Tests various ways of printing and dependency ordering of script modules.
    19902154         *
Note: See TracChangeset for help on using the changeset viewer.