From 40f9bce149912d9f04fa3de750ae286aca329691 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 8 Jul 2025 17:26:43 -0700 Subject: [PATCH 01/18] Avoid enqueueing scripts and styles for a block unless content is rendered --- src/wp-includes/class-wp-block.php | 83 +++++++++++++++++------------- src/wp-includes/script-loader.php | 13 ++++- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index e3b97f650190f..92ac16ad7f679 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -588,41 +588,6 @@ public function render( $options = array() ) { $post = $global_post; } - if ( ( ! empty( $this->block_type->script_handles ) ) ) { - foreach ( $this->block_type->script_handles as $script_handle ) { - wp_enqueue_script( $script_handle ); - } - } - - if ( ! empty( $this->block_type->view_script_handles ) ) { - foreach ( $this->block_type->view_script_handles as $view_script_handle ) { - wp_enqueue_script( $view_script_handle ); - } - } - - if ( ! empty( $this->block_type->view_script_module_ids ) ) { - foreach ( $this->block_type->view_script_module_ids as $view_script_module_id ) { - wp_enqueue_script_module( $view_script_module_id ); - } - } - - /* - * For Core blocks, these styles are only enqueued if `wp_should_load_separate_core_block_assets()` returns - * true. Otherwise these `wp_enqueue_style()` calls will not have any effect, as the Core blocks are relying on - * the combined 'wp-block-library' stylesheet instead, which is unconditionally enqueued. - */ - if ( ( ! empty( $this->block_type->style_handles ) ) ) { - foreach ( $this->block_type->style_handles as $style_handle ) { - wp_enqueue_style( $style_handle ); - } - } - - if ( ( ! empty( $this->block_type->view_style_handles ) ) ) { - foreach ( $this->block_type->view_style_handles as $view_style_handle ) { - wp_enqueue_style( $view_style_handle ); - } - } - /** * Filters the content of a single block. * @@ -656,6 +621,54 @@ public function render( $options = array() ) { $root_interactive_block = null; } + /* + * Proceed with enqueueing scripts and styles only if the block rendered any tags. A block may commonly not + * render anything. Some examples: + * + * - Featured Image block on a post without a featured image. + * - Comments block on a post with comments disabled. + * - An arbitrary block which has a `render_block` filter that returns an empty string (or an HTML comment). + * + * In all these cases, adding scripts and styles will be a waste since they will not be used on the page. + */ + $processor = new WP_HTML_Tag_Processor( $block_content ); + if ( $processor->next_tag() ) { + if ( ( ! empty( $this->block_type->script_handles ) ) ) { + foreach ( $this->block_type->script_handles as $script_handle ) { + wp_enqueue_script( $script_handle ); + } + } + + if ( ! empty( $this->block_type->view_script_handles ) ) { + foreach ( $this->block_type->view_script_handles as $view_script_handle ) { + wp_enqueue_script( $view_script_handle ); + } + } + + if ( ! empty( $this->block_type->view_script_module_ids ) ) { + foreach ( $this->block_type->view_script_module_ids as $view_script_module_id ) { + wp_enqueue_script_module( $view_script_module_id ); + } + } + + /* + * For Core blocks, these styles are only enqueued if `wp_should_load_separate_core_block_assets()` returns + * true. Otherwise these `wp_enqueue_style()` calls will not have any effect, as the Core blocks are relying on + * the combined 'wp-block-library' stylesheet instead, which is unconditionally enqueued. + */ + if ( ( ! empty( $this->block_type->style_handles ) ) ) { + foreach ( $this->block_type->style_handles as $style_handle ) { + wp_enqueue_style( $style_handle ); + } + } + + if ( ( ! empty( $this->block_type->view_style_handles ) ) ) { + foreach ( $this->block_type->view_style_handles as $view_style_handle ) { + wp_enqueue_style( $view_style_handle ); + } + } + } + return $block_content; } } diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 3beddac135e43..3daf1be39a93b 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2737,7 +2737,11 @@ function enqueue_block_styles_assets() { 'render_block', static function ( $html, $block ) use ( $block_name, $style_properties ) { if ( $block['blockName'] === $block_name ) { - wp_enqueue_style( $style_properties['style_handle'] ); + // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. + $processor = new WP_HTML_Tag_Processor( $html ); + if ( $processor->next_tag() ) { + wp_enqueue_style( $style_properties['style_handle'] ); + } } return $html; }, @@ -3292,6 +3296,13 @@ function wp_enqueue_block_style( $block_name, $args ) { * @return string Block content. */ $callback = static function ( $content ) use ( $args ) { + + // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. + $processor = new WP_HTML_Tag_Processor( $content ); + if ( ! $processor->next_tag() ) { + return $content; + } + // Register the stylesheet. if ( ! empty( $args['src'] ) ) { wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] ); From f3209f6bad21bcd8d12aaa34a3fd2b3efedd00cc Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 28 Jul 2025 18:26:05 -0700 Subject: [PATCH 02/18] Introduce enqueue_empty_block_content_assets filter --- src/wp-includes/class-wp-block.php | 11 ++++++++++- src/wp-includes/script-loader.php | 8 +++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 8791c837948ec..141ef2d24faa1 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -632,7 +632,16 @@ public function render( $options = array() ) { * In all these cases, adding scripts and styles will be a waste since they will not be used on the page. */ $processor = new WP_HTML_Tag_Processor( $block_content ); - if ( $processor->next_tag() ) { + + /** + * 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. + */ + if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $processor->next_tag(), $this->name ) ) { if ( ( ! empty( $this->block_type->script_handles ) ) ) { foreach ( $this->block_type->script_handles as $script_handle ) { wp_enqueue_script( $script_handle ); diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 3daf1be39a93b..58d5e6865c205 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2739,7 +2739,8 @@ static function ( $html, $block ) use ( $block_name, $style_properties ) { if ( $block['blockName'] === $block_name ) { // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. $processor = new WP_HTML_Tag_Processor( $html ); - if ( $processor->next_tag() ) { + /** This filter is documented in src/wp-includes/class-wp-block.php */ + if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $processor->next_tag(), $block_name ) ) { wp_enqueue_style( $style_properties['style_handle'] ); } } @@ -3295,11 +3296,12 @@ function wp_enqueue_block_style( $block_name, $args ) { * is to ensure the content exists. * @return string Block content. */ - $callback = static function ( $content ) use ( $args ) { + $callback = static function ( $content ) use ( $block_name, $args ) { // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. $processor = new WP_HTML_Tag_Processor( $content ); - if ( ! $processor->next_tag() ) { + /** This filter is documented in src/wp-includes/class-wp-block.php */ + if ( ! (bool) apply_filters( 'enqueue_empty_block_content_assets', $processor->next_tag(), $block_name ) ) { return $content; } From 59c354335bdc245eb2ebe2e9d061fba69d331032 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 29 Jul 2025 11:53:21 -0700 Subject: [PATCH 03/18] Add tests for enqueue_empty_block_content_assets with intentionally-failing test case --- tests/phpunit/tests/blocks/wpBlock.php | 173 ++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/blocks/wpBlock.php b/tests/phpunit/tests/blocks/wpBlock.php index f28bbd6c83733..d274de34ef025 100644 --- a/tests/phpunit/tests/blocks/wpBlock.php +++ b/tests/phpunit/tests/blocks/wpBlock.php @@ -13,7 +13,7 @@ class Tests_Blocks_wpBlock extends WP_UnitTestCase { /** * Fake block type registry. * - * @var WP_Block_Type_Registry + * @var WP_Block_Type_Registry|null */ private $registry = null; @@ -23,6 +23,11 @@ class Tests_Blocks_wpBlock extends WP_UnitTestCase { public function set_up() { parent::set_up(); + global $wp_styles, $wp_scripts, $wp_script_modules; + $wp_styles = null; + $wp_scripts = null; + $wp_script_modules = null; + $this->registry = new WP_Block_Type_Registry(); } @@ -32,6 +37,11 @@ public function set_up() { public function tear_down() { $this->registry = null; + global $wp_styles, $wp_scripts, $wp_script_modules; + $wp_styles = null; + $wp_scripts = null; + $wp_script_modules = null; + parent::tear_down(); } @@ -352,6 +362,167 @@ public function test_render_applies_dynamic_render_block_filter() { $this->assertSame( 'Original: "StaticOriginal: "Inner", from block "core/example"", from block "core/example"', $rendered_content ); } + /** + * Data provider for test_render_enqueues_scripts_and_styles. + * + * @return array + */ + public function data_provider_test_render_enqueues_scripts_and_styles(): array { + return array( + 'all_printed' => array( + 'set_up' => null, + 'expected_rendered_block' => '

Hello World!

', + 'expected_styles' => array( 'static-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ), + ), + 'dynamic_hidden_scripts_omitted' => array( + 'set_up' => static function () { + add_filter( 'render_block_core/dynamic', '__return_empty_string' ); + }, + 'expected_rendered_block' => '
', + 'expected_styles' => array( 'static-view-style' ), + 'expected_scripts' => array( 'static-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module' ), + ), + 'dynamic_hidden_scripts_included' => array( + 'set_up' => static function () { + add_filter( 'render_block_core/dynamic', '__return_empty_string' ); + add_filter( + 'enqueue_empty_block_content_assets', + static function ( $enqueue, $block_name ) { + if ( 'core/dynamic' === $block_name ) { + $enqueue = true; + } + return $enqueue; + }, + 10, + 2 + ); + }, + 'expected_rendered_block' => '
', + 'expected_styles' => array( 'static-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ), + ), + 'static_hidden_scripts_omitted' => array( + 'set_up' => static function () { + add_filter( 'render_block_core/static', '__return_empty_string' ); + }, + 'expected_rendered_block' => '', + // TODO: This test is currently failing. Since the inner dynamic block is rendered, it is getting its assets enqueued even though the parent static block is not rendered. When the parent is not rendered, then the nested assets really shouldn't be enqueued. + 'expected_styles' => array(), + 'expected_scripts' => array(), + 'expected_script_modules' => array(), + ), + 'all_hidden_scripts_omitted' => array( + 'set_up' => static function () { + add_filter( 'render_block', '__return_empty_string' ); + }, + 'expected_rendered_block' => '', + 'expected_styles' => array(), + 'expected_scripts' => array(), + 'expected_script_modules' => array(), + ), + 'all_hidden_scripts_included' => array( + 'set_up' => static function () { + add_filter( 'render_block', '__return_empty_string' ); + add_filter( 'enqueue_empty_block_content_assets', '__return_true' ); + }, + 'expected_rendered_block' => '', + 'expected_styles' => array( 'static-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ), + ), + ); + } + + /** + * @ticket 63676 + * @covers WP_Block::render() + * + * @dataProvider data_provider_test_render_enqueues_scripts_and_styles + * + * @param Closure|null $set_up + * @param string[] $expected_styles + * @param string[] $expected_scripts + * @param string[] $expected_script_modules + */ + public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, string $expected_rendered_block, array $expected_styles, array $expected_scripts, array $expected_script_modules ) { + if ( $set_up instanceof Closure ) { + $set_up(); + } + wp_register_style( 'static-view-style', home_url( '/static-view-style.css' ) ); + wp_register_script( 'static-view-script', home_url( '/static-view-script.js' ) ); + wp_register_script_module( 'static-view-script-module', home_url( '/static-view-script-module.js' ) ); + $this->registry->register( + 'core/static', + array( + 'view_style_handles' => array( 'static-view-style' ), + 'view_script_handles' => array( 'static-view-script' ), + 'view_script_module_ids' => array( 'static-view-script-module' ), + ) + ); + + wp_register_style( 'dynamic-view-style', home_url( '/dynamic-view-style.css' ) ); + wp_register_script( 'dynamic-view-script', home_url( '/dynamic-view-script.js' ) ); + wp_register_script_module( 'dynamic-view-script-module', home_url( '/dynamic-view-script-module.js' ) ); + $this->registry->register( + 'core/dynamic', + array( + 'render_callback' => static function () { + return '

Hello World!

'; + }, + 'view_style_handles' => array( 'dynamic-view-style' ), + 'view_script_handles' => array( 'dynamic-view-script' ), + 'view_script_module_ids' => array( 'dynamic-view-script-module' ), + ) + ); + + $parsed_blocks = parse_blocks( '
' ); + $parsed_block = $parsed_blocks[0]; + $context = array(); + $block = new WP_Block( $parsed_block, $context, $this->registry ); + $rendered_block = $block->render(); + + $this->assertEqualHTML( + $expected_rendered_block, + $rendered_block + ); + + remove_action( 'wp_print_styles', 'print_emoji_styles' ); + + $actual_styles = array(); + $printed_styles = get_echo( 'wp_print_styles' ); + $processor = new WP_HTML_Tag_Processor( $printed_styles ); + while ( $processor->next_tag( array( 'tag_name' => 'LINK' ) ) ) { + if ( 1 === preg_match( '/^(.+)-css$/', $processor->get_attribute( 'id' ), $matches ) ) { + $actual_styles[] = $matches[1]; + } + } + $this->assertSameSets( $expected_styles, $actual_styles ); + + $actual_scripts = array(); + $printed_scripts = get_echo( 'wp_print_scripts' ); + $processor = new WP_HTML_Tag_Processor( $printed_scripts ); + while ( $processor->next_tag( array( 'tag_name' => 'SCRIPT' ) ) ) { + if ( 1 === preg_match( '/^(.+)-js$/', $processor->get_attribute( 'id' ), $matches ) ) { + $actual_scripts[] = $matches[1]; + } + } + $this->assertSameSets( $expected_scripts, $actual_scripts ); + + $actual_script_modules = array(); + $printed_script_modules = get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) ); + $processor = new WP_HTML_Tag_Processor( $printed_script_modules ); + while ( $processor->next_tag( array( 'tag_name' => 'SCRIPT' ) ) ) { + if ( 1 === preg_match( '/^(.+)-js-module$/', $processor->get_attribute( 'id' ), $matches ) ) { + $actual_script_modules[] = $matches[1]; + } + } + $this->assertSameSets( $expected_script_modules, $actual_script_modules ); + } + /** * @ticket 49927 */ From ccc9126298b6fc4130b1ae1ea3016ad46e92ac07 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 29 Jul 2025 11:55:09 -0700 Subject: [PATCH 04/18] Fix indentation --- src/wp-includes/script-loader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 58d5e6865c205..d53cc5ca9fdf5 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2739,8 +2739,8 @@ static function ( $html, $block ) use ( $block_name, $style_properties ) { if ( $block['blockName'] === $block_name ) { // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. $processor = new WP_HTML_Tag_Processor( $html ); - /** This filter is documented in src/wp-includes/class-wp-block.php */ - if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $processor->next_tag(), $block_name ) ) { + /** This filter is documented in src/wp-includes/class-wp-block.php */ + if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $processor->next_tag(), $block_name ) ) { wp_enqueue_style( $style_properties['style_handle'] ); } } From de22ae9b4f528b9c082c21e018dc156449550a38 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 8 Oct 2025 12:49:31 -0700 Subject: [PATCH 05/18] Skip instantiating HTML Processor if content is empty Co-authored-by: Peter Wilson --- src/wp-includes/class-wp-block.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 873aea38a2615..12a474dd4dc4b 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -636,7 +636,12 @@ public function render( $options = array() ) { * * In all these cases, adding scripts and styles will be a waste since they will not be used on the page. */ - $processor = new WP_HTML_Tag_Processor( $block_content ); + if ( trim( $block_content ) === '' ) { + $enqueue = false; + } else { + $processor = new WP_HTML_Tag_Processor( $block_content ); + $enqueue = $processor->next_tag(); + } /** * Filters whether to enqueue assets for a block which has no rendered content. @@ -646,7 +651,7 @@ public function render( $options = array() ) { * @param bool $enqueue Whether to enqueue assets. * @param string $block_name Block name. */ - if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $processor->next_tag(), $this->name ) ) { + if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $enqueue, $this->name ) ) { if ( ( ! empty( $this->block_type->script_handles ) ) ) { foreach ( $this->block_type->script_handles as $script_handle ) { wp_enqueue_script( $script_handle ); From 556c0741c6b2bd067f27c06b8d25d0be622d236e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 8 Oct 2025 14:22:23 -0700 Subject: [PATCH 06/18] Fix test_get_block_editor_settings_overrides_default_settings_all_editors in isolation --- tests/phpunit/tests/blocks/editor.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/phpunit/tests/blocks/editor.php b/tests/phpunit/tests/blocks/editor.php index 2ba757921038d..6f6d7a9a9d187 100644 --- a/tests/phpunit/tests/blocks/editor.php +++ b/tests/phpunit/tests/blocks/editor.php @@ -31,9 +31,19 @@ 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(); + 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; @@ -42,6 +52,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; From 0ba3ccb44178bb5b2def7885d3ec305571a6e652 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 8 Oct 2025 14:32:14 -0700 Subject: [PATCH 07/18] Update enqueue_block_styles_assets() and wp_enqueue_block_style() to account for empty content Co-authored-by: Peter Wilson --- src/wp-includes/script-loader.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 125d0be047d71..e153cca07f9c2 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2767,9 +2767,15 @@ function enqueue_block_styles_assets() { static function ( $html, $block ) use ( $block_name, $style_properties ) { if ( $block['blockName'] === $block_name ) { // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. - $processor = new WP_HTML_Tag_Processor( $html ); + if ( trim( $html ) === '' ) { + $enqueue = false; + } else { + $processor = new WP_HTML_Tag_Processor( $html ); + $enqueue = $processor->next_tag(); + } + /** This filter is documented in src/wp-includes/class-wp-block.php */ - if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $processor->next_tag(), $block_name ) ) { + if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $enqueue, $block_name ) ) { wp_enqueue_style( $style_properties['style_handle'] ); } } @@ -3328,9 +3334,15 @@ function wp_enqueue_block_style( $block_name, $args ) { $callback = static function ( $content ) use ( $block_name, $args ) { // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. - $processor = new WP_HTML_Tag_Processor( $content ); + if ( trim( $content ) === '' ) { + $enqueue = false; + } else { + $processor = new WP_HTML_Tag_Processor( $content ); + $enqueue = $processor->next_tag(); + } + /** This filter is documented in src/wp-includes/class-wp-block.php */ - if ( ! (bool) apply_filters( 'enqueue_empty_block_content_assets', $processor->next_tag(), $block_name ) ) { + if ( ! (bool) apply_filters( 'enqueue_empty_block_content_assets', $enqueue, $block_name ) ) { return $content; } From 0027b4683fc66688c3259187a2a635ce7169c260 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 8 Oct 2025 15:40:11 -0700 Subject: [PATCH 08/18] Revert failed approach to conditional enqueues --- src/wp-includes/class-wp-block.php | 97 +++++++++++------------------- src/wp-includes/script-loader.php | 29 +-------- 2 files changed, 37 insertions(+), 89 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 12a474dd4dc4b..b498c1189700e 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -593,6 +593,41 @@ public function render( $options = array() ) { $post = $global_post; } + if ( ( ! empty( $this->block_type->script_handles ) ) ) { + foreach ( $this->block_type->script_handles as $script_handle ) { + wp_enqueue_script( $script_handle ); + } + } + + if ( ! empty( $this->block_type->view_script_handles ) ) { + foreach ( $this->block_type->view_script_handles as $view_script_handle ) { + wp_enqueue_script( $view_script_handle ); + } + } + + if ( ! empty( $this->block_type->view_script_module_ids ) ) { + foreach ( $this->block_type->view_script_module_ids as $view_script_module_id ) { + wp_enqueue_script_module( $view_script_module_id ); + } + } + + /* + * For Core blocks, these styles are only enqueued if `wp_should_load_separate_core_block_assets()` returns + * true. Otherwise these `wp_enqueue_style()` calls will not have any effect, as the Core blocks are relying on + * the combined 'wp-block-library' stylesheet instead, which is unconditionally enqueued. + */ + if ( ( ! empty( $this->block_type->style_handles ) ) ) { + foreach ( $this->block_type->style_handles as $style_handle ) { + wp_enqueue_style( $style_handle ); + } + } + + if ( ( ! empty( $this->block_type->view_style_handles ) ) ) { + foreach ( $this->block_type->view_style_handles as $view_style_handle ) { + wp_enqueue_style( $view_style_handle ); + } + } + /** * Filters the content of a single block. * @@ -626,68 +661,6 @@ public function render( $options = array() ) { $root_interactive_block = null; } - /* - * Proceed with enqueueing scripts and styles only if the block rendered any tags. A block may commonly not - * render anything. Some examples: - * - * - Featured Image block on a post without a featured image. - * - Comments block on a post with comments disabled. - * - An arbitrary block which has a `render_block` filter that returns an empty string (or an HTML comment). - * - * In all these cases, adding scripts and styles will be a waste since they will not be used on the page. - */ - if ( trim( $block_content ) === '' ) { - $enqueue = false; - } else { - $processor = new WP_HTML_Tag_Processor( $block_content ); - $enqueue = $processor->next_tag(); - } - - /** - * 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. - */ - if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $enqueue, $this->name ) ) { - if ( ( ! empty( $this->block_type->script_handles ) ) ) { - foreach ( $this->block_type->script_handles as $script_handle ) { - wp_enqueue_script( $script_handle ); - } - } - - if ( ! empty( $this->block_type->view_script_handles ) ) { - foreach ( $this->block_type->view_script_handles as $view_script_handle ) { - wp_enqueue_script( $view_script_handle ); - } - } - - if ( ! empty( $this->block_type->view_script_module_ids ) ) { - foreach ( $this->block_type->view_script_module_ids as $view_script_module_id ) { - wp_enqueue_script_module( $view_script_module_id ); - } - } - - /* - * For Core blocks, these styles are only enqueued if `wp_should_load_separate_core_block_assets()` returns - * true. Otherwise these `wp_enqueue_style()` calls will not have any effect, as the Core blocks are relying on - * the combined 'wp-block-library' stylesheet instead, which is unconditionally enqueued. - */ - if ( ( ! empty( $this->block_type->style_handles ) ) ) { - foreach ( $this->block_type->style_handles as $style_handle ) { - wp_enqueue_style( $style_handle ); - } - } - - if ( ( ! empty( $this->block_type->view_style_handles ) ) ) { - foreach ( $this->block_type->view_style_handles as $view_style_handle ) { - wp_enqueue_style( $view_style_handle ); - } - } - } - return $block_content; } } diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index e153cca07f9c2..fc4165009e82c 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2766,18 +2766,7 @@ function enqueue_block_styles_assets() { 'render_block', static function ( $html, $block ) use ( $block_name, $style_properties ) { if ( $block['blockName'] === $block_name ) { - // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. - if ( trim( $html ) === '' ) { - $enqueue = false; - } else { - $processor = new WP_HTML_Tag_Processor( $html ); - $enqueue = $processor->next_tag(); - } - - /** This filter is documented in src/wp-includes/class-wp-block.php */ - if ( (bool) apply_filters( 'enqueue_empty_block_content_assets', $enqueue, $block_name ) ) { - wp_enqueue_style( $style_properties['style_handle'] ); - } + wp_enqueue_style( $style_properties['style_handle'] ); } return $html; }, @@ -3331,21 +3320,7 @@ function wp_enqueue_block_style( $block_name, $args ) { * is to ensure the content exists. * @return string Block content. */ - $callback = static function ( $content ) use ( $block_name, $args ) { - - // If the block didn't render any tags, then do not enqueue any styles. Rendering just an HTML comment is also excluded. - if ( trim( $content ) === '' ) { - $enqueue = false; - } else { - $processor = new WP_HTML_Tag_Processor( $content ); - $enqueue = $processor->next_tag(); - } - - /** This filter is documented in src/wp-includes/class-wp-block.php */ - if ( ! (bool) apply_filters( 'enqueue_empty_block_content_assets', $enqueue, $block_name ) ) { - return $content; - } - + $callback = static function ( $content ) use ( $args ) { // Register the stylesheet. if ( ! empty( $args['src'] ) ) { wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] ); From 7358d5bab34d453e7aa9ef792fb2cbc858598c5d Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 9 Oct 2025 13:14:00 -0700 Subject: [PATCH 09/18] Expose queue on WP_Script_Modules for parity with WP_Scripts and WP_Styles --- src/wp-includes/class-wp-script-modules.php | 45 ++++++++----------- .../tests/script-modules/wpScriptModules.php | 25 +++++++++++ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/wp-includes/class-wp-script-modules.php b/src/wp-includes/class-wp-script-modules.php index 08d08a5d1a65e..52537b1a3412e 100644 --- a/src/wp-includes/class-wp-script-modules.php +++ b/src/wp-includes/class-wp-script-modules.php @@ -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. * - * @since 6.5.0 - * @var array + * @since 6.9.0 + * @var string[] */ - private $enqueued_before_registered = array(); + public $queue = array(); /** * Tracks whether the @wordpress/a11y script module is available. @@ -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, ); @@ -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; } } @@ -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 ) ); } /** @@ -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 ] ); } /** @@ -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( '', esc_url( $this->get_src( $id ) ), @@ -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 ); @@ -359,13 +353,10 @@ private function get_import_map(): array { * @return 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 + ); } /** @@ -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; } diff --git a/tests/phpunit/tests/script-modules/wpScriptModules.php b/tests/phpunit/tests/script-modules/wpScriptModules.php index 35b96cd3e4af4..18c14a25cfc62 100644 --- a/tests/phpunit/tests/script-modules/wpScriptModules.php +++ b/tests/phpunit/tests/script-modules/wpScriptModules.php @@ -1343,6 +1343,31 @@ public function test_set_fetchpriority_with_invalid_value() { $this->assertSame( 'auto', $registered_modules['foo']['fetchpriority'] ); } + /** + * Tests that directly manipulating the queue works as expected. + * + * @ticket 63676 + * + * @covers WP_Script_Modules::queue + * @covers WP_Script_Modules::dequeue + */ + public function test_direct_queue_manipulation() { + $this->script_modules->register( 'foo', '/foo.js' ); + $this->script_modules->register( 'bar', '/bar.js' ); + $this->script_modules->register( 'baz', '/baz.js' ); + $this->assertSame( array(), $this->script_modules->queue ); + $this->script_modules->enqueue( 'foo' ); + $this->script_modules->enqueue( 'foo' ); + $this->script_modules->enqueue( 'bar' ); + $this->assertSame( array( 'foo', 'bar' ), $this->script_modules->queue ); + $this->script_modules->queue = array( 'baz' ); + $this->script_modules->enqueue( 'bar' ); + $this->assertSame( array( 'baz', 'bar' ), $this->script_modules->queue ); + $this->script_modules->dequeue( 'baz' ); + $this->script_modules->dequeue( 'bar' ); + $this->assertSame( array(), $this->script_modules->queue ); + } + /** * Gets registered script modules. * From 90a9b4ef94be8459c16517a4d3ef04512cb58c39 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 9 Oct 2025 13:20:12 -0700 Subject: [PATCH 10/18] Capture assets enqueued during render and include in queues if block content is not empty --- src/wp-includes/class-wp-block.php | 34 +++++ tests/phpunit/tests/blocks/wpBlock.php | 175 +++++++++++++++++++++---- 2 files changed, 184 insertions(+), 25 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index b498c1189700e..aec6027eef907 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -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. @@ -661,6 +669,32 @@ 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; + + // Merge the newly enqueued assets with the existing assets if the rendered block is not empty. + if ( + 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 ) + ) { + wp_styles()->queue = array_unique( array_merge( wp_styles()->queue, $new_styles_queue ) ); + wp_scripts()->queue = array_unique( array_merge( wp_scripts()->queue, $new_scripts_queue ) ); + wp_script_modules()->queue = array_unique( array_merge( wp_script_modules()->queue, $new_script_modules_queue ) ); + } + return $block_content; } } diff --git a/tests/phpunit/tests/blocks/wpBlock.php b/tests/phpunit/tests/blocks/wpBlock.php index d274de34ef025..952d365cfa7ce 100644 --- a/tests/phpunit/tests/blocks/wpBlock.php +++ b/tests/phpunit/tests/blocks/wpBlock.php @@ -368,24 +368,79 @@ public function test_render_applies_dynamic_render_block_filter() { * @return array */ public function data_provider_test_render_enqueues_scripts_and_styles(): array { + $block_markup = ' + +
+ +
First child
+ + + +
Last child
+ +
+ + '; + + // TODO: Add case where a dynamic block renders other blocks? return array( - 'all_printed' => array( + 'all_printed' => array( 'set_up' => null, - 'expected_rendered_block' => '

Hello World!

', - 'expected_styles' => array( 'static-view-style', 'dynamic-view-style' ), - 'expected_scripts' => array( 'static-view-script', 'dynamic-view-script' ), - 'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ), + 'block_markup' => $block_markup, + 'expected_rendered_block' => ' +
+
First child
+

Hello World!

+
Last child
+
+ ', + 'expected_styles' => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ), + ), + 'all_printed_with_extra_asset_via_filter' => array( + 'set_up' => static function () { + add_filter( + 'render_block_core/dynamic', + static function ( $content ) { + wp_enqueue_style( 'dynamic-extra', home_url( '/dynamic-extra.css' ), array(), null ); + $processor = new WP_HTML_Tag_Processor( $content ); + if ( $processor->next_tag() ) { + $processor->add_class( 'filtered' ); + $content = $processor->get_updated_html(); + } + return $content; + } + ); + }, + 'block_markup' => $block_markup, + 'expected_rendered_block' => ' +
+
First child
+

Hello World!

+
Last child
+
+ ', + 'expected_styles' => array( 'static-view-style', 'dynamic-extra', 'static-child-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ), ), - 'dynamic_hidden_scripts_omitted' => array( + 'dynamic_hidden_assets_omitted' => array( 'set_up' => static function () { add_filter( 'render_block_core/dynamic', '__return_empty_string' ); }, - 'expected_rendered_block' => '
', - 'expected_styles' => array( 'static-view-style' ), - 'expected_scripts' => array( 'static-view-script' ), - 'expected_script_modules' => array( 'static-view-script-module' ), + 'block_markup' => $block_markup, + 'expected_rendered_block' => ' +
+
First child
+
Last child
+
+ ', + 'expected_styles' => array( 'static-view-style', 'static-child-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'static-child-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module' ), ), - 'dynamic_hidden_scripts_included' => array( + 'dynamic_hidden_assets_included' => array( 'set_up' => static function () { add_filter( 'render_block_core/dynamic', '__return_empty_string' ); add_filter( @@ -400,39 +455,93 @@ static function ( $enqueue, $block_name ) { 2 ); }, - 'expected_rendered_block' => '
', - 'expected_styles' => array( 'static-view-style', 'dynamic-view-style' ), - 'expected_scripts' => array( 'static-view-script', 'dynamic-view-script' ), - 'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ), + 'block_markup' => $block_markup, + 'expected_rendered_block' => ' +
+
First child
+
Last child
+
+ ', + 'expected_styles' => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ), ), - 'static_hidden_scripts_omitted' => array( + 'static_hidden_assets_omitted' => array( 'set_up' => static function () { add_filter( 'render_block_core/static', '__return_empty_string' ); + add_filter( + 'render_block_core/dynamic', + static function ( $content ) { + wp_enqueue_style( 'dynamic-extra', home_url( '/dynamic-extra.css' ), array(), null ); + return $content; + } + ); }, + 'block_markup' => $block_markup, 'expected_rendered_block' => '', - // TODO: This test is currently failing. Since the inner dynamic block is rendered, it is getting its assets enqueued even though the parent static block is not rendered. When the parent is not rendered, then the nested assets really shouldn't be enqueued. 'expected_styles' => array(), 'expected_scripts' => array(), 'expected_script_modules' => array(), ), - 'all_hidden_scripts_omitted' => array( + 'static_child_hidden_assets_omitted' => array( + 'set_up' => static function () { + add_filter( 'render_block_core/static-child', '__return_empty_string' ); + }, + 'block_markup' => $block_markup, + 'expected_rendered_block' => ' +
+

Hello World!

+
+ ', + 'expected_styles' => array( 'static-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ), + ), + 'last_static_child_hidden_assets_omitted' => array( + 'set_up' => static function () { + add_filter( + 'render_block_core/static-child', + static function ( $content ) { + if ( str_contains( $content, 'Last child' ) ) { + $content = ''; + } + return $content; + }, + 10, + 3 + ); + }, + 'block_markup' => $block_markup, + 'expected_rendered_block' => ' +
+
First child
+

Hello World!

+
+ ', + 'expected_styles' => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ), + ), + 'all_hidden_assets_omitted' => array( 'set_up' => static function () { add_filter( 'render_block', '__return_empty_string' ); }, + 'block_markup' => $block_markup, 'expected_rendered_block' => '', 'expected_styles' => array(), 'expected_scripts' => array(), 'expected_script_modules' => array(), ), - 'all_hidden_scripts_included' => array( + 'all_hidden_assets_included' => array( 'set_up' => static function () { add_filter( 'render_block', '__return_empty_string' ); add_filter( 'enqueue_empty_block_content_assets', '__return_true' ); }, + 'block_markup' => $block_markup, 'expected_rendered_block' => '', - 'expected_styles' => array( 'static-view-style', 'dynamic-view-style' ), - 'expected_scripts' => array( 'static-view-script', 'dynamic-view-script' ), - 'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ), + 'expected_styles' => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ), + 'expected_scripts' => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ), + 'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ), ), ); } @@ -444,11 +553,12 @@ static function ( $enqueue, $block_name ) { * @dataProvider data_provider_test_render_enqueues_scripts_and_styles * * @param Closure|null $set_up + * @param string $block_markup * @param string[] $expected_styles * @param string[] $expected_scripts * @param string[] $expected_script_modules */ - public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, string $expected_rendered_block, array $expected_styles, array $expected_scripts, array $expected_script_modules ) { + public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, string $block_markup, string $expected_rendered_block, array $expected_styles, array $expected_scripts, array $expected_script_modules ) { if ( $set_up instanceof Closure ) { $set_up(); } @@ -464,6 +574,18 @@ public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, strin ) ); + wp_register_style( 'static-child-view-style', home_url( '/static-child-view-style.css' ) ); + wp_register_script( 'static-child-view-script', home_url( '/static-child-view-script.js' ) ); + wp_register_script_module( 'static-child-view-script-module', home_url( '/static-child-view-script-module.js' ) ); + $this->registry->register( + 'core/static-child', + array( + 'view_style_handles' => array( 'static-child-view-style' ), + 'view_script_handles' => array( 'static-child-view-script' ), + 'view_script_module_ids' => array( 'static-child-view-script-module' ), + ) + ); + wp_register_style( 'dynamic-view-style', home_url( '/dynamic-view-style.css' ) ); wp_register_script( 'dynamic-view-script', home_url( '/dynamic-view-script.js' ) ); wp_register_script_module( 'dynamic-view-script-module', home_url( '/dynamic-view-script-module.js' ) ); @@ -479,7 +601,8 @@ public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, strin ) ); - $parsed_blocks = parse_blocks( '
' ); + // TODO: Why not use do_blocks() instead? + $parsed_blocks = parse_blocks( trim( $block_markup ) ); $parsed_block = $parsed_blocks[0]; $context = array(); $block = new WP_Block( $parsed_block, $context, $this->registry ); @@ -487,7 +610,9 @@ public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, strin $this->assertEqualHTML( $expected_rendered_block, - $rendered_block + $rendered_block, + '', + "Snapshot:\n$rendered_block" ); remove_action( 'wp_print_styles', 'print_emoji_styles' ); From 7460f61e5986fd5415313be91bdea6d25500a4e1 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Oct 2025 15:59:48 -0700 Subject: [PATCH 11/18] Update assertion message Co-authored-by: Peter Wilson ', - "Snapshot:\n$rendered_block" + "Rendered block does not contain expected HTML:\n$rendered_block" ); remove_action( 'wp_print_styles', 'print_emoji_styles' ); From df2d01817d7a188746fec400eca17693089a7db0 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Oct 2025 16:00:02 -0700 Subject: [PATCH 12/18] Update assertion message Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> --- tests/phpunit/tests/blocks/wpBlock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/blocks/wpBlock.php b/tests/phpunit/tests/blocks/wpBlock.php index eab977284d450..7277d7e52c6d0 100644 --- a/tests/phpunit/tests/blocks/wpBlock.php +++ b/tests/phpunit/tests/blocks/wpBlock.php @@ -625,7 +625,7 @@ public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, strin $actual_styles[] = $matches[1]; } } - $this->assertSameSets( $expected_styles, $actual_styles ); + $this->assertSameSets( $expected_styles, $actual_styles, 'Enqueued styles do not meet expectations' ); $actual_scripts = array(); $printed_scripts = get_echo( 'wp_print_scripts' ); From 2ac38f583b5e77f332be35945ab0411c93175b0f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Oct 2025 16:00:10 -0700 Subject: [PATCH 13/18] Update assertion message Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> --- tests/phpunit/tests/blocks/wpBlock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/blocks/wpBlock.php b/tests/phpunit/tests/blocks/wpBlock.php index 7277d7e52c6d0..a364d9ee1e7d4 100644 --- a/tests/phpunit/tests/blocks/wpBlock.php +++ b/tests/phpunit/tests/blocks/wpBlock.php @@ -635,7 +635,7 @@ public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, strin $actual_scripts[] = $matches[1]; } } - $this->assertSameSets( $expected_scripts, $actual_scripts ); + $this->assertSameSets( $expected_scripts, $actual_scripts, 'Enqueued scripts do not meet expectations' ); $actual_script_modules = array(); $printed_script_modules = get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) ); From c9b892e17528d4437deda06976d8917c6a16d014 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Oct 2025 16:00:27 -0700 Subject: [PATCH 14/18] Update assertion message Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> --- tests/phpunit/tests/blocks/wpBlock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/blocks/wpBlock.php b/tests/phpunit/tests/blocks/wpBlock.php index a364d9ee1e7d4..39ab3712b1842 100644 --- a/tests/phpunit/tests/blocks/wpBlock.php +++ b/tests/phpunit/tests/blocks/wpBlock.php @@ -645,7 +645,7 @@ public function test_render_enqueues_scripts_and_styles( ?Closure $set_up, strin $actual_script_modules[] = $matches[1]; } } - $this->assertSameSets( $expected_script_modules, $actual_script_modules ); + $this->assertSameSets( $expected_script_modules, $actual_script_modules, 'Enqueued script modules do not meet expectations' ); } /** From 29a46d4cbba3c195910dad316c216ec7edfefe8b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Oct 2025 16:03:46 -0700 Subject: [PATCH 15/18] Add messages to test_direct_queue_manipulation Co-authored-by: Peter Wilson --- tests/phpunit/tests/script-modules/wpScriptModules.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/script-modules/wpScriptModules.php b/tests/phpunit/tests/script-modules/wpScriptModules.php index 18c14a25cfc62..a912a0e95b268 100644 --- a/tests/phpunit/tests/script-modules/wpScriptModules.php +++ b/tests/phpunit/tests/script-modules/wpScriptModules.php @@ -1355,17 +1355,17 @@ public function test_direct_queue_manipulation() { $this->script_modules->register( 'foo', '/foo.js' ); $this->script_modules->register( 'bar', '/bar.js' ); $this->script_modules->register( 'baz', '/baz.js' ); - $this->assertSame( array(), $this->script_modules->queue ); + $this->assertSame( array(), $this->script_modules->queue, 'Expected queue to be empty.' ); $this->script_modules->enqueue( 'foo' ); $this->script_modules->enqueue( 'foo' ); $this->script_modules->enqueue( 'bar' ); - $this->assertSame( array( 'foo', 'bar' ), $this->script_modules->queue ); + $this->assertSame( array( 'foo', 'bar' ), $this->script_modules->queue, 'Expected two deduplicated queued items.' ); $this->script_modules->queue = array( 'baz' ); $this->script_modules->enqueue( 'bar' ); - $this->assertSame( array( 'baz', 'bar' ), $this->script_modules->queue ); + $this->assertSame( array( 'baz', 'bar' ), $this->script_modules->queue, 'Expected queue updated via setter and enqueue method to have two items.' ); $this->script_modules->dequeue( 'baz' ); $this->script_modules->dequeue( 'bar' ); - $this->assertSame( array(), $this->script_modules->queue ); + $this->assertSame( array(), $this->script_modules->queue, 'Expected queue to be empty after dequeueing both items.' ); } /** From 73a899aaa87ab06e63bf37a5b20014d821669f34 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Oct 2025 16:10:21 -0700 Subject: [PATCH 16/18] Ensure the globals are re-instantiated Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> --- tests/phpunit/tests/blocks/editor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/phpunit/tests/blocks/editor.php b/tests/phpunit/tests/blocks/editor.php index 6f6d7a9a9d187..ac7918885728f 100644 --- a/tests/phpunit/tests/blocks/editor.php +++ b/tests/phpunit/tests/blocks/editor.php @@ -35,6 +35,8 @@ public function set_up() { 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(); } From e7daf5122cb8e878a0608617f478238fe558e9fe Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Oct 2025 16:20:49 -0700 Subject: [PATCH 17/18] Fix phpcs issue --- tests/phpunit/tests/blocks/editor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/blocks/editor.php b/tests/phpunit/tests/blocks/editor.php index ac7918885728f..4241161388eb8 100644 --- a/tests/phpunit/tests/blocks/editor.php +++ b/tests/phpunit/tests/blocks/editor.php @@ -35,8 +35,8 @@ public function set_up() { 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 = null; + $wp_styles = null; wp_scripts(); wp_styles(); } From 92c44f4c30fa0825d99c7c0d97c015232836f5f1 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Oct 2025 16:37:14 -0700 Subject: [PATCH 18/18] Avoid needless calls to array_unique() and array_merge() when there is nothing to merge --- src/wp-includes/class-wp-block.php | 38 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index aec6027eef907..fc6cf86cfd2ba 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -676,23 +676,35 @@ public function render( $options = array() ) { 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 ( - 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 ) + ( $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 ) + ) ) { - wp_styles()->queue = array_unique( array_merge( wp_styles()->queue, $new_styles_queue ) ); - wp_scripts()->queue = array_unique( array_merge( wp_scripts()->queue, $new_scripts_queue ) ); - wp_script_modules()->queue = array_unique( array_merge( wp_script_modules()->queue, $new_script_modules_queue ) ); + 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;