Skip to content

Commit 6357346

Browse files
committed
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 #8024 Follow-up to [61323]. Props sirreal, westonruter. See #64229. Fixes #61500. git-svn-id: https://develop.svn.wordpress.org/trunk@61587 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 4677c7a commit 6357346

File tree

6 files changed

+597
-162
lines changed

6 files changed

+597
-162
lines changed

src/wp-includes/class-wp-script-modules.php

Lines changed: 125 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -88,31 +88,31 @@ class WP_Script_Modules {
8888
* @since 6.5.0
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
*
118118
* @type bool $in_footer Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional.
@@ -260,31 +260,31 @@ public function set_in_footer( string $id, bool $in_footer ): bool {
260260
* @since 6.5.0
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
*
290290
* @type bool $in_footer Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional.
@@ -533,13 +533,87 @@ public function print_import_map() {
533533
* Returns the import map array.
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
538541
* identifiers and their respective URLs, including the version query.
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 ) {
545619
$imports[ $id ] = $src;

src/wp-includes/class-wp-scripts.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,48 @@ public function add_data( $handle, $key, $value ) {
920920
);
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 );
925967
}

0 commit comments

Comments
 (0)