-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interactivity API: Use modules instead of scripts in the frontend #56143
Changes from 21 commits
1f3f99d
8fe36f9
68692d1
0215330
9c5ea5d
3ffc77c
7cc08c8
11b4e1b
c080fa7
bd0a881
e0201ce
89f6be0
64fd8ac
6a21c10
0e6a178
211d1d5
400afda
87809c5
43ff14a
6d8d965
823fcfa
81c014e
5488eab
4e6c5da
829c6ec
77cd95e
366ad42
4e7a3ef
3e8d5fd
46c9a65
9f185d8
df56463
0c6237a
2a2eeda
5926282
6b932fe
1b176d0
b627d73
0affd41
3cd253a
148f1dd
4135f22
876fdb1
88fcee8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
/** | ||
* Interactive modules. | ||
* | ||
* @package Gutenberg | ||
* @subpackage Interactivity API | ||
*/ | ||
|
||
/** | ||
* Register the `@wordpress/interactivity` module. | ||
*/ | ||
function gutenberg_register_interactivity_module() { | ||
gutenberg_register_module( | ||
'@wordpress/interactivity', | ||
'/wp-content/plugins/gutenberg/build/interactivity/index.min.js', | ||
luisherranz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
array(), | ||
defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' ) | ||
); | ||
|
||
// TODO: Move this to a local file and replace with a simpler version that | ||
// only provides support for import maps. | ||
wp_enqueue_script( | ||
'es-module-shims', | ||
'https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js', | ||
array(), | ||
null, | ||
array( | ||
'strategy' => 'defer', | ||
) | ||
); | ||
} | ||
|
||
add_action( 'wp_enqueue_scripts', 'gutenberg_register_interactivity_module' ); |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
<?php | ||
/** | ||
* Modules API: Gutenberg_Modules class. | ||
* | ||
* Native support for ES Modules and Import Maps. | ||
* | ||
* @package Gutenberg | ||
* @subpackage Modules | ||
*/ | ||
|
||
/** | ||
* Gutenberg_Modules class | ||
*/ | ||
class Gutenberg_Modules { | ||
/** | ||
* An array of registered modules, keyed by module identifier. | ||
* | ||
* @var array | ||
*/ | ||
private static $registered = array(); | ||
|
||
/** | ||
* An array of queued modules. | ||
* | ||
* @var string[] | ||
*/ | ||
private static $enqueued = array(); | ||
|
||
/** | ||
* Registers the module if no module with that module identifier already | ||
* exists. | ||
* | ||
* @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 static and dynamic dependencies of this module. It can be an indexed array, in which case all the dependencies are static, or it can be an associative array, in which case it has to contain the keys `static` and `dynamic`. | ||
luisherranz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @param string|bool|null $version Optional. String specifying module version number. It is added to the URL as a query string for cache busting purposes. If SCRIPT_DEBUG is true, a timestamp is used. If it is set to false, a version number is automatically added equal to current installed WordPress version. If set to null, no version is added. | ||
*/ | ||
public static function register( $module_identifier, $src, $dependencies = array(), $version = false ) { | ||
// Register the module if it's not already registered. | ||
if ( ! isset( self::$registered[ $module_identifier ] ) ) { | ||
$deps = array( | ||
'static' => 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 string The import map. | ||
luisherranz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
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() { | ||
echo '<script type="importmap">' . wp_json_encode( self::get_import_map(), JSON_HEX_TAG | JSON_HEX_AMP ) . '</script>'; | ||
} | ||
|
||
/** | ||
* Prints all the enqueued modules using <script type="module">. | ||
*/ | ||
public static function print_enqueued_modules() { | ||
foreach ( self::$enqueued as $module_identifier ) { | ||
if ( isset( self::$registered[ $module_identifier ] ) ) { | ||
$module = self::$registered[ $module_identifier ]; | ||
echo '<script type="module" src="' . $module['src'] . self::get_version_query_string( $module['version'] ) . '" id="' . $module_identifier . '"></script>'; | ||
luisherranz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
|
||
/** | ||
* Prints the link tag with rel="modulepreload" for all the static | ||
* dependencies of the enqueued modules. | ||
*/ | ||
public static function print_module_preloads() { | ||
foreach ( self::get_dependencies( self::$enqueued, array( 'static' ) ) as $dependency_identifier => $module ) { | ||
echo '<link rel="modulepreload" href="' . $module['src'] . self::get_version_query_string( $module['version'] ) . '" id="' . $dependency_identifier . '">'; | ||
luisherranz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
/** | ||
* Gets the module's version. It either returns a timestamp (if SCRIPT_DEBUG | ||
* is true), the explicit version of the module if it is set and not false, or | ||
* an empty string if none of the above conditions are met. | ||
* | ||
* @param array $version The version of the module. | ||
* @return string A string presenting the version. | ||
*/ | ||
private static function get_version_query_string( $version ) { | ||
if ( SCRIPT_DEBUG ) { | ||
return '?ver=' . time(); | ||
} elseif ( false === $version ) { | ||
return '?ver=' . get_bloginfo( 'version' ); | ||
} elseif ( null !== $version ) { | ||
return '?ver=' . $version; | ||
} | ||
return ''; | ||
} | ||
|
||
/** | ||
* Returns all unique static and/or dynamic dependencies for the received modules. It's | ||
* recursive, so it will also get the static or dynamic dependencies of the dependencies. | ||
* | ||
* @param array $module_identifiers The identifiers of the modules to get dependencies for. | ||
* @param array $types The type of dependencies to retrieve. It can be `static`, `dynamic` or both. | ||
* @return array The array containing the unique dependencies of the modules. | ||
*/ | ||
private static function get_dependencies( $module_identifiers, $types = array( 'static', 'dynamic' ) ) { | ||
return array_reduce( | ||
$module_identifiers, | ||
function ( $dependency_modules, $module_identifier ) use ( $types ) { | ||
if ( ! isset( self::$registered[ $module_identifier ] ) ) { | ||
return $dependency_modules; | ||
} | ||
|
||
$dependencies = array(); | ||
foreach ( $types as $type ) { | ||
$dependencies = array_merge( $dependencies, self::$registered[ $module_identifier ]['dependencies'][ $type ] ); | ||
} | ||
$dependencies = array_unique( $dependencies ); | ||
$dependency_modules = array_intersect_key( self::$registered, array_flip( $dependencies ) ); | ||
|
||
return array_merge( $dependency_modules, $dependency_modules, self::get_dependencies( $dependencies, $types ) ); | ||
}, | ||
array() | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Registers a JavaScript module. It will be added to the import map. | ||
* | ||
* @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 static and dynamic dependencies of this module. It can be an indexed array, in which case all the dependencies are static, or it can be an associative array, in which case it has to contain the keys `static` and `dynamic`. | ||
* @param string|bool|null $version Optional. String specifying module version number. It is added to the URL as a query string for cache busting purposes. If SCRIPT_DEBUG is true, a timestamp is used. If it is set to false, a version number is automatically added equal to current installed WordPress version. If 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 ); | ||
} | ||
|
||
/** | ||
* Enqueues a JavaScript module. It will be added to both the import map and a | ||
* script tag with the "module" type. | ||
* | ||
* @param string $module_identifier The identifier of the module. Should be unique. It will be used in the final import map. | ||
*/ | ||
function gutenberg_enqueue_module( $module_identifier ) { | ||
Gutenberg_Modules::enqueue( $module_identifier ); | ||
} | ||
|
||
// Prints the import map in the head tag. | ||
add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_import_map' ) ); | ||
|
||
// Prints the enqueued modules in the head tag. | ||
add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_enqueued_modules' ) ); | ||
|
||
// Prints the preloaded modules in the head tag. | ||
add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_module_preloads' ) ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,19 +16,8 @@ | |
*/ | ||
function render_block_core_file( $attributes, $content, $block ) { | ||
$should_load_view_script = ! empty( $attributes['displayPreview'] ); | ||
$view_js_file = 'wp-block-file-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 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 ) ); | ||
} | ||
if ( $should_load_view_script ) { | ||
gutenberg_enqueue_module( '@wordpress/block-library/file-block' ); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels like complex logic, do we expect any interactive block to write this kind of code? Is there a way to streamline that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar feedback before WP 6.4 Beta 1: WordPress/wordpress-develop#5262 (comment). Follow-up ticket in Trac: https://core.trac.wordpress.org/ticket/59462. |
||
|
||
// Update object's aria-label attribute if present in block HTML. | ||
|
@@ -96,5 +85,12 @@ function register_block_core_file() { | |
'render_callback' => 'render_block_core_file', | ||
) | ||
); | ||
|
||
gutenberg_register_module( | ||
'@wordpress/block-library/file-block', | ||
'/wp-content/plugins/gutenberg/build/interactivity/file.min.js', | ||
array( '@wordpress/interactivity' ), | ||
defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' ) | ||
); | ||
} | ||
add_action( 'init', 'register_block_core_file' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a way in our code to have an array of "esm modules" and have these be registered automatically (similarly to the other types of packages we have: non modules)