diff --git a/src/wp-includes/class-wp-script-modules.php b/src/wp-includes/class-wp-script-modules.php index dbfa038f8cbe2..e7b9ad2482280 100644 --- a/src/wp-includes/class-wp-script-modules.php +++ b/src/wp-includes/class-wp-script-modules.php @@ -208,10 +208,15 @@ public function add_hooks() { */ public function print_enqueued_script_modules() { foreach ( $this->get_marked_for_enqueue() as $id => $script_module ) { + $src = $this->get_src( $id ); + if ( null === $src ) { + continue; + } + wp_print_script_tag( array( 'type' => 'module', - 'src' => $this->get_src( $id ), + 'src' => $src, 'id' => $id . '-js-module', ) ); @@ -228,11 +233,16 @@ public function print_enqueued_script_modules() { */ public function print_script_module_preloads() { foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ), array( 'static' ) ) as $id => $script_module ) { + $src = $this->get_src( $id ); + if ( null === $src ) { + continue; + } + // Don't preload if it's marked for enqueue. if ( true !== $script_module['enqueue'] ) { echo sprintf( '', - esc_url( $this->get_src( $id ) ), + esc_url( $src ), esc_attr( $id . '-js-modulepreload' ) ); } @@ -262,14 +272,53 @@ public function print_import_map() { * * @since 6.5.0 * - * @return array Array with an `imports` key mapping to an array of script module identifiers and their respective - * URLs, including the version query. + * @global WP_Dependencies $wp_scripts + * + * @return array 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 ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ) ) as $id => $script_module ) { - $imports[ $id ] = $this->get_src( $id ); + + $classic_script_dependencies = array(); + if ( $wp_scripts instanceof WP_Scripts ) { + foreach ( $wp_scripts->registered as $dependency ) { + $handle = $dependency->handle; + + if ( + ! $wp_scripts->query( $handle, 'done' ) && + ! $wp_scripts->query( $handle, 'to_do' ) && + ! $wp_scripts->query( $handle, 'enqueued' ) + ) { + continue; + } + + $module_deps = $wp_scripts->get_data( $handle, 'module_deps' ); + if ( ! $module_deps ) { + continue; + } + foreach ( $module_deps as $id ) { + $src = $this->get_src( $id ); + if ( null === $src ) { + continue; + } + $imports[ $id ] = $src; + $classic_script_dependencies[] = $id; + } + } } + + foreach ( $this->get_dependencies( array_merge( $classic_script_dependencies, array_keys( $this->get_marked_for_enqueue() ) ) ) as $id => $script_module ) { + $src = $this->get_src( $id ); + if ( null === $src ) { + continue; + } + $imports[ $id ] = $src; + } + return array( 'imports' => $imports ); } @@ -335,11 +384,11 @@ function ( $dependency_script_modules, $id ) use ( $import_types ) { * @since 6.5.0 * * @param string $id The script module identifier. - * @return string The script module src with a version if relevant. + * @return string|null The script module src with a version if relevant. */ - private function get_src( string $id ): string { + private function get_src( string $id ): ?string { if ( ! isset( $this->registered[ $id ] ) ) { - return ''; + return null; } $script_module = $this->registered[ $id ]; diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 77dff94c0497a..29ae293350974 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -150,6 +150,58 @@ public function __construct() { add_action( 'init', array( $this, 'init' ), 0 ); } + /** + * Register an item. + * + * Registers the item if no item of that name already exists. + * + * This method is subclassed here in order to add special handling for script module + * dependencies. + * + * @since 6.8.0 + * + * @see WP_Dependencies::add() + * + * @param string $handle Name of the item. Should be unique. + * @param string|false $src Full URL of the item, or path of the item relative + * to the WordPress root directory. If source is set to false, + * the item is an alias of other items it depends on. + * @param (string|array{type: string, id: string})[] $deps Optional. An array of registered item handles this item depends on. + * Default empty array. + * @param string|bool|null $ver Optional. String specifying item 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 mixed $args Optional. Custom property of the item. NOT the class property $args. + * Examples: $media, $in_footer. + * @return bool Whether the item has been registered. True on success, false on failure. + */ + public function add( $handle, $src, $deps = array(), $ver = false, $args = null ) { + $module_deps = array(); + $script_deps = array(); + if ( array() !== $deps ) { + foreach ( $deps as $dep ) { + if ( is_string( $dep ) ) { + $script_deps[] = $dep; + } elseif ( + isset( $dep['type'], $dep['id'] ) && + 'module' === $dep['type'] && + is_string( $dep['id'] ) + ) { + $module_deps[] = $dep['id']; + } + } + } + if ( ! parent::add( $handle, $src, $script_deps, $ver, $args ) ) { + return false; + } + if ( array() !== $module_deps ) { + $this->add_data( $handle, 'module_deps', $module_deps ); + } + return true; + } + /** * Initialize the class. * diff --git a/tests/phpunit/tests/script-modules/wpScriptModules.php b/tests/phpunit/tests/script-modules/wpScriptModules.php index 85f9599f0dac3..a44317d598ecc 100644 --- a/tests/phpunit/tests/script-modules/wpScriptModules.php +++ b/tests/phpunit/tests/script-modules/wpScriptModules.php @@ -27,14 +27,44 @@ public function set_up() { parent::set_up(); // Set up the WP_Script_Modules instance. $this->script_modules = new WP_Script_Modules(); + unset( $GLOBALS['wp_scripts'] ); } + private static $wp_scripts; + private static $wp_scripts_was_set = false; + + public static function set_up_before_class() { + parent::set_up_before_class(); + + // If the global is set, store it for restoring when done testing. + static::$wp_scripts_was_set = array_key_exists( 'wp_scripts', $GLOBALS ); + if ( static::$wp_scripts_was_set ) { + static::$wp_scripts = $GLOBALS['wp_scripts']; + unset( $GLOBALS['wp_scripts'] ); + } + } + + public static function tear_down_after_class() { + // Restore the global if it was set before running this set of tests. + if ( static::$wp_scripts_was_set ) { + $GLOBALS['wp_scripts'] = static::$wp_scripts; + } + + parent::tear_down_after_class(); + } + + public function clean_up_global_scope() { + unset( $GLOBALS['wp_scripts'] ); + parent::clean_up_global_scope(); + } + + /** * Gets a list of the enqueued script modules. * * @return array Enqueued script module URLs, keyed by script module identifier. */ - public function get_enqueued_script_modules() { + private function get_enqueued_script_modules() { $script_modules_markup = get_echo( array( $this->script_modules, 'print_enqueued_script_modules' ) ); $p = new WP_HTML_Tag_Processor( $script_modules_markup ); $enqueued_script_modules = array(); @@ -54,10 +84,12 @@ public function get_enqueued_script_modules() { * * @return array Import map entry URLs, keyed by script module identifier. */ - public function get_import_map() { + private function get_import_map() { $import_map_markup = get_echo( array( $this->script_modules, 'print_import_map' ) ); preg_match( '/