Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
40f9bce
Avoid enqueueing scripts and styles for a block unless content is ren…
westonruter Jul 9, 2025
0e5a3f2
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Jul 29, 2025
f3209f6
Introduce enqueue_empty_block_content_assets filter
westonruter Jul 29, 2025
59c3543
Add tests for enqueue_empty_block_content_assets with intentionally-f…
westonruter Jul 29, 2025
ccc9126
Fix indentation
westonruter Jul 29, 2025
648713b
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Oct 8, 2025
de22ae9
Skip instantiating HTML Processor if content is empty
westonruter Oct 8, 2025
556c074
Fix test_get_block_editor_settings_overrides_default_settings_all_edi…
westonruter Oct 8, 2025
0ba3ccb
Update enqueue_block_styles_assets() and wp_enqueue_block_style() to …
westonruter Oct 8, 2025
0027b46
Revert failed approach to conditional enqueues
westonruter Oct 8, 2025
7358d5b
Expose queue on WP_Script_Modules for parity with WP_Scripts and WP_S…
westonruter Oct 9, 2025
90a9b4e
Capture assets enqueued during render and include in queues if block …
westonruter Oct 9, 2025
0bbcaf6
Merge branch 'trunk' into trac-63676
westonruter Oct 9, 2025
ce4789b
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Oct 10, 2025
7460f61
Update assertion message
westonruter Oct 13, 2025
df2d018
Update assertion message
westonruter Oct 13, 2025
2ac38f5
Update assertion message
westonruter Oct 13, 2025
c9b892e
Update assertion message
westonruter Oct 13, 2025
29a46d4
Add messages to test_direct_queue_manipulation
westonruter Oct 13, 2025
73a899a
Ensure the globals are re-instantiated
westonruter Oct 13, 2025
e7daf51
Fix phpcs issue
westonruter Oct 13, 2025
92c44f4
Avoid needless calls to array_unique() and array_merge() when there i…
westonruter Oct 13, 2025
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
46 changes: 46 additions & 0 deletions src/wp-includes/class-wp-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,14 @@ public function replace_rich_text( $rich_text ) {
public function render( $options = array() ) {
global $post;

// Capture the current assets queues and then clear out to capture the diff of what was introduced by rendering.
$before_styles_queue = wp_styles()->queue;
$before_scripts_queue = wp_scripts()->queue;
$before_script_modules_queue = wp_script_modules()->queue;
wp_styles()->queue = array();
wp_scripts()->queue = array();
wp_script_modules()->queue = array();

/*
* There can be only one root interactive block at a time because the rendered HTML of that block contains
* the rendered HTML of all its inner blocks, including any interactive block.
Expand Down Expand Up @@ -661,6 +669,44 @@ public function render( $options = array() ) {
$root_interactive_block = null;
}

// Capture the new assets enqueued during rendering, and restore the queues the state prior to rendering.
$new_styles_queue = wp_styles()->queue;
$new_scripts_queue = wp_scripts()->queue;
$new_script_modules_queue = wp_script_modules()->queue;
wp_styles()->queue = $before_styles_queue;
wp_scripts()->queue = $before_scripts_queue;
wp_script_modules()->queue = $before_script_modules_queue;
$has_new_styles = count( $new_styles_queue ) > 0;
$has_new_scripts = count( $new_scripts_queue ) > 0;
$has_new_script_modules = count( $new_script_modules_queue ) > 0;

// Merge the newly enqueued assets with the existing assets if the rendered block is not empty.
if (
( $has_new_styles || $has_new_scripts || $has_new_script_modules ) &&
(
trim( $block_content ) !== '' ||
/**
* Filters whether to enqueue assets for a block which has no rendered content.
*
* @since 6.9.0
*
* @param bool $enqueue Whether to enqueue assets.
* @param string $block_name Block name.
*/
(bool) apply_filters( 'enqueue_empty_block_content_assets', false, $this->name )
)
) {
if ( $has_new_styles ) {
wp_styles()->queue = array_unique( array_merge( wp_styles()->queue, $new_styles_queue ) );
}
if ( $has_new_scripts ) {
wp_scripts()->queue = array_unique( array_merge( wp_scripts()->queue, $new_scripts_queue ) );
}
if ( $has_new_script_modules ) {
wp_script_modules()->queue = array_unique( array_merge( wp_script_modules()->queue, $new_script_modules_queue ) );
}
}

