Make WordPress Core

Changeset 61587


Ignore:
Timestamp:
02/04/2026 01:41:25 AM (2 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.