From 0bade94c21ee83fcd50a73f550ed70f1a735619a Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Thu, 11 Jan 2024 23:37:07 +0100 Subject: [PATCH 01/28] Move modules api to 6.5 folder --- .../wordpress-6.5/class-wp-script-modules.php | 359 +++++++++++++ lib/compat/wordpress-6.5/scripts-modules.php | 304 +++++++++++ .../modules/class-gutenberg-modules.php | 470 ------------------ lib/load.php | 4 +- 4 files changed, 665 insertions(+), 472 deletions(-) create mode 100644 lib/compat/wordpress-6.5/class-wp-script-modules.php create mode 100644 lib/compat/wordpress-6.5/scripts-modules.php delete mode 100644 lib/experimental/modules/class-gutenberg-modules.php diff --git a/lib/compat/wordpress-6.5/class-wp-script-modules.php b/lib/compat/wordpress-6.5/class-wp-script-modules.php new file mode 100644 index 00000000000000..54e04aa0cd3e64 --- /dev/null +++ b/lib/compat/wordpress-6.5/class-wp-script-modules.php @@ -0,0 +1,359 @@ + $deps Optional. An array of module + * identifiers of the dependencies of + * this module. The dependencies can + * be strings or arrays. If they are + * arrays, they need an `id` key with + * the module identifier, and can + * contain an `import` key with either + * `static` or `dynamic`. By default, + * dependencies that don't contain an + * `import` key are considered static. + * @param string|false|null $version Optional. String specifying the + * module version number. Defaults to + * false. It is added to the URL as a + * query string for cache busting + * purposes. If $version is set to + * false, the version number is the + * currently installed WordPress + * version. If $version is set to + * null, no version is added. + */ + public function register( $module_id, $src, $deps = array(), $version = false ) { + if ( ! isset( $this->registered[ $module_id ] ) ) { + $dependencies = array(); + foreach ( $deps as $dependency ) { + if ( is_array( $dependency ) ) { + if ( ! isset( $dependency['id'] ) ) { + _doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' ); + continue; + } + $dependencies[] = array( + 'id' => $dependency['id'], + 'import' => isset( $dependency['import'] ) && 'dynamic' === $dependency['import'] ? 'dynamic' : 'static', + ); + } elseif ( is_string( $dependency ) ) { + $dependencies[] = array( + 'id' => $dependency, + 'import' => 'static', + ); + } else { + _doing_it_wrong( __METHOD__, __( 'Entries in dependencies array must be either strings or arrays with an id key.' ), '6.5.0' ); + } + } + + $this->registered[ $module_id ] = array( + 'src' => $src, + 'version' => $version, + 'enqueue' => isset( $this->enqueued_before_registered[ $module_id ] ), + 'dependencies' => $dependencies, + 'enqueued' => false, + 'preloaded' => false, + ); + } + } + + /** + * Marks the module to be enqueued in the page the next time + * `prints_enqueued_modules` is called. + * + * If a src is provided and the module has not been registered yet, it will be + * registered. + * + * @since 6.5.0 + * + * @param string $module_id The identifier of the module. + * Should be unique. It will be used + * in the final import map. + * @param string $src Optional. Full URL of the module, + * or path of the module relative to + * the WordPress root directory. If + * it is provided and the module has + * not been registered yet, it will be + * registered. + * @param array $deps Optional. An array of module + * identifiers of the dependencies of + * this module. The dependencies can + * be strings or arrays. If they are + * arrays, they need an `id` key with + * the module identifier, and can + * contain an `import` key with either + * `static` or `dynamic`. By default, + * dependencies that don't contain an + * `import` key are considered static. + * @param string|false|null $version Optional. String specifying the + * module version number. Defaults to + * false. It is added to the URL as a + * query string for cache busting + * purposes. If $version is set to + * false, the version number is the + * currently installed WordPress + * version. If $version is set to + * null, no version is added. + */ + public function enqueue( $module_id, $src = '', $deps = array(), $version = false ) { + if ( isset( $this->registered[ $module_id ] ) ) { + $this->registered[ $module_id ]['enqueue'] = true; + } elseif ( $src ) { + $this->register( $module_id, $src, $deps, $version ); + $this->registered[ $module_id ]['enqueue'] = true; + } else { + $this->enqueued_before_registered[ $module_id ] = true; + } + } + + /** + * Unmarks the module so it will no longer be enqueued in the page. + * + * @since 6.5.0 + * + * @param string $module_id The identifier of the module. + */ + public function dequeue( $module_id ) { + if ( isset( $this->registered[ $module_id ] ) ) { + $this->registered[ $module_id ]['enqueue'] = false; + } + unset( $this->enqueued_before_registered[ $module_id ] ); + } + + /** + * Adds the hooks to print the import map, enqueued modules and module + * preloads. + * + * It adds the actions to print the enqueued modules and module preloads to + * both `wp_head` and `wp_footer` because in classic themes, the modules + * used by the theme and plugins will likely be able to be printed in the + * `head`, but the ones used by the blocks will need to be enqueued in the + * `footer`. + * + * As all modules are deferred and dependencies are handled by the browser, + * the order of the modules is not important, but it's still better to print + * the ones that are available when the `wp_head` is rendered, so the browser + * starts downloading those as soon as possible. + * + * The import map is also printed in the footer to be able to include the + * dependencies of all the modules, including the ones printed in the footer. + * + * @since 6.5.0 + */ + public function add_hooks() { + add_action( 'wp_head', array( $this, 'print_import_map' ) ); + add_action( 'wp_head', array( $this, 'print_enqueued_modules' ) ); + add_action( 'wp_head', array( $this, 'print_module_preloads' ) ); + if ( ! wp_is_block_theme() ) { + add_action( 'wp_footer', array( $this, 'print_import_map' ) ); + } + add_action( 'wp_footer', array( $this, 'print_module_preloads' ) ); + add_action( 'wp_footer', array( $this, 'print_enqueued_modules' ) ); + } + + /** + * Prints the enqueued modules using script tags with type="module" + * attributes. + * + * If a enqueued module has already been printed, it will not be printed again + * on subsequent calls to this function. + * + * @since 6.5.0 + */ + public function print_enqueued_modules() { + foreach ( $this->get_marked_for_enqueue() as $module_id => $module ) { + if ( false === $module['enqueued'] ) { + // Mark it as enqueued so it doesn't get enqueued again. + $this->registered[ $module_id ]['enqueued'] = true; + + wp_print_script_tag( + array( + 'type' => 'module', + 'src' => $this->get_versioned_src( $module ), + 'id' => $module_id . '-js-module', + ) + ); + } + } + } + + /** + * Prints the the static dependencies of the enqueued modules using link tags + * with rel="modulepreload" attributes. + * + * If a module is marked for enqueue, it will not be preloaded. If a preloaded + * module has already been printed, it will not be printed again on subsequent + * calls to this function. + * + * @since 6.5.0 + */ + public function print_module_preloads() { + foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ), array( 'static' ) ) as $module_id => $module ) { + // Don't preload if it's marked for enqueue or has already been preloaded. + if ( true !== $module['enqueue'] && false === $module['preloaded'] ) { + // Mark it as preloaded so it doesn't get preloaded again. + $this->registered[ $module_id ]['preloaded'] = true; + + echo sprintf( + '', + esc_url( $this->get_versioned_src( $module ) ), + esc_attr( $module_id . '-js-modulepreload' ) + ); + } + } + } + + /** + * Prints the import map using a script tag with a type="importmap" attribute. + * + * @since 6.5.0 + */ + public function print_import_map() { + $import_map = $this->get_import_map(); + if ( ! empty( $import_map['imports'] ) ) { + wp_print_inline_script_tag( + wp_json_encode( $import_map, JSON_HEX_TAG | JSON_HEX_AMP ), + array( + 'type' => 'importmap', + 'id' => 'wp-importmap', + ) + ); + } + } + + /** + * Returns the import map array. + * + * @since 6.5.0 + * + * @return array Array with an `imports` key mapping to an array of module identifiers and their respective URLs, + * including the version query. + */ + private function get_import_map() { + $imports = array(); + foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ) ) as $module_id => $module ) { + $imports[ $module_id ] = $this->get_versioned_src( $module ); + } + return array( 'imports' => $imports ); + } + + /** + * Retrieves the list of modules marked for enqueue. + * + * @since 6.5.0 + * + * @return array Modules marked for enqueue, keyed by module identifier. + */ + private function get_marked_for_enqueue() { + $enqueued = array(); + foreach ( $this->registered as $module_id => $module ) { + if ( true === $module['enqueue'] ) { + $enqueued[ $module_id ] = $module; + } + } + return $enqueued; + } + + /** + * Retrieves all the dependencies for the given module identifiers, filtered + * by import types. + * + * It will consolidate an array containing a set of unique dependencies based + * on the requested import types: 'static', 'dynamic', or both. This method is + * recursive and also retrieves dependencies of the dependencies. + * + * @since 6.5.0 + * + * @param array $module_ids The identifiers of the modules for which to gather dependencies. + * @param array $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. + * Default is both. + * @return array List of dependencies, keyed by module identifier. + */ + private function get_dependencies( $module_ids, $import_types = array( 'static', 'dynamic' ) ) { + return array_reduce( + $module_ids, + function ( $dependency_modules, $module_id ) use ( $import_types ) { + $dependencies = array(); + foreach ( $this->registered[ $module_id ]['dependencies'] as $dependency ) { + if ( + in_array( $dependency['import'], $import_types, true ) && + isset( $this->registered[ $dependency['id'] ] ) && + ! isset( $dependency_modules[ $dependency['id'] ] ) + ) { + $dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ]; + } + } + return array_merge( $dependency_modules, $dependencies, $this->get_dependencies( array_keys( $dependencies ), $import_types ) ); + }, + array() + ); + } + + /** + * Gets the versioned URL for a module src. + * + * If $version is set to false, the version number is the currently installed + * WordPress version. If $version is set to null, no version is added. + * Otherwise, the string passed in $version is used. + * + * @since 6.5.0 + * + * @param array $module The module. + * @return string The module src with a version if relevant. + */ + private function get_versioned_src( array $module ) { + $args = array(); + if ( false === $module['version'] ) { + $args['ver'] = get_bloginfo( 'version' ); + } elseif ( null !== $module['version'] ) { + $args['ver'] = $module['version']; + } + if ( $args ) { + return add_query_arg( $args, $module['src'] ); + } + return $module['src']; + } +} diff --git a/lib/compat/wordpress-6.5/scripts-modules.php b/lib/compat/wordpress-6.5/scripts-modules.php new file mode 100644 index 00000000000000..e3fa21e8acc59d --- /dev/null +++ b/lib/compat/wordpress-6.5/scripts-modules.php @@ -0,0 +1,304 @@ +add_hooks(); + } + return $instance; +} + +/** + * Registers the module if no module with that module identifier has already + * been registered. + * + * @since 6.5.0 + * + * @param string $module_id The identifier of the module. + * Should be unique. It will be used + * in the final import map. + * @param string $src Full URL of the module, or path of + * the module relative to the + * WordPress root directory. + * @param array $deps Optional. An array of module + * identifiers of the dependencies of + * this module. The dependencies can + * be strings or arrays. If they are + * arrays, they need an `id` key with + * the module identifier, and can + * contain an `import` key with either + * `static` or `dynamic`. By default, + * dependencies that don't contain an + * `import` key are considered static. + * @param string|false|null $version Optional. String specifying the + * module version number. Defaults to + * false. It is added to the URL as a + * query string for cache busting + * purposes. If $version is set to + * false, the version number is the + * currently installed WordPress + * version. If $version is set to + * null, no version is added. + */ +function gutenberg_register_module( $module_id, $src, $deps = array(), $version = false ) { + gutenberg_modules()->register( $module_id, $src, $deps, $version ); +} + +/** + * Marks the module to be enqueued in the page. + * + * If a src is provided and the module has not been registered yet, it will be + * registered. + * + * @since 6.5.0 + * + * @param string $module_id The identifier of the module. + * Should be unique. It will be used + * in the final import map. + * @param string $src Optional. Full URL of the module, + * or path of the module relative to + * the WordPress root directory. If + * it is provided and the module has + * not been registered yet, it will be + * registered. + * @param array $deps Optional. An array of module + * identifiers of the dependencies of + * this module. The dependencies can + * be strings or arrays. If they are + * arrays, they need an `id` key with + * the module identifier, and can + * contain an `import` key with either + * `static` or `dynamic`. By default, + * dependencies that don't contain an + * `import` key are considered static. + * @param string|false|null $version Optional. String specifying the + * module version number. Defaults to + * false. It is added to the URL as a + * query string for cache busting + * purposes. If $version is set to + * false, the version number is the + * currently installed WordPress + * version. If $version is set to + * null, no version is added. + */ +function gutenberg_enqueue_module( $module_id, $src = '', $deps = array(), $version = false ) { + gutenberg_modules()->enqueue( $module_id, $src, $deps, $version ); +} + +/** + * Unmarks the module so it is no longer enqueued in the page. + * + * @since 6.5.0 + * + * @param string $module_id The identifier of the module. + */ +function gutenberg_dequeue_module( $module_id ) { + gutenberg_modules()->dequeue( $module_id ); +} + +/** + * Add module fields from block metadata to WP_Block_Type settings. + * + * This filter allows us to register modules from block metadata and attach additional fields to + * WP_Block_Type instances. + * + * @param array $settings Array of determined settings for registering a block type. + * @param array $metadata Metadata provided for registering a block type. + */ +function gutenberg_filter_block_type_metadata_settings_register_modules( $settings, $metadata = null ) { + $module_fields = array( + 'viewModule' => 'view_module_ids', + ); + foreach ( $module_fields as $metadata_field_name => $settings_field_name ) { + if ( ! empty( $settings[ $metadata_field_name ] ) ) { + $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; + } + if ( ! empty( $metadata[ $metadata_field_name ] ) ) { + $modules = $metadata[ $metadata_field_name ]; + $processed_modules = array(); + if ( is_array( $modules ) ) { + for ( $index = 0; $index < count( $modules ); $index++ ) { + $processed_modules[] = gutenberg_register_block_module_id( + $metadata, + $metadata_field_name, + $index + ); + } + } else { + $processed_modules[] = gutenberg_register_block_module_id( + $metadata, + $metadata_field_name + ); + } + $settings[ $settings_field_name ] = $processed_modules; + } + } + + return $settings; +} + +add_filter( 'block_type_metadata_settings', 'gutenberg_filter_block_type_metadata_settings_register_modules', 10, 2 ); + +/** + * Enqueue modules associated with the block. + * + * @param string $block_content The block content. + * @param array $block The full block, including name and attributes. + * @param WP_Block $instance The block instance. + */ +function gutenberg_filter_render_block_enqueue_view_modules( $block_content, $parsed_block, $block_instance ) { + $block_type = $block_instance->block_type; + + if ( ! empty( $block_type->view_module_ids ) ) { + foreach ( $block_type->view_module_ids as $module_id ) { + gutenberg_enqueue_module( $module_id ); + } + } + + return $block_content; +} + +add_filter( 'render_block', 'gutenberg_filter_render_block_enqueue_view_modules', 10, 3 ); + +/** + * Finds a module ID for the selected block metadata field. It detects + * when a path to file was provided and finds a corresponding asset file + * with details necessary to register the module under an automatically + * generated module ID. + * + * This is analogous to the `register_block_script_handle` in WordPress Core. + * + * @param array $metadata Block metadata. + * @param string $field_name Field name to pick from metadata. + * @param int $index Optional. Index of the script to register when multiple items passed. + * Default 0. + * @return string Module ID. + */ +function gutenberg_register_block_module_id( $metadata, $field_name, $index = 0 ) { + if ( empty( $metadata[ $field_name ] ) ) { + return false; + } + + $module_id = $metadata[ $field_name ]; + if ( is_array( $module_id ) ) { + if ( empty( $module_id[ $index ] ) ) { + return false; + } + $module_id = $module_id[ $index ]; + } + + $module_path = remove_block_asset_path_prefix( $module_id ); + if ( $module_id === $module_path ) { + return $module_id; + } + + $path = dirname( $metadata['file'] ); + $module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) ); + $module_id = gutenberg_generate_block_asset_module_id( $metadata['name'], $field_name, $index ); + $module_asset_path = wp_normalize_path( realpath( $module_asset_raw_path ) ); + + if ( empty( $module_asset_path ) ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + // This string is from WordPress Core. See `register_block_script_handle`. + // Translators: This is a translation from WordPress Core (default). No need to translate. + __( 'The asset file (%1$s) for the "%2$s" defined in "%3$s" block definition is missing.', 'default' ), + $module_asset_raw_path, + $field_name, + $metadata['name'] + ), + '6.5.0' + ); + return false; + } + + $module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) ); + $module_uri = get_block_asset_url( $module_path_norm ); + $module_asset = require $module_asset_path; + $module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array(); + + gutenberg_register_module( + $module_id, + $module_uri, + $module_dependencies, + isset( $module_asset['version'] ) ? $module_asset['version'] : false + ); + + return $module_id; +} + +/** + * Generates the module ID for an asset based on the name of the block + * and the field name provided. + * + * This is analogous to the `generate_block_asset_handle` in WordPress Core. + * + * @param string $block_name Name of the block. + * @param string $field_name Name of the metadata field. + * @param int $index Optional. Index of the asset when multiple items passed. + * Default 0. + * @return string Generated module ID for the block's field. + */ +function gutenberg_generate_block_asset_module_id( $block_name, $field_name, $index = 0 ) { + if ( str_starts_with( $block_name, 'core/' ) ) { + $asset_handle = str_replace( 'core/', 'wp-block-', $block_name ); + if ( str_starts_with( $field_name, 'editor' ) ) { + $asset_handle .= '-editor'; + } + if ( str_starts_with( $field_name, 'view' ) ) { + $asset_handle .= '-view'; + } + if ( $index > 0 ) { + $asset_handle .= '-' . ( $index + 1 ); + } + return $asset_handle; + } + + $field_mappings = array( + 'viewModule' => 'view-module', + ); + $asset_handle = str_replace( '/', '-', $block_name ) . + '-' . $field_mappings[ $field_name ]; + if ( $index > 0 ) { + $asset_handle .= '-' . ( $index + 1 ); + } + return $asset_handle; +} + +function gutenberg_register_view_module_ids_rest_field() { + register_rest_field( + 'block-type', + 'view_module_ids', + array( + 'get_callback' => function ( $item ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $item['name'] ); + if ( isset( $block_type->view_module_ids ) ) { + return $block_type->view_module_ids; + } + return array(); + }, + ) + ); +} + +add_action( 'rest_api_init', 'gutenberg_register_view_module_ids_rest_field' ); diff --git a/lib/experimental/modules/class-gutenberg-modules.php b/lib/experimental/modules/class-gutenberg-modules.php deleted file mode 100644 index f773e2c998c237..00000000000000 --- a/lib/experimental/modules/class-gutenberg-modules.php +++ /dev/null @@ -1,470 +0,0 @@ - $dependency['id'], - 'type' => isset( $dependency['type'] ) && 'dynamic' === $dependency['type'] ? 'dynamic' : 'static', - ); - } elseif ( is_string( $dependency ) ) { - $deps[] = array( - 'id' => $dependency, - 'type' => 'static', - ); - } - } - - self::$registered[ $module_identifier ] = array( - 'src' => $src, - 'version' => $version, - 'enqueued' => in_array( $module_identifier, self::$enqueued_modules_before_register, true ), - 'dependencies' => $deps, - ); - } - } - - /** - * Marks the module to be enqueued in the page. - * - * @param string $module_identifier The identifier of the module. - */ - public static function enqueue( $module_identifier ) { - if ( isset( self::$registered[ $module_identifier ] ) ) { - self::$registered[ $module_identifier ]['enqueued'] = true; - } elseif ( ! in_array( $module_identifier, self::$enqueued_modules_before_register, true ) ) { - self::$enqueued_modules_before_register[] = $module_identifier; - } - } - - /** - * Unmarks the module so it is no longer enqueued in the page. - * - * @param string $module_identifier The identifier of the module. - */ - public static function dequeue( $module_identifier ) { - if ( isset( self::$registered[ $module_identifier ] ) ) { - self::$registered[ $module_identifier ]['enqueued'] = false; - } - $key = array_search( $module_identifier, self::$enqueued_modules_before_register, true ); - if ( false !== $key ) { - array_splice( self::$enqueued_modules_before_register, $key, 1 ); - } - } - - /** - * Returns the import map array. - * - * @return array Array with an 'imports' key mapping to an array of module identifiers and their respective source URLs, including the version query. - */ - public static function get_import_map() { - $imports = array(); - foreach ( self::get_dependencies( array_keys( self::get_enqueued() ) ) as $module_identifier => $module ) { - $imports[ $module_identifier ] = $module['src'] . self::get_version_query_string( $module['version'] ); - } - return array( 'imports' => $imports ); - } - - /** - * Prints the import map using a script tag with an type="importmap" attribute. - */ - public static function print_import_map() { - $import_map = self::get_import_map(); - if ( ! empty( $import_map['imports'] ) ) { - echo ''; - } - } - - /** - * Prints all the enqueued modules using ' - ); - } - - /** - * Gets the version of a module. - * - * If SCRIPT_DEBUG is true, the version is the current timestamp. If $version - * is set to false, the version number is the currently installed WordPress - * version. If $version is set to null, no version is added. - * - * @param array $version The version of the module. - * @return string A string presenting the version. - */ - private static function get_version_query_string( $version ) { - if ( defined( 'SCRIPT_DEBUG ' ) && SCRIPT_DEBUG ) { - return '?ver=' . time(); - } elseif ( false === $version ) { - return '?ver=' . get_bloginfo( 'version' ); - } elseif ( null !== $version ) { - return '?ver=' . $version; - } - return ''; - } - - /** - * Retrieves an array of enqueued modules. - * - * @return array Array of modules keyed by module identifier. - */ - private static function get_enqueued() { - $enqueued = array(); - foreach ( self::$registered as $module_identifier => $module ) { - if ( true === $module['enqueued'] ) { - $enqueued[ $module_identifier ] = $module; - } - } - return $enqueued; - } - - /** - * Retrieves all the dependencies for given modules depending on type. - * - * This method is recursive to also retrieve dependencies of the dependencies. - * It will consolidate an array containing unique dependencies based on the - * requested types ('static' or 'dynamic'). - * - * @param array $module_identifiers The identifiers of the modules for which to gather dependencies. - * @param array $types Optional. Types of dependencies to retrieve: 'static', 'dynamic', or both. Default is both. - * @return array Array of modules keyed by module identifier. - */ - private static function get_dependencies( $module_identifiers, $types = array( 'static', 'dynamic' ) ) { - return array_reduce( - $module_identifiers, - function ( $dependency_modules, $module_identifier ) use ( $types ) { - $dependencies = array(); - foreach ( self::$registered[ $module_identifier ]['dependencies'] as $dependency ) { - if ( - in_array( $dependency['type'], $types, true ) && - isset( self::$registered[ $dependency['id'] ] ) && - ! isset( $dependency_modules[ $dependency['id'] ] ) - ) { - $dependencies[ $dependency['id'] ] = self::$registered[ $dependency['id'] ]; - } - } - return array_merge( $dependency_modules, $dependencies, self::get_dependencies( array_keys( $dependencies ), $types ) ); - }, - array() - ); - } -} - -/** - * Registers the module if no module with that module identifier has already - * been registered. - * - * @param string $module_identifier The identifier of the module. Should be unique. It will be used in the final import map. - * @param string $src Full URL of the module, or path of the script relative to the WordPress root directory. - * @param array $dependencies Optional. An array of module identifiers of the dependencies of this module. The dependencies can be strings or arrays. If they are arrays, they need an `id` key with the module identifier, and can contain a `type` key with either `static` or `dynamic`. By default, dependencies that don't contain a type are considered static. - * @param string|false|null $version Optional. String specifying module version number. Defaults to false. It is added to the URL as a query string for cache busting purposes. If SCRIPT_DEBUG is true, the version is the current timestamp. If $version is set to false, the version number is the currently installed WordPress version. If $version is set to null, no version is added. - */ -function gutenberg_register_module( $module_identifier, $src, $dependencies = array(), $version = false ) { - Gutenberg_Modules::register( $module_identifier, $src, $dependencies, $version ); -} - -/** - * Marks the module to be enqueued in the page. - * - * @param string $module_identifier The identifier of the module. - */ -function gutenberg_enqueue_module( $module_identifier ) { - Gutenberg_Modules::enqueue( $module_identifier ); -} - -/** - * Unmarks the module so it is not longer enqueued in the page. - * - * @param string $module_identifier The identifier of the module. - */ -function gutenberg_dequeue_module( $module_identifier ) { - Gutenberg_Modules::dequeue( $module_identifier ); -} - -$modules_position = wp_is_block_theme() ? 'wp_head' : 'wp_footer'; -// Prints the import map in the head tag in block themes. Otherwise in the footer. -add_action( $modules_position, array( 'Gutenberg_Modules', 'print_import_map' ) ); - -// Prints the enqueued modules in the head tag in block themes. Otherwise in the footer. -add_action( $modules_position, array( 'Gutenberg_Modules', 'print_enqueued_modules' ) ); - -// Prints the preloaded modules in the head tag in block themes. Otherwise in the footer. -add_action( $modules_position, array( 'Gutenberg_Modules', 'print_module_preloads' ) ); - -// Prints the script that loads the import map polyfill in the footer. -add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_import_map_polyfill' ), 11 ); - -/** - * Add module fields from block metadata to WP_Block_Type settings. - * - * This filter allows us to register modules from block metadata and attach additional fields to - * WP_Block_Type instances. - * - * @param array $settings Array of determined settings for registering a block type. - * @param array $metadata Metadata provided for registering a block type. - */ -function gutenberg_filter_block_type_metadata_settings_register_modules( $settings, $metadata = null ) { - $module_fields = array( - 'viewModule' => 'view_module_ids', - ); - foreach ( $module_fields as $metadata_field_name => $settings_field_name ) { - if ( ! empty( $settings[ $metadata_field_name ] ) ) { - $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; - } - if ( ! empty( $metadata[ $metadata_field_name ] ) ) { - $modules = $metadata[ $metadata_field_name ]; - $processed_modules = array(); - if ( is_array( $modules ) ) { - for ( $index = 0; $index < count( $modules ); $index++ ) { - $processed_modules[] = gutenberg_register_block_module_id( - $metadata, - $metadata_field_name, - $index - ); - } - } else { - $processed_modules[] = gutenberg_register_block_module_id( - $metadata, - $metadata_field_name - ); - } - $settings[ $settings_field_name ] = $processed_modules; - } - } - - return $settings; -} - -add_filter( 'block_type_metadata_settings', 'gutenberg_filter_block_type_metadata_settings_register_modules', 10, 2 ); - -/** - * Enqueue modules associated with the block. - * - * @param string $block_content The block content. - * @param array $block The full block, including name and attributes. - * @param WP_Block $instance The block instance. - */ -function gutenberg_filter_render_block_enqueue_view_modules( $block_content, $parsed_block, $block_instance ) { - $block_type = $block_instance->block_type; - - if ( ! empty( $block_type->view_module_ids ) ) { - foreach ( $block_type->view_module_ids as $module_id ) { - gutenberg_enqueue_module( $module_id ); - } - } - - return $block_content; -} - -add_filter( 'render_block', 'gutenberg_filter_render_block_enqueue_view_modules', 10, 3 ); - -/** - * Finds a module ID for the selected block metadata field. It detects - * when a path to file was provided and finds a corresponding asset file - * with details necessary to register the module under an automatically - * generated module ID. - * - * This is analogous to the `register_block_script_handle` in WordPress Core. - * - * @param array $metadata Block metadata. - * @param string $field_name Field name to pick from metadata. - * @param int $index Optional. Index of the script to register when multiple items passed. - * Default 0. - * @return string Module ID. - */ -function gutenberg_register_block_module_id( $metadata, $field_name, $index = 0 ) { - if ( empty( $metadata[ $field_name ] ) ) { - return false; - } - - $module_id = $metadata[ $field_name ]; - if ( is_array( $module_id ) ) { - if ( empty( $module_id[ $index ] ) ) { - return false; - } - $module_id = $module_id[ $index ]; - } - - $module_path = remove_block_asset_path_prefix( $module_id ); - if ( $module_id === $module_path ) { - return $module_id; - } - - $path = dirname( $metadata['file'] ); - $module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) ); - $module_id = gutenberg_generate_block_asset_module_id( $metadata['name'], $field_name, $index ); - $module_asset_path = wp_normalize_path( realpath( $module_asset_raw_path ) ); - - if ( empty( $module_asset_path ) ) { - _doing_it_wrong( - __FUNCTION__, - sprintf( - // This string is from WordPress Core. See `register_block_script_handle`. - // Translators: This is a translation from WordPress Core (default). No need to translate. - __( 'The asset file (%1$s) for the "%2$s" defined in "%3$s" block definition is missing.', 'default' ), - $module_asset_raw_path, - $field_name, - $metadata['name'] - ), - '6.5.0' - ); - return false; - } - - $module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) ); - $module_uri = get_block_asset_url( $module_path_norm ); - $module_asset = require $module_asset_path; - $module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array(); - - gutenberg_register_module( - $module_id, - $module_uri, - $module_dependencies, - isset( $module_asset['version'] ) ? $module_asset['version'] : false - ); - - return $module_id; -} - -/** - * Generates the module ID for an asset based on the name of the block - * and the field name provided. - * - * This is analogous to the `generate_block_asset_handle` in WordPress Core. - * - * @param string $block_name Name of the block. - * @param string $field_name Name of the metadata field. - * @param int $index Optional. Index of the asset when multiple items passed. - * Default 0. - * @return string Generated module ID for the block's field. - */ -function gutenberg_generate_block_asset_module_id( $block_name, $field_name, $index = 0 ) { - if ( str_starts_with( $block_name, 'core/' ) ) { - $asset_handle = str_replace( 'core/', 'wp-block-', $block_name ); - if ( str_starts_with( $field_name, 'editor' ) ) { - $asset_handle .= '-editor'; - } - if ( str_starts_with( $field_name, 'view' ) ) { - $asset_handle .= '-view'; - } - if ( $index > 0 ) { - $asset_handle .= '-' . ( $index + 1 ); - } - return $asset_handle; - } - - $field_mappings = array( - 'viewModule' => 'view-module', - ); - $asset_handle = str_replace( '/', '-', $block_name ) . - '-' . $field_mappings[ $field_name ]; - if ( $index > 0 ) { - $asset_handle .= '-' . ( $index + 1 ); - } - return $asset_handle; -} - -function gutenberg_register_view_module_ids_rest_field() { - register_rest_field( - 'block-type', - 'view_module_ids', - array( - 'get_callback' => function ( $item ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $item['name'] ); - if ( isset( $block_type->view_module_ids ) ) { - return $block_type->view_module_ids; - } - return array(); - }, - ) - ); -} - -add_action( 'rest_api_init', 'gutenberg_register_view_module_ids_rest_field' ); diff --git a/lib/load.php b/lib/load.php index d413334227ee73..99b1a0b85bfbfa 100644 --- a/lib/load.php +++ b/lib/load.php @@ -102,6 +102,8 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.5 compat. require __DIR__ . '/compat/wordpress-6.5/block-patterns.php'; +require __DIR__ . '/compat/wordpress-6.5/class-wp-script-modules.php'; +require __DIR__ . '/compat/wordpress-6.5/scripts-modules.php'; require __DIR__ . '/compat/wordpress-6.5/class-wp-navigation-block-renderer.php'; require __DIR__ . '/compat/wordpress-6.5/kses.php'; @@ -130,8 +132,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/interactivity-api/directives/wp-text.php'; require __DIR__ . '/experimental/interactivity-api/directives/wp-interactive.php'; -require __DIR__ . '/experimental/modules/class-gutenberg-modules.php'; - // Fonts API / Font Face. remove_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); // Turns off WordPress 6.0's stopgap handler. From 046d43ec3e0417d0d28b7f35fd172b9c91b6ee96 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Fri, 12 Jan 2024 12:18:25 +0100 Subject: [PATCH 02/28] Update functions to use wp_modules instead of gutenberg_modules --- .../class-wp-navigation-block-renderer.php | 2 +- .../wordpress-6.5/class-wp-script-modules.php | 7 +- lib/compat/wordpress-6.5/scripts-modules.php | 219 +----- .../interactivity-api/modules.php | 2 +- lib/experimental/modules/scripts-modules.php | 146 ++++ lib/load.php | 1 + packages/block-library/src/file/index.php | 4 +- packages/block-library/src/image/index.php | 4 +- .../block-library/src/navigation/index.php | 2 +- packages/block-library/src/query/index.php | 4 +- packages/block-library/src/search/index.php | 4 +- .../e2e-tests/plugins/interactive-blocks.php | 2 +- .../directive-bind/render.php | 2 +- .../directive-body/render.php | 2 +- .../directive-class/render.php | 2 +- .../directive-context/render.php | 2 +- .../directive-init/render.php | 2 +- .../directive-key/render.php | 2 +- .../directive-on/render.php | 2 +- .../directive-priorities/render.php | 2 +- .../directive-slots/render.php | 2 +- .../directive-style/render.php | 2 +- .../directive-text/render.php | 2 +- .../directive-watch/render.php | 2 +- .../negation-operator/render.php | 2 +- .../router-navigate/render.php | 2 +- .../router-regions/render.php | 2 +- .../interactive-blocks/store-tag/render.php | 2 +- .../tovdom-islands/render.php | 2 +- .../interactive-blocks/tovdom/render.php | 2 +- .../modules/class-gutenberg-modules-test.php | 666 ++++++++++++++---- 31 files changed, 726 insertions(+), 373 deletions(-) create mode 100644 lib/experimental/modules/scripts-modules.php diff --git a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php index 9c270f59fa220e..bf25d8ab630b33 100644 --- a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php +++ b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php @@ -567,7 +567,7 @@ private static function handle_view_script_loading( $attributes, $block, $inner_ if ( $is_gutenberg_plugin ) { if ( $should_load_view_script ) { - gutenberg_enqueue_module( '@wordpress/block-library/navigation-block' ); + wp_enqueue_module( '@wordpress/block-library/navigation-block' ); } // Remove the view script because we are using the module. $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); diff --git a/lib/compat/wordpress-6.5/class-wp-script-modules.php b/lib/compat/wordpress-6.5/class-wp-script-modules.php index 54e04aa0cd3e64..0a278a24f4e449 100644 --- a/lib/compat/wordpress-6.5/class-wp-script-modules.php +++ b/lib/compat/wordpress-6.5/class-wp-script-modules.php @@ -184,12 +184,13 @@ public function dequeue( $module_id ) { * @since 6.5.0 */ public function add_hooks() { - add_action( 'wp_head', array( $this, 'print_import_map' ) ); - add_action( 'wp_head', array( $this, 'print_enqueued_modules' ) ); - add_action( 'wp_head', array( $this, 'print_module_preloads' ) ); if ( ! wp_is_block_theme() ) { add_action( 'wp_footer', array( $this, 'print_import_map' ) ); + } else { + add_action( 'wp_head', array( $this, 'print_import_map' ) ); } + add_action( 'wp_head', array( $this, 'print_enqueued_modules' ) ); + add_action( 'wp_head', array( $this, 'print_module_preloads' ) ); add_action( 'wp_footer', array( $this, 'print_module_preloads' ) ); add_action( 'wp_footer', array( $this, 'print_enqueued_modules' ) ); } diff --git a/lib/compat/wordpress-6.5/scripts-modules.php b/lib/compat/wordpress-6.5/scripts-modules.php index e3fa21e8acc59d..f5ccc92acf6a2a 100644 --- a/lib/compat/wordpress-6.5/scripts-modules.php +++ b/lib/compat/wordpress-6.5/scripts-modules.php @@ -18,15 +18,18 @@ * * @return WP_Script_Modules The main WP_Script_Modules instance. */ -function gutenberg_modules() { - static $instance = null; - if ( is_null( $instance ) ) { - $instance = new WP_Script_Modules(); - $instance->add_hooks(); +if ( ! function_exists( 'wp_modules' ) ) { + function wp_modules() { + static $instance = null; + if ( is_null( $instance ) ) { + $instance = new WP_Script_Modules(); + $instance->add_hooks(); + } + return $instance; } - return $instance; } + /** * Registers the module if no module with that module identifier has already * been registered. @@ -59,8 +62,10 @@ function gutenberg_modules() { * version. If $version is set to * null, no version is added. */ -function gutenberg_register_module( $module_id, $src, $deps = array(), $version = false ) { - gutenberg_modules()->register( $module_id, $src, $deps, $version ); +if ( ! function_exists( 'wp_register_module' ) ) { + function wp_register_module( $module_id, $src, $deps = array(), $version = false ) { + wp_modules()->register( $module_id, $src, $deps, $version ); + } } /** @@ -100,8 +105,10 @@ function gutenberg_register_module( $module_id, $src, $deps = array(), $version * version. If $version is set to * null, no version is added. */ -function gutenberg_enqueue_module( $module_id, $src = '', $deps = array(), $version = false ) { - gutenberg_modules()->enqueue( $module_id, $src, $deps, $version ); +if ( ! function_exists( 'wp_enqueue_module' ) ) { + function wp_enqueue_module( $module_id, $src = '', $deps = array(), $version = false ) { + wp_modules()->enqueue( $module_id, $src, $deps, $version ); + } } /** @@ -111,194 +118,8 @@ function gutenberg_enqueue_module( $module_id, $src = '', $deps = array(), $vers * * @param string $module_id The identifier of the module. */ -function gutenberg_dequeue_module( $module_id ) { - gutenberg_modules()->dequeue( $module_id ); -} - -/** - * Add module fields from block metadata to WP_Block_Type settings. - * - * This filter allows us to register modules from block metadata and attach additional fields to - * WP_Block_Type instances. - * - * @param array $settings Array of determined settings for registering a block type. - * @param array $metadata Metadata provided for registering a block type. - */ -function gutenberg_filter_block_type_metadata_settings_register_modules( $settings, $metadata = null ) { - $module_fields = array( - 'viewModule' => 'view_module_ids', - ); - foreach ( $module_fields as $metadata_field_name => $settings_field_name ) { - if ( ! empty( $settings[ $metadata_field_name ] ) ) { - $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; - } - if ( ! empty( $metadata[ $metadata_field_name ] ) ) { - $modules = $metadata[ $metadata_field_name ]; - $processed_modules = array(); - if ( is_array( $modules ) ) { - for ( $index = 0; $index < count( $modules ); $index++ ) { - $processed_modules[] = gutenberg_register_block_module_id( - $metadata, - $metadata_field_name, - $index - ); - } - } else { - $processed_modules[] = gutenberg_register_block_module_id( - $metadata, - $metadata_field_name - ); - } - $settings[ $settings_field_name ] = $processed_modules; - } +if ( ! function_exists( 'wp_dequeue_module' ) ) { + function wp_dequeue_module( $module_id ) { + wp_modules()->dequeue( $module_id ); } - - return $settings; } - -add_filter( 'block_type_metadata_settings', 'gutenberg_filter_block_type_metadata_settings_register_modules', 10, 2 ); - -/** - * Enqueue modules associated with the block. - * - * @param string $block_content The block content. - * @param array $block The full block, including name and attributes. - * @param WP_Block $instance The block instance. - */ -function gutenberg_filter_render_block_enqueue_view_modules( $block_content, $parsed_block, $block_instance ) { - $block_type = $block_instance->block_type; - - if ( ! empty( $block_type->view_module_ids ) ) { - foreach ( $block_type->view_module_ids as $module_id ) { - gutenberg_enqueue_module( $module_id ); - } - } - - return $block_content; -} - -add_filter( 'render_block', 'gutenberg_filter_render_block_enqueue_view_modules', 10, 3 ); - -/** - * Finds a module ID for the selected block metadata field. It detects - * when a path to file was provided and finds a corresponding asset file - * with details necessary to register the module under an automatically - * generated module ID. - * - * This is analogous to the `register_block_script_handle` in WordPress Core. - * - * @param array $metadata Block metadata. - * @param string $field_name Field name to pick from metadata. - * @param int $index Optional. Index of the script to register when multiple items passed. - * Default 0. - * @return string Module ID. - */ -function gutenberg_register_block_module_id( $metadata, $field_name, $index = 0 ) { - if ( empty( $metadata[ $field_name ] ) ) { - return false; - } - - $module_id = $metadata[ $field_name ]; - if ( is_array( $module_id ) ) { - if ( empty( $module_id[ $index ] ) ) { - return false; - } - $module_id = $module_id[ $index ]; - } - - $module_path = remove_block_asset_path_prefix( $module_id ); - if ( $module_id === $module_path ) { - return $module_id; - } - - $path = dirname( $metadata['file'] ); - $module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) ); - $module_id = gutenberg_generate_block_asset_module_id( $metadata['name'], $field_name, $index ); - $module_asset_path = wp_normalize_path( realpath( $module_asset_raw_path ) ); - - if ( empty( $module_asset_path ) ) { - _doing_it_wrong( - __FUNCTION__, - sprintf( - // This string is from WordPress Core. See `register_block_script_handle`. - // Translators: This is a translation from WordPress Core (default). No need to translate. - __( 'The asset file (%1$s) for the "%2$s" defined in "%3$s" block definition is missing.', 'default' ), - $module_asset_raw_path, - $field_name, - $metadata['name'] - ), - '6.5.0' - ); - return false; - } - - $module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) ); - $module_uri = get_block_asset_url( $module_path_norm ); - $module_asset = require $module_asset_path; - $module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array(); - - gutenberg_register_module( - $module_id, - $module_uri, - $module_dependencies, - isset( $module_asset['version'] ) ? $module_asset['version'] : false - ); - - return $module_id; -} - -/** - * Generates the module ID for an asset based on the name of the block - * and the field name provided. - * - * This is analogous to the `generate_block_asset_handle` in WordPress Core. - * - * @param string $block_name Name of the block. - * @param string $field_name Name of the metadata field. - * @param int $index Optional. Index of the asset when multiple items passed. - * Default 0. - * @return string Generated module ID for the block's field. - */ -function gutenberg_generate_block_asset_module_id( $block_name, $field_name, $index = 0 ) { - if ( str_starts_with( $block_name, 'core/' ) ) { - $asset_handle = str_replace( 'core/', 'wp-block-', $block_name ); - if ( str_starts_with( $field_name, 'editor' ) ) { - $asset_handle .= '-editor'; - } - if ( str_starts_with( $field_name, 'view' ) ) { - $asset_handle .= '-view'; - } - if ( $index > 0 ) { - $asset_handle .= '-' . ( $index + 1 ); - } - return $asset_handle; - } - - $field_mappings = array( - 'viewModule' => 'view-module', - ); - $asset_handle = str_replace( '/', '-', $block_name ) . - '-' . $field_mappings[ $field_name ]; - if ( $index > 0 ) { - $asset_handle .= '-' . ( $index + 1 ); - } - return $asset_handle; -} - -function gutenberg_register_view_module_ids_rest_field() { - register_rest_field( - 'block-type', - 'view_module_ids', - array( - 'get_callback' => function ( $item ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $item['name'] ); - if ( isset( $block_type->view_module_ids ) ) { - return $block_type->view_module_ids; - } - return array(); - }, - ) - ); -} - -add_action( 'rest_api_init', 'gutenberg_register_view_module_ids_rest_field' ); diff --git a/lib/experimental/interactivity-api/modules.php b/lib/experimental/interactivity-api/modules.php index 0695da26f4b1b3..55dc69545be202 100644 --- a/lib/experimental/interactivity-api/modules.php +++ b/lib/experimental/interactivity-api/modules.php @@ -10,7 +10,7 @@ * Register the `@wordpress/interactivity` module. */ function gutenberg_register_interactivity_module() { - gutenberg_register_module( + wp_register_module( '@wordpress/interactivity', gutenberg_url( '/build/interactivity/index.min.js' ), array(), diff --git a/lib/experimental/modules/scripts-modules.php b/lib/experimental/modules/scripts-modules.php new file mode 100644 index 00000000000000..9d91d48c8dbf1f --- /dev/null +++ b/lib/experimental/modules/scripts-modules.php @@ -0,0 +1,146 @@ +block_type; + + if ( ! empty( $block_type->view_module_ids ) ) { + foreach ( $block_type->view_module_ids as $module_id ) { + wp_enqueue_module( $module_id ); + } + } + + return $block_content; +} + +add_filter( 'render_block', 'gutenberg_filter_render_block_enqueue_view_modules', 10, 3 ); + +/** + * Finds a module ID for the selected block metadata field. It detects + * when a path to file was provided and finds a corresponding asset file + * with details necessary to register the module under an automatically + * generated module ID. + * + * This is analogous to the `register_block_script_handle` in WordPress Core. + * + * @param array $metadata Block metadata. + * @param string $field_name Field name to pick from metadata. + * @param int $index Optional. Index of the script to register when multiple items passed. + * Default 0. + * @return string Module ID. + */ +function gutenberg_register_block_module_id( $metadata, $field_name, $index = 0 ) { + if ( empty( $metadata[ $field_name ] ) ) { + return false; + } + + $module_id = $metadata[ $field_name ]; + if ( is_array( $module_id ) ) { + if ( empty( $module_id[ $index ] ) ) { + return false; + } + $module_id = $module_id[ $index ]; + } + + $module_path = remove_block_asset_path_prefix( $module_id ); + if ( $module_id === $module_path ) { + return $module_id; + } + + $path = dirname( $metadata['file'] ); + $module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) ); + $module_id = gutenberg_generate_block_asset_module_id( $metadata['name'], $field_name, $index ); + $module_asset_path = wp_normalize_path( realpath( $module_asset_raw_path ) ); + + if ( empty( $module_asset_path ) ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + // This string is from WordPress Core. See `register_block_script_handle`. + // Translators: This is a translation from WordPress Core (default). No need to translate. + __( 'The asset file (%1$s) for the "%2$s" defined in "%3$s" block definition is missing.', 'default' ), + $module_asset_raw_path, + $field_name, + $metadata['name'] + ), + '6.5.0' + ); + return false; + } + + $module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) ); + $module_uri = get_block_asset_url( $module_path_norm ); + $module_asset = require $module_asset_path; + $module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array(); + + wp_register_module( + $module_id, + $module_uri, + $module_dependencies, + isset( $module_asset['version'] ) ? $module_asset['version'] : false + ); + + return $module_id; +} + +/** + * Generates the module ID for an asset based on the name of the block + * and the field name provided. + * + * This is analogous to the `generate_block_asset_handle` in WordPress Core. + * + * @param string $block_name Name of the block. + * @param string $field_name Name of the metadata field. + * @param int $index Optional. Index of the asset when multiple items passed. + * Default 0. + * @return string Generated module ID for the block's field. + */ +function gutenberg_generate_block_asset_module_id( $block_name, $field_name, $index = 0 ) { + if ( str_starts_with( $block_name, 'core/' ) ) { + $asset_handle = str_replace( 'core/', 'wp-block-', $block_name ); + if ( str_starts_with( $field_name, 'editor' ) ) { + $asset_handle .= '-editor'; + } + if ( str_starts_with( $field_name, 'view' ) ) { + $asset_handle .= '-view'; + } + if ( $index > 0 ) { + $asset_handle .= '-' . ( $index + 1 ); + } + return $asset_handle; + } + + $field_mappings = array( + 'viewModule' => 'view-module', + ); + $asset_handle = str_replace( '/', '-', $block_name ) . + '-' . $field_mappings[ $field_name ]; + if ( $index > 0 ) { + $asset_handle .= '-' . ( $index + 1 ); + } + return $asset_handle; +} + +function gutenberg_register_view_module_ids_rest_field() { + register_rest_field( + 'block-type', + 'view_module_ids', + array( + 'get_callback' => function ( $item ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $item['name'] ); + if ( isset( $block_type->view_module_ids ) ) { + return $block_type->view_module_ids; + } + return array(); + }, + ) + ); +} + +add_action( 'rest_api_init', 'gutenberg_register_view_module_ids_rest_field' ); diff --git a/lib/load.php b/lib/load.php index 99b1a0b85bfbfa..fbc441adbea260 100644 --- a/lib/load.php +++ b/lib/load.php @@ -114,6 +114,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/kses.php'; require __DIR__ . '/experimental/l10n.php'; require __DIR__ . '/experimental/synchronization.php'; +require __DIR__ . '/experimental/modules/scripts-modules.php'; if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) { require __DIR__ . '/experimental/disable-tinymce.php'; diff --git a/packages/block-library/src/file/index.php b/packages/block-library/src/file/index.php index 5910a63e6cf182..3f8a6fa719a137 100644 --- a/packages/block-library/src/file/index.php +++ b/packages/block-library/src/file/index.php @@ -22,7 +22,7 @@ function render_block_core_file( $attributes, $content, $block ) { if ( $is_gutenberg_plugin ) { if ( $should_load_view_script ) { - gutenberg_enqueue_module( '@wordpress/block-library/file-block' ); + wp_enqueue_module( '@wordpress/block-library/file-block' ); } // Remove the view script because we are using the module. $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); @@ -108,7 +108,7 @@ function register_block_core_file() { ); if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { - gutenberg_register_module( + wp_register_module( '@wordpress/block-library/file-block', gutenberg_url( '/build/interactivity/file.min.js' ), array( '@wordpress/interactivity' ), diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index add8e5989ab7de..d3c387a3595c8b 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -52,7 +52,7 @@ function render_block_core_image( $attributes, $content, $block ) { true === $lightbox_settings['enabled'] ) { if ( $is_gutenberg_plugin ) { - gutenberg_enqueue_module( '@wordpress/block-library/image' ); + wp_enqueue_module( '@wordpress/block-library/image' ); // Remove the view script because we are using the module. $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file_handle ) ); } elseif ( ! in_array( $view_js_file_handle, $script_handles, true ) ) { @@ -359,7 +359,7 @@ function register_block_core_image() { ); if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { - gutenberg_register_module( + wp_register_module( '@wordpress/block-library/image', gutenberg_url( '/build/interactivity/image.min.js' ), array( '@wordpress/interactivity' ), diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 3af85afd92522f..41e5a1ef9b75de 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -428,7 +428,7 @@ function register_block_core_navigation() { ); if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { - gutenberg_register_module( + wp_register_module( '@wordpress/block-library/navigation-block', gutenberg_url( '/build/interactivity/navigation.min.js' ), array( '@wordpress/interactivity' ), diff --git a/packages/block-library/src/query/index.php b/packages/block-library/src/query/index.php index 75b5218364d7b1..c298fa0543b8af 100644 --- a/packages/block-library/src/query/index.php +++ b/packages/block-library/src/query/index.php @@ -70,7 +70,7 @@ class="wp-block-query__enhanced-pagination-animation" if ( $is_gutenberg_plugin ) { if ( $should_load_view_script ) { - gutenberg_enqueue_module( '@wordpress/block-library/query' ); + wp_enqueue_module( '@wordpress/block-library/query' ); } // Remove the view script because we are using the module. $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_asset ) ); @@ -138,7 +138,7 @@ function register_block_core_query() { ); if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { - gutenberg_register_module( + wp_register_module( '@wordpress/block-library/query', '/wp-content/plugins/gutenberg/build/interactivity/query.min.js', array( '@wordpress/interactivity' ), diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index ae6ddb1c4fb372..85b8e57cc81fc7 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -92,7 +92,7 @@ function render_block_core_search( $attributes, $content, $block ) { if ( $is_gutenberg_plugin ) { if ( $is_expandable_searchfield ) { - gutenberg_enqueue_module( '@wordpress/block-library/search-block' ); + wp_enqueue_module( '@wordpress/block-library/search-block' ); } // Remove the view script because we are using the module. $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); @@ -214,7 +214,7 @@ function register_block_core_search() { ); if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { - gutenberg_register_module( + wp_register_module( '@wordpress/block-library/search-block', gutenberg_url( '/build/interactivity/search.min.js' ), array( '@wordpress/interactivity' ), diff --git a/packages/e2e-tests/plugins/interactive-blocks.php b/packages/e2e-tests/plugins/interactive-blocks.php index 1236ecd7d277f5..631b63581b05d2 100644 --- a/packages/e2e-tests/plugins/interactive-blocks.php +++ b/packages/e2e-tests/plugins/interactive-blocks.php @@ -21,7 +21,7 @@ function () { $view_file = plugin_dir_url( $block_folder ) . $name . '/' . 'view.js'; - gutenberg_register_module( + wp_register_module( $name . '-view', $view_file, array( '@wordpress/interactivity' ), diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-bind/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-bind/render.php index efa62c3a18b7e6..3dcf7b4610a38e 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-bind/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-bind/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-bind-view' ); +wp_enqueue_module( 'directive-bind-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-body/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-body/render.php index 16178815c24869..2ad0fecfe1c48e 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-body/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-body/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-body-view' ); +wp_enqueue_module( 'directive-body-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php index fe9a6c4f85a161..7ef05fcfa43977 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-context-view' ); +wp_enqueue_module( 'directive-context-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-init/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-init/render.php index 6ee0115ff05bac..ae0f1e441edc75 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-init/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-init/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-init-view' ); +wp_enqueue_module( 'directive-init-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-key/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-key/render.php index 1a5f57b526fbf2..7f01c86624ff1b 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-key/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-key/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-key-view' ); +wp_enqueue_module( 'directive-key-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-priorities/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-priorities/render.php index f082d44dd8fdfa..da366479186ba6 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-priorities/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-priorities/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-priorities-view' ); +wp_enqueue_module( 'directive-priorities-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-slots/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-slots/render.php index c9a989ccaf0275..c330c4a9f8740a 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-slots/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-slots/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-slots-view' ); +wp_enqueue_module( 'directive-slots-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-text/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-text/render.php index 98803669c6b0bc..401a79bbf00d67 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-text/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-text/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-text-view' ); +wp_enqueue_module( 'directive-text-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-watch/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-watch/render.php index 4fc7dd96ee883d..21947f23b4d4fd 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/directive-watch/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/directive-watch/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'directive-watch-view' ); +wp_enqueue_module( 'directive-watch-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/negation-operator/render.php b/packages/e2e-tests/plugins/interactive-blocks/negation-operator/render.php index 8d686e6c3ff464..a532412267fda9 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/negation-operator/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/negation-operator/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'negation-operator-view' ); +wp_enqueue_module( 'negation-operator-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php index 3fbddf623db605..6a56d972a1f27f 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php @@ -7,7 +7,7 @@ * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable */ -gutenberg_enqueue_module( 'router-navigate-view' ); +wp_enqueue_module( 'router-navigate-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/store-tag/render.php b/packages/e2e-tests/plugins/interactive-blocks/store-tag/render.php index 06deea9e1169da..b50be487db338c 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/store-tag/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/store-tag/render.php @@ -7,7 +7,7 @@ * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable */ -gutenberg_enqueue_module( 'store-tag-view' ); +wp_enqueue_module( 'store-tag-view' ); // These variables simulates SSR. $test_store_tag_counter = 'ok' === $attributes['condition'] ? 3 : 0; diff --git a/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php b/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php index 1f53ca1331a377..f75f1b5bb8fe00 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php @@ -5,7 +5,7 @@ * @package gutenberg-test-interactive-blocks */ -gutenberg_enqueue_module( 'tovdom-islands-view' ); +wp_enqueue_module( 'tovdom-islands-view' ); ?>
diff --git a/packages/e2e-tests/plugins/interactive-blocks/tovdom/render.php b/packages/e2e-tests/plugins/interactive-blocks/tovdom/render.php index 309b42a5829359..fd82f8b56f3197 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/tovdom/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/tovdom/render.php @@ -9,7 +9,7 @@ $src_proc_ins = $plugin_url . 'tovdom/processing-instructions.js'; $src_cdata = $plugin_url . 'tovdom/cdata.js'; -gutenberg_enqueue_module( 'tovdom-view' ); +wp_enqueue_module( 'tovdom-view' ); ?>
diff --git a/phpunit/experimental/modules/class-gutenberg-modules-test.php b/phpunit/experimental/modules/class-gutenberg-modules-test.php index a7f6c3b491a53d..2c82b592caacf5 100644 --- a/phpunit/experimental/modules/class-gutenberg-modules-test.php +++ b/phpunit/experimental/modules/class-gutenberg-modules-test.php @@ -1,55 +1,73 @@ registered = new ReflectionProperty( 'Gutenberg_Modules', 'registered' ); - $this->registered->setAccessible( true ); - $this->old_registered = $this->registered->getValue(); - $this->registered->setValue( array() ); - } - - public function tear_down() { - $this->registered->setValue( $this->old_registered ); - parent::tear_down(); + $this->modules = new WP_Script_Modules(); } + /** + * Gets a list of the enqueued modules. + * + * @return array Enqueued module URLs, keyed by module identifier. + */ public function get_enqueued_modules() { - $modules_markup = get_echo( array( 'Gutenberg_Modules', 'print_enqueued_modules' ) ); + $modules_markup = get_echo( array( $this->modules, 'print_enqueued_modules' ) ); $p = new WP_HTML_Tag_Processor( $modules_markup ); $enqueued_modules = array(); while ( $p->next_tag( array( - 'tag' => 'SCRIPT', - 'type' => 'module', + 'tag' => 'SCRIPT', + 'import' => 'module', ) ) ) { - $enqueued_modules[ $p->get_attribute( 'id' ) ] = $p->get_attribute( 'src' ); + $id = preg_replace( '/-js-module$/', '', $p->get_attribute( 'id' ) ); + $enqueued_modules[ $id ] = $p->get_attribute( 'src' ); } return $enqueued_modules; } + /** + * Gets the modules listed in the import map. + * + * @return array Import map entry URLs, keyed by module identifier. + */ public function get_import_map() { - $import_map_markup = get_echo( array( 'Gutenberg_Modules', 'print_import_map' ) ); - preg_match( '/