Skip to content
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

Script Modules API: update the code and move it to the compat/wordpress-6.5 folder #57778

Merged
merged 29 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0bade94
Move modules api to 6.5 folder
cbravobernal Jan 11, 2024
046d43e
Update functions to use wp_modules instead of gutenberg_modules
cbravobernal Jan 12, 2024
3692916
Prevent class redeclaration
cbravobernal Jan 12, 2024
737a227
Add classic themes conditional
cbravobernal Jan 12, 2024
c454a02
Yet another script position movement
cbravobernal Jan 12, 2024
6ff5ddf
Add gutenberg_*_module deprecations
cbravobernal Jan 12, 2024
2153cf6
Add correct version deprecations
cbravobernal Jan 15, 2024
f45b07e
Add correct version deprecations
cbravobernal Jan 15, 2024
55afaf4
Use correct order for module hooks
cbravobernal Jan 15, 2024
94f95ef
Rename module functions to script_module
cbravobernal Jan 15, 2024
a311162
Rename module functions to script_module
cbravobernal Jan 16, 2024
6b860cd
Update files from WP Core
luisherranz Jan 23, 2024
24420fd
Fix deprecated version and rename experimental file
luisherranz Jan 23, 2024
e893d25
Add warning to upcoming deleted file
luisherranz Jan 23, 2024
e01c9ee
Refactor file to use only modules
luisherranz Jan 23, 2024
1fcf8a7
Refactor search block to use only modules
luisherranz Jan 23, 2024
57747db
Refactor query block to use only modules
luisherranz Jan 23, 2024
a68cc60
Refactor image block to use only modules
luisherranz Jan 23, 2024
f6bd5b8
Refactor navigation block to use only modules
luisherranz Jan 23, 2024
d9af702
Fix some params and add missing DocBlock
luisherranz Jan 23, 2024
5cee69c
Update DEWP readme
luisherranz Jan 23, 2024
f5e760d
Remove tests
luisherranz Jan 23, 2024
9331e42
Merge branch 'trunk' into update/move-modules-api-to-6-5
luisherranz Jan 23, 2024
b114497
Update enqueue in new e2e tests
luisherranz Jan 23, 2024
ae63189
Fix wrong print function names
luisherranz Jan 23, 2024
50395f8
Remove unnecessary extra argument
luisherranz Jan 23, 2024
be3d06c
Add missing function
luisherranz Jan 23, 2024
5b652cf
Fix redeclared class problem
sirreal Jan 23, 2024
16150b8
Remove we from docs
cbravobernal Jan 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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_script_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 ) );
Expand Down
355 changes: 355 additions & 0 deletions lib/compat/wordpress-6.5/class-wp-script-modules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
<?php
/**
* Modules API: WP_Script_Modules class.
*
* Native support for ES Modules and Import Maps.
*
* @package WordPress
* @subpackage Script Modules
*/

if ( class_exists( 'WP_Script_Modules' ) ) {
return;
}

/**
* Core class used to register modules.
*
* @since 6.5.0
*/
class WP_Script_Modules {
/**
* Holds the registered modules, keyed by module identifier.
*
* @since 6.5.0
* @var array
*/
private $registered = array();

/**
* Holds the module identifiers that were enqueued before registered.
*
* @since 6.5.0
* @var array
*/
private $enqueued_before_registered = array();

/**
* 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<string|array{id: string, import?: 'static'|'dynamic' }> $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<string|array{id: string, import?: 'static'|'dynamic' }> $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() {
$position = wp_is_block_theme() ? 'wp_head' : 'wp_footer';
add_action( $position, array( $this, 'print_import_map' ) );
add_action( $position, array( $this, 'print_enqueued_modules' ) );
add_action( $position, array( $this, 'print_module_preloads' ) );
}

/**
* 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(
'<link rel="modulepreload" href="%s" id="%s">',
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'];
}
}
Loading
Loading