From bce6f45c4f8041d00e35ebeca21f8e81b8506fa1 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Wed, 29 Nov 2023 16:48:39 +0100 Subject: [PATCH] Interactivity API: Use modules instead of scripts in the frontend (#56143) * Bundle `@wordpress/interactivity` as an ES module * Add the class with a basic API * Make it work with the Navigation block * Add versions * Register `@wordpress/interactivity` module * Add query and image blocks * Add id with module identifier to the enqueued modules * Add requried $usage for admin/frontend * Avoid the use of enqueue to register modules * Refactor versions * Switch from `both` to `['admin', 'frontend']` * Improve comments * Add an optional static dependency graph * Add static and dynamic dependencies in the server * Add the file and search blocks * Move $version out of $args to match wp_register_script * Improve version logic * Add polyfill * Fix $version using its own arg in register calls * Add unit tests * Refactor tests, add test get import map * Cleaning data * Use gutenberg_url() * Use wp_print_script_tag * Fix DocBlock on get_import_map() * Load navigation module only on Gutenberg plugin * Load query module only on Gutenberg plugin * Load search module only on Gutenberg plugin * Load file module only on Gutenberg plugin * Load image module only on Gutenberg plugin * Make registration optional * Improve navigation logic * Remove unnecessary check * Fix missing view file * Don't print the import map if it's empty * Load the importmap polyfill locally * Move the es-modules-shims package to the top * Use the public functions in the tests * Update test to be more output oriented * Remove we from comments. * Start using modules in the interactivity e2e tests * Update package-lock.json --------- Co-authored-by: Carlos Bravo --- docs/reference-guides/core-blocks.md | 2 +- .../class-wp-navigation-block-renderer.php | 32 ++- .../interactivity-api/modules.php | 33 +++ .../interactivity-api/scripts.php | 40 ---- .../modules/class-gutenberg-modules.php | 195 +++++++++++++++ lib/load.php | 4 +- package-lock.json | 13 + package.json | 1 + packages/block-library/src/file/index.php | 37 ++- packages/block-library/src/image/block.json | 1 + packages/block-library/src/image/index.php | 19 +- .../block-library/src/navigation/index.php | 9 + packages/block-library/src/query/index.php | 42 +++- packages/block-library/src/search/index.php | 58 ++--- packages/e2e-tests/package.json | 1 + .../e2e-tests/plugins/interactive-blocks.php | 4 +- .../directive-bind/render.php | 2 + .../interactive-blocks/directive-bind/view.js | 62 ++--- .../directive-body/render.php | 2 + .../interactive-blocks/directive-body/view.js | 21 +- .../directive-class/render.php | 2 + .../directive-class/view.js | 39 +-- .../directive-context/render.php | 2 + .../directive-context/view.js | 93 ++++---- .../directive-init/render.php | 2 + .../interactive-blocks/directive-init/view.js | 120 +++++----- .../directive-key/render.php | 1 + .../interactive-blocks/directive-key/view.js | 27 ++- .../directive-on/render.php | 2 + .../interactive-blocks/directive-on/view.js | 53 +++-- .../directive-priorities/render.php | 2 + .../directive-priorities/view.js | 225 +++++++++--------- .../directive-slots/render.php | 2 + .../directive-slots/view.js | 33 +-- .../directive-style/render.php | 1 + .../directive-style/view.js | 41 ++-- .../directive-text/render.php | 2 + .../interactive-blocks/directive-text/view.js | 31 +-- .../directive-watch/render.php | 2 + .../directive-watch/view.js | 100 ++++---- .../negation-operator/render.php | 2 + .../negation-operator/view.js | 32 ++- .../router-navigate/render.php | 2 +- .../router-navigate/view.js | 55 ++--- .../router-regions/render.php | 1 + .../interactive-blocks/router-regions/view.js | 76 +++--- .../interactive-blocks/store-tag/render.php | 3 + .../interactive-blocks/store-tag/view.js | 40 ++-- .../tovdom-islands/render.php | 2 + .../interactive-blocks/tovdom-islands/view.js | 46 ++-- .../interactive-blocks/tovdom/render.php | 2 + .../plugins/interactive-blocks/tovdom/view.js | 9 +- .../modules/class-gutenberg-modules-test.php | 66 +++++ tools/webpack/interactivity.js | 39 ++- 54 files changed, 1052 insertions(+), 681 deletions(-) create mode 100644 lib/experimental/interactivity-api/modules.php delete mode 100644 lib/experimental/interactivity-api/scripts.php create mode 100644 lib/experimental/modules/class-gutenberg-modules.php create mode 100644 phpunit/experimental/modules/class-gutenberg-modules-test.php diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 4f42550ba4cfb..dd7ef824aa6b0 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -378,7 +378,7 @@ Insert an image to make a visual statement. ([Source](https://github.com/WordPre - **Name:** core/image - **Category:** media -- **Supports:** align (center, full, left, right, wide), anchor, color (~~background~~, ~~text~~), filter (duotone) +- **Supports:** align (center, full, left, right, wide), anchor, color (~~background~~, ~~text~~), filter (duotone), interactivity - **Attributes:** alt, aspectRatio, caption, height, href, id, lightbox, linkClass, linkDestination, linkTarget, rel, scale, sizeSlug, title, url, width ## Latest Comments 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 52ec4f508246a..9c2314ebe6890 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 @@ -543,20 +543,28 @@ private static function get_nav_element_directives( $should_load_view_script ) { */ private static function handle_view_script_loading( $attributes, $block, $inner_blocks ) { $should_load_view_script = static::should_load_view_script( $attributes, $inner_blocks ); + $is_gutenberg_plugin = defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN; + $view_js_file = 'wp-block-navigation-view'; + $script_handles = $block->block_type->view_script_handles; - $view_js_file = 'wp-block-navigation-view'; - - // If the script already exists, there is no point in removing it from viewScript. - if ( ! wp_script_is( $view_js_file ) ) { - $script_handles = $block->block_type->view_script_handles; - - // If the script is not needed, and it is still in the `view_script_handles`, remove it. - if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); + if ( $is_gutenberg_plugin ) { + if ( $should_load_view_script ) { + gutenberg_enqueue_module( '@wordpress/block-library/navigation-block' ); } - // If the script is needed, but it was previously removed, add it again. - if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); + // Remove the view script because we are using the module. + $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); + } else { + // If the script already exists, there is no point in removing it from viewScript. + if ( ! wp_script_is( $view_js_file ) ) { + + // If the script is not needed, and it is still in the `view_script_handles`, remove it. + if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) { + $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); + } + // If the script is needed, but it was previously removed, add it again. + if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) { + $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); + } } } } diff --git a/lib/experimental/interactivity-api/modules.php b/lib/experimental/interactivity-api/modules.php new file mode 100644 index 0000000000000..7db774de04fd7 --- /dev/null +++ b/lib/experimental/interactivity-api/modules.php @@ -0,0 +1,33 @@ + 'defer', + ) + ); +} + +add_action( 'wp_enqueue_scripts', 'gutenberg_register_interactivity_module' ); diff --git a/lib/experimental/interactivity-api/scripts.php b/lib/experimental/interactivity-api/scripts.php deleted file mode 100644 index ed1fca8550070..0000000000000 --- a/lib/experimental/interactivity-api/scripts.php +++ /dev/null @@ -1,40 +0,0 @@ -=' ); - if ( $supports_defer ) { - // Defer execution of @wordpress/interactivity package but continue loading in head. - wp_script_add_data( 'wp-interactivity', 'strategy', 'defer' ); - wp_script_add_data( 'wp-interactivity', 'group', 0 ); - } else { - // Move the @wordpress/interactivity package to the footer. - wp_script_add_data( 'wp-interactivity', 'group', 1 ); - } - - // Move all the view scripts of the interactive blocks to the footer. - $registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered(); - foreach ( array_values( $registered_blocks ) as $block ) { - if ( isset( $block->supports['interactivity'] ) && $block->supports['interactivity'] ) { - foreach ( $block->view_script_handles as $handle ) { - // Note that all block view scripts are already made defer by default. - wp_script_add_data( $handle, 'group', $supports_defer ? 0 : 1 ); - } - } - } -} -add_action( 'wp_enqueue_scripts', 'gutenberg_interactivity_move_interactive_scripts_to_the_footer', 11 ); diff --git a/lib/experimental/modules/class-gutenberg-modules.php b/lib/experimental/modules/class-gutenberg-modules.php new file mode 100644 index 0000000000000..ca74d863043ee --- /dev/null +++ b/lib/experimental/modules/class-gutenberg-modules.php @@ -0,0 +1,195 @@ + isset( $dependencies['static'] ) || isset( $dependencies['dynamic'] ) ? $dependencies['static'] ?? array() : $dependencies, + 'dynamic' => isset( $dependencies['dynamic'] ) ? $dependencies['dynamic'] : array(), + ); + + self::$registered[ $module_identifier ] = array( + 'src' => $src, + 'version' => $version, + 'dependencies' => $deps, + ); + } + } + + /** + * Enqueues a module in the page. + * + * @param string $module_identifier The identifier of the module. + */ + public static function enqueue( $module_identifier ) { + // Add the module to the queue if it's not already there. + if ( ! in_array( $module_identifier, self::$enqueued, true ) ) { + self::$enqueued[] = $module_identifier; + } + } + + /** + * Returns the import map array. + * + * @return array Associative array with 'imports' key mapping to an array of module identifiers and their respective source strings. + */ + public static function get_import_map() { + $imports = array(); + foreach ( self::get_dependencies( self::$enqueued, array( 'static', 'dynamic' ) ) as $module_identifier => $module ) { + $imports[ $module_identifier ] = $module['src'] . self::get_version_query_string( $module['version'] ); + } + return array( 'imports' => $imports ); + } + + /** + * Prints the import map. + */ + public static function print_import_map() { + $import_map = self::get_import_map(); + if ( ! empty( $import_map['imports'] ) ) { + echo ''; + } + } + + /** + * Prints all the enqueued modules using