return $block_content;
}
}
45 changes: 18 additions & 27 deletions src/wp-includes/class-wp-script-modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ class WP_Script_Modules {
private $registered = array();

/**
* Holds the script module identifiers that were enqueued before registered.
* An array of IDs for queued script modules.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a need to switch from keying by script ID to an array of script ID values?

It's adding a bunch of work to avoid duplicates that I think could be more easily covered by using a method to add the new asset in the wp_block class.

As developer interfaces go, I also think that an ::add_to_queue method would be more helpful that requiring third party devs do an array_unique( array_merge() ) each time they modify the queue.

Even if we advice that manipulating the queue in this manner is discouraged, it will be done.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This brings WP_Script_Modules in alignment with WP_Scripts and WP_Styles which both expose a $queue member variable. It allows us to easily obtain the list of IDs enqueued, and then merge them.

For WP_Scripts and WP_Styles it is primarily for reading, but it is also used for writing here (now in 6.9):

// Make sure inline style is printed first since it was previously printed at wp_head priority 1 and this preserves the CSS cascade.
array_unshift( wp_styles()->queue, $handle );

In reality, the array_unique() is not needed because WP_Scripts, WP_Styles, and WP_Script_Modules all obtain the unique items when processing anyway (or else keep track via done).

We could introduce a magic setter function for the queue member (which we could then make private). This would have the benefit of not only ensuring uniqueness, but we could also throw an error if anything other than an array of strings is attempted to be set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, let's go with consistency.

*
* @since 6.5.0
* @var array<string, true>
* @since 6.9.0
* @var string[]
*/
private $enqueued_before_registered = array();
public $queue = array();

/**
* Tracks whether the @wordpress/a11y script module is available.
Expand Down Expand Up @@ -122,7 +122,6 @@ public function register( string $id, string $src, array $deps = array(), $versi
$this->registered[ $id ] = array(
'src' => $src,
'version' => $version,
'enqueue' => isset( $this->enqueued_before_registered[ $id ] ),
'dependencies' => $dependencies,
'fetchpriority' => $fetchpriority,
);
Expand Down Expand Up @@ -213,13 +212,11 @@ public function set_fetchpriority( string $id, string $priority ): bool {
* }
*/
public function enqueue( string $id, string $src = '', array $deps = array(), $version = false, array $args = array() ) {
if ( isset( $this->registered[ $id ] ) ) {
$this->registered[ $id ]['enqueue'] = true;
} elseif ( $src ) {
if ( ! in_array( $id, $this->queue, true ) ) {
$this->queue[] = $id;
}
if ( ! isset( $this->registered[ $id ] ) && $src ) {
$this->register( $id, $src, $deps, $version, $args );
$this->registered[ $id ]['enqueue'] = true;
} else {
$this->enqueued_before_registered[ $id ] = true;
}
}

Expand All @@ -231,10 +228,7 @@ public function enqueue( string $id, string $src = '', array $deps = array(), $v
* @param string $id The identifier of the script module.
*/
public function dequeue( string $id ) {
if ( isset( $this->registered[ $id ] ) ) {
$this->registered[ $id ]['enqueue'] = false;
}
unset( $this->enqueued_before_registered[ $id ] );
$this->queue = array_diff( $this->queue, array( $id ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter

Quick Q: After the change it will bypass the updating on $this->registered[ $id ]['enqueue'] to false. Does it accepted behaviour?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enqueue property of the script module array has been removed entirely. Now the enqueued state is exclusively captured in the enqueue class member variable, same as with WP_Scripts. Since all of this was private before, there shouldn't be any back-compat issues.

}

/**
Expand All @@ -245,8 +239,8 @@ public function dequeue( string $id ) {
* @param string $id The identifier of the script module.
*/
public function deregister( string $id ) {
$this->dequeue( $id );
unset( $this->registered[ $id ] );
unset( $this->enqueued_before_registered[ $id ] );
}

/**
Expand Down Expand Up @@ -304,9 +298,9 @@ public function print_enqueued_script_modules() {
* @since 6.5.0
*/
public function print_script_module_preloads() {
foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ), array( 'static' ) ) as $id => $script_module ) {
foreach ( $this->get_dependencies( array_unique( $this->queue ), array( 'static' ) ) as $id => $script_module ) {
// Don't preload if it's marked for enqueue.
if ( true !== $script_module['enqueue'] ) {
if ( ! in_array( $id, $this->queue, true ) ) {
echo sprintf(
'<link rel="modulepreload" href="%s" id="%s"%s>',
esc_url( $this->get_src( $id ) ),
Expand Down Expand Up @@ -345,7 +339,7 @@ public function print_import_map() {
*/
private function get_import_map(): array {
$imports = array();
foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ) ) as $id => $script_module ) {
foreach ( $this->get_dependencies( array_unique( $this->queue ) ) as $id => $script_module ) {
$imports[ $id ] = $this->get_src( $id );
}
return array( 'imports' => $imports );
Expand All @@ -359,13 +353,10 @@ private function get_import_map(): array {
* @return array<string, array> Script modules marked for enqueue, keyed by script module identifier.
*/
private function get_marked_for_enqueue(): array {
$enqueued = array();
foreach ( $this->registered as $id => $script_module ) {
if ( true === $script_module['enqueue'] ) {
$enqueued[ $id ] = $script_module;
}
}
return $enqueued;
return wp_array_slice_assoc(
$this->registered,
$this->queue
);
}

/**
Expand Down Expand Up @@ -457,7 +448,7 @@ private function get_src( string $id ): string {
*/
public function print_script_module_data(): void {
$modules = array();
foreach ( array_keys( $this->get_marked_for_enqueue() ) as $id ) {
foreach ( array_unique( $this->queue ) as $id ) {
if ( '@wordpress/a11y' === $id ) {
$this->a11y_available = true;
}
Expand Down
22 changes: 22 additions & 0 deletions tests/phpunit/tests/blocks/editor.php
Copy link
Member Author

@westonruter westonruter Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes here are discussed in Slack: https://wordpress.slack.com/archives/C02QB2JS7/p1759958061791369

They are needed to allow npm run test:php -- --group=blocks to run without errors. Otherwise, this occurs:

Tests_Blocks_Editor::test_get_block_editor_settings_overrides_default_settings_all_editors
Attempt to read property "registered" on null

/var/www/src/wp-includes/block-editor.php:317
/var/www/src/wp-includes/block-editor.php:633
/var/www/tests/phpunit/tests/blocks/editor.php:406

It seems _wp_get_iframed_editor_assets() is assuming the $wp_scripts and $wp_styles globals are defined when they may not be, and normally when the entire test suite runs one of the other tests will have populated these globals without cleaning up after itself. But when this group of tests runs in isolation, the globals aren't defined, and thus the error occurs.

I think the function should be made more robust:

diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php
index 6f5720ec21..19edc778f7 100644
--- a/src/wp-includes/block-editor.php
+++ b/src/wp-includes/block-editor.php
@@ -302,8 +302,8 @@ function _wp_get_iframed_editor_assets() {
 	global $wp_styles, $wp_scripts;
 
 	// Keep track of the styles and scripts instance to restore later.
-	$current_wp_styles  = $wp_styles;
-	$current_wp_scripts = $wp_scripts;
+	$current_wp_styles  = wp_styles();
+	$current_wp_scripts = wp_scripts();
 
 	// Create new instances to collect the assets.
 	$wp_styles  = new WP_Styles();

Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,21 @@ public function set_up() {

global $post_ID;
$post_ID = 1;

global $wp_scripts, $wp_styles;
$this->original_wp_scripts = $wp_scripts;
$this->original_wp_styles = $wp_styles;
$wp_scripts = null;
$wp_styles = null;
wp_scripts();
wp_styles();
}

public function tear_down() {
global $wp_scripts, $wp_styles;
$wp_scripts = $this->original_wp_scripts;
$wp_styles = $this->original_wp_styles;

/** @var WP_REST_Server $wp_rest_server */
global $wp_rest_server;
$wp_rest_server = null;
Expand All @@ -42,6 +54,16 @@ public function tear_down() {
parent::tear_down();
}

/**
* @var WP_Scripts|null
*/
protected $original_wp_scripts;

/**
* @var WP_Styles|null
*/
protected $original_wp_styles;

public function filter_set_block_categories_post( $block_categories, $post ) {
if ( empty( $post ) ) {
return $block_categories;
Expand Down
Loading
Loading