Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
39a11a8
Add "expose" logic to script modules
sirreal Oct 8, 2024
65fd1df
Make get_src return nullable
sirreal Oct 8, 2024
843e1e0
Rework, rename, add tests
sirreal Dec 16, 2024
2e180cf
Remove uniqueness checks
sirreal Dec 17, 2024
7fc42d2
Replace excessively prescriptive comment
sirreal Dec 17, 2024
5144a90
Add test for correct merge with regular dependencies
sirreal Dec 17, 2024
d83ea4b
Fix overwriting array keys
sirreal Dec 17, 2024
9125683
Working proposal with caveats
sirreal Dec 19, 2024
5ddc2dd
Revert preveious approach at module dependencies
sirreal Dec 19, 2024
9e976bb
Inspect wp_scripts and extract module deps from scripts on the page
sirreal Dec 19, 2024
591e3b5
Fix regression printing enqueued modules in importmap
sirreal Dec 19, 2024
d1d8c43
Check for WP_Scripts instance
sirreal Dec 19, 2024
10c3a02
Fix possible unset module id in script modules
sirreal Dec 19, 2024
a444ffa
Fix lint
sirreal Dec 19, 2024
b0f579b
Update and improve tests with classic scripts
sirreal Dec 19, 2024
8366f45
Improve variable name and array push of many values
sirreal Dec 19, 2024
45cacbb
Remove a loop from importmap checking
sirreal Dec 19, 2024
14ae089
Merge branch 'trunk' into scripts/allow-script-module-dependency
sirreal Apr 3, 2025
6122621
Merge trunk into scripts/allow-script-module-dependency
westonruter Jan 29, 2026
b77dbc7
Remove merge conflict markers
westonruter Jan 29, 2026
1d12174
Fix test_included_module_appears_in_importmap
westonruter Jan 29, 2026
64e67b8
Fix test_included_modules_concat_with_enqueued_dependencies
westonruter Jan 29, 2026
fda5f17
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Jan 29, 2026
f2f72a6
Pass module dependencies via args insead of deps
westonruter Jan 29, 2026
d926e8f
Ensure module_dependencies is an array before iterating over
westonruter Jan 29, 2026
1a1df99
Optimize classic script module dependency resolution
westonruter Jan 30, 2026
6b56f5f
Improve variable naming, add comment, and add phpdoc
westonruter Jan 30, 2026
cf8f813
Restore WP_Script_Modules::get_src() only returning string
westonruter Jan 30, 2026
34867df
Warn when script is enqueued with missing script module dependency
westonruter Jan 30, 2026
babe954
Use array_pop() instead of array_shift() to avoid re-indexing
westonruter Jan 30, 2026
b450826
Validate module_dependencies in WP_Scripts::add_data()
westonruter Jan 30, 2026
269c4c7
Validate extra args in wp_register_script() and wp_enqueue_script()
westonruter Jan 30, 2026
3c2c84b
Refactor wp_register_script() and wp_enqueue_script() to avoid code d…
westonruter Jan 30, 2026
354f42d
Standardize wp_scripts global handling in script module tests
westonruter Jan 30, 2026
ae642f4
Use PHP 7.4 class member types
westonruter Jan 30, 2026
8a782ac
Fix variable alignment
westonruter Jan 30, 2026
6a3141a
Use globals consistently
westonruter Jan 30, 2026
07ab8d6
Add descriptions to script module dependency tests
westonruter Jan 30, 2026
907cb0d
Refactor and simplify script module dependency tests
westonruter Jan 30, 2026
23e2a33
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Jan 30, 2026
7f53642
Add comment explaining why a classic script with a missing dynamic sc…
westonruter Jan 30, 2026
7ddcdd7
Improve phrasing
westonruter Jan 31, 2026
0cb95e0
Improve param default value description
westonruter Jan 31, 2026
ce5124f
Avoid calling _wp_scripts_add_args_data() with empty args
westonruter Jan 31, 2026
083287a
Use WP_Scripts::add_data() instead of WP_Scripts::add() for _doing_it…
westonruter Jan 31, 2026
9acd7f0
Update test_wp_scripts_doing_it_wrong_for_missing_script_module_depen…
westonruter Jan 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion src/wp-includes/class-wp-script-modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -533,13 +533,77 @@ public function print_import_map() {
* Returns the import map array.
*
* @since 6.5.0
* @since 7.0.0 Script module dependencies ('module_dependencies') of classic scripts are now included.
*
* @global WP_Scripts $wp_scripts
*
* @return array<string, array<string, string>> Array with an `imports` key mapping to an array of script module
* identifiers and their respective URLs, including the version query.
*/
private function get_import_map(): array {
global $wp_scripts;

$imports = array();
foreach ( array_keys( $this->get_dependencies( $this->queue ) ) as $id ) {

// Identify script modules that are dependencies of classic scripts.
$classic_script_module_dependencies = array();
if ( $wp_scripts instanceof WP_Scripts ) {
$handles = array_merge(
$wp_scripts->queue,
$wp_scripts->to_do,
$wp_scripts->done
);

$processed = array();
while ( ! empty( $handles ) ) {
$handle = array_pop( $handles );
if ( isset( $processed[ $handle ] ) || ! isset( $wp_scripts->registered[ $handle ] ) ) {
continue;
}
$processed[ $handle ] = true;

$module_dependencies = $wp_scripts->get_data( $handle, 'module_dependencies' );
if ( is_array( $module_dependencies ) ) {
$missing_module_dependencies = array();
foreach ( $module_dependencies as $id ) {
if ( ! isset( $this->registered[ $id ] ) ) {
$missing_module_dependencies[] = $id;
} else {
$classic_script_module_dependencies[] = $id;
}
}

if ( count( $missing_module_dependencies ) > 0 ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a good place for !empty(). I know that function is avoided sometimes, but this is an array and it seems appropriate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with count() because it is being used class-wp-dependencies.php:

} elseif ( count( $missing_dependencies ) > 0 ) {
if ( ! in_array( $handle, $this->dependencies_with_missing_dependencies, true ) ) {
_doing_it_wrong(
get_class( $this ) . '::add',
$this->get_dependency_warning_message( $handle, $missing_dependencies ),
'6.9.1'
);
$this->dependencies_with_missing_dependencies[] = $handle;
}

Personally I prefer to not use empty() when possible because if it so happens that you have a typo in the variable name, the mistake goes silently under the radar.

There's also disallowedEmpty in PHPStan strict rules:

Disallow empty() - it's a very loose comparison (see manual), it's recommended to use more strict one.

We eliminated empty() in Performance Lab: WordPress/performance#1219

_doing_it_wrong(
'WP_Scripts::add_data',
sprintf(
/* translators: 1: Script handle, 2: 'module_dependencies', 3: List of missing dependency IDs. */
__( 'The script with the handle "%1$s" was enqueued with script module dependencies ("%2$s") that are not registered: %3$s.' ),
$handle,
'module_dependencies',
implode( wp_get_list_item_separator(), $missing_module_dependencies )
),
'7.0.0'
);
}
}

foreach ( $wp_scripts->registered[ $handle ]->deps as $dep ) {
if ( ! isset( $processed[ $dep ] ) ) {
$handles[] = $dep;
}
}
}
}

// Note: the script modules in $this->queue are not included in the importmap because they get printed as scripts.
$ids = array_unique(
array_merge(
$classic_script_module_dependencies,
array_keys( $this->get_dependencies( array_merge( $this->queue, $classic_script_module_dependencies ) ) )
)
);
foreach ( $ids as $id ) {
$src = $this->get_src( $id );
if ( '' !== $src ) {
$imports[ $id ] = $src;
Expand Down
39 changes: 39 additions & 0 deletions src/wp-includes/class-wp-scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,45 @@ public function add_data( $handle, $key, $value ) {
);
return false;
}
} elseif ( 'module_dependencies' === $key ) {
if ( ! is_array( $value ) ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1: 'module_dependencies', 2: Script handle. */
__( 'The value for "%1$s" must be an array for the "%2$s" script.' ),
'module_dependencies',
$handle
),
'7.0.0'
);
return false;
}

$sanitized_value = array();
$has_invalid_ids = false;
foreach ( $value as $id ) {
if ( ! is_string( $id ) ) {
$has_invalid_ids = true;
} else {
$sanitized_value[] = $id;
}
}

if ( $has_invalid_ids ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1: Script handle, 2: 'module_dependencies' */
__( 'The script handle "%1$s" has one or more of its script module dependencies ("%2$s") which are not strings.' ),
$handle,
'module_dependencies'
),
'7.0.0'
);
}

$value = $sanitized_value;
}
return parent::add_data( $handle, $key, $value );
}
Expand Down
122 changes: 77 additions & 45 deletions src/wp-includes/functions.wp-scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,47 @@ function _wp_scripts_maybe_doing_it_wrong( $function_name, $handle = '' ) {
);
}

/**
* Adds the data for the recognized args and warns for unrecognized args.
*
* @ignore
* @since 7.0.0
*
* @param string $function_name Function name.
* @param WP_Scripts $wp_scripts WP_Scripts instance.
* @param string $handle Script handle.
* @param array $args Array of extra args for the script.
*/
function _wp_scripts_add_args_data( string $function_name, WP_Scripts $wp_scripts, string $handle, array $args ) {
$allowed_keys = array( 'strategy', 'in_footer', 'fetchpriority', 'module_dependencies' );
$unknown_keys = array_diff( array_keys( $args ), $allowed_keys );
if ( ! empty( $unknown_keys ) ) {
_doing_it_wrong(
$function_name,
sprintf(
/* translators: 1: $args, 2: List of unrecognized keys. */
__( 'Unrecognized keys in the %1$s array: %2$s.' ),
'$args',
implode( wp_get_list_item_separator(), $unknown_keys )
),
'7.0.0'
);
Comment on lines +86 to +95
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importantly, this will catch cases where someone “mispells” the arg module_dependencies, like moduel_dependencys. I went with the full “dependencies” in the name as opposed to module_deps because there is also WP_Dependencies. See also Naming Conventions to not abbreviate names unnecessarily.

}

if ( ! empty( $args['in_footer'] ) ) {
$wp_scripts->add_data( $handle, 'group', 1 );
}
if ( ! empty( $args['strategy'] ) ) {
$wp_scripts->add_data( $handle, 'strategy', $args['strategy'] );
}
if ( ! empty( $args['fetchpriority'] ) ) {
$wp_scripts->add_data( $handle, 'fetchpriority', $args['fetchpriority'] );
}
if ( ! empty( $args['module_dependencies'] ) ) {
$wp_scripts->add_data( $handle, 'module_dependencies', $args['module_dependencies'] );
}
}

/**
* Prints scripts in document head that are in the $handles queue.
*
Expand Down Expand Up @@ -159,22 +200,24 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) {
* @since 4.3.0 A return value was added.
* @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
* @since 6.9.0 The $fetchpriority parameter of type string was added to the $args parameter of type array.
*
* @param string $handle Name of the script. Should be unique.
* @param string|false $src Full URL of the script, or path of the script relative to the WordPress root directory.
* If source is set to false, script is an alias of other scripts it depends on.
* @param string[] $deps Optional. An array of registered script handles this script depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param array<string, string|bool>|bool $args {
* Optional. An array of additional script loading strategies. Default empty array.
* @since 7.0.0 The $module_dependencies parameter of type string[] was added to the $args parameter of type array.
*
* @param string $handle Name of the script. Should be unique.
* @param string|false $src Full URL of the script, or path of the script relative to the WordPress root directory.
* If source is set to false, script is an alias of other scripts it depends on.
* @param string[] $deps Optional. An array of registered script handles this script depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param array<string, string|bool|string[]>|bool $args {
* Optional. An array of extra args for the script. Default empty array.
* Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
*
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
* @type string $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
* @type string $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
* @type string[] $module_dependencies Optional. IDs for module dependencies loaded via dynamic import. Default empty array.
* }
* @return bool Whether the script has been registered. True on success, false on failure.
*/
Expand All @@ -189,15 +232,8 @@ function wp_register_script( $handle, $src, $deps = array(), $ver = false, $args
$wp_scripts = wp_scripts();

$registered = $wp_scripts->add( $handle, $src, $deps, $ver );
if ( ! empty( $args['in_footer'] ) ) {
$wp_scripts->add_data( $handle, 'group', 1 );
}
if ( ! empty( $args['strategy'] ) ) {
$wp_scripts->add_data( $handle, 'strategy', $args['strategy'] );
}
if ( ! empty( $args['fetchpriority'] ) ) {
$wp_scripts->add_data( $handle, 'fetchpriority', $args['fetchpriority'] );
}
_wp_scripts_add_args_data( __FUNCTION__, $wp_scripts, $handle, $args );

return $registered;
}

Expand Down Expand Up @@ -345,22 +381,24 @@ function wp_deregister_script( $handle ) {
* @since 2.1.0
* @since 6.3.0 The $in_footer parameter of type boolean was overloaded to be an $args parameter of type array.
* @since 6.9.0 The $fetchpriority parameter of type string was added to the $args parameter of type array.
*
* @param string $handle Name of the script. Should be unique.
* @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
* Default empty.
* @param string[] $deps Optional. An array of registered script handles this script depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param array<string, string|bool>|bool $args {
* Optional. An array of additional script loading strategies. Default empty array.
* @since 7.0.0 The $module_dependencies parameter of type string[] was added to the $args parameter of type array.
*
* @param string $handle Name of the script. Should be unique.
* @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
* Default empty.
* @param string[] $deps Optional. An array of registered script handles this script depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param array<string, string|bool|string[]>|bool $args {
* Optional. An array of extra args for the script. Default empty array.
* Otherwise, it may be a boolean in which case it determines whether the script is printed in the footer. Default false.
*
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
* @type string $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
* @type string $strategy Optional. If provided, may be either 'defer' or 'async'.
* @type bool $in_footer Optional. Whether to print the script in the footer. Default 'false'.
* @type string $fetchpriority Optional. The fetch priority for the script. Default 'auto'.
* @type string[] $module_dependencies Optional. IDs for module dependencies loaded via dynamic import. Default empty array.
* }
*/
function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $args = array() ) {
Expand All @@ -379,14 +417,8 @@ function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $
if ( $src ) {
$wp_scripts->add( $_handle[0], $src, $deps, $ver );
}
if ( ! empty( $args['in_footer'] ) ) {
$wp_scripts->add_data( $_handle[0], 'group', 1 );
}
if ( ! empty( $args['strategy'] ) ) {
$wp_scripts->add_data( $_handle[0], 'strategy', $args['strategy'] );
}
if ( ! empty( $args['fetchpriority'] ) ) {
$wp_scripts->add_data( $_handle[0], 'fetchpriority', $args['fetchpriority'] );
if ( ! empty( $args ) ) {
_wp_scripts_add_args_data( __FUNCTION__, $wp_scripts, $_handle[0], $args );
}
}

Expand Down
Loading
Loading