From 08402d39f6407d4ebc3529cb4a617ec06cfecb9a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 14 Oct 2025 00:12:28 +0000 Subject: [PATCH] Editor: Avoid enqueueing assets for blocks which do not render content. This change prevents scripts, styles, and script modules from being enqueued for blocks that do not render any HTML content. This is common for hidden blocks or blocks like the Featured Image block when no image is present. This change reduces the amount of unused CSS and JavaScript on a page, improving performance. A new filter, `enqueue_empty_block_content_assets`, is introduced to allow developers to override this behavior and enqueue assets for empty blocks if needed. The implementation involves capturing the asset queues before and after a block is rendered. The newly enqueued assets are only merged if the block's rendered content is not empty. This is done recursively for nested blocks to ensure that assets for inner blocks are also not enqueued if a parent block is hidden. Developed in https://github.com/WordPress/wordpress-develop/pull/9213. Props westonruter, aristath, peterwilsoncc, gziolo, krupajnanda, dd32, jorbin. See #50328. Fixes #63676. Built from https://develop.svn.wordpress.org/trunk@60930 git-svn-id: http://core.svn.wordpress.org/trunk@60266 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp-block.php | 46 +++++++++++++++++++++++++ wp-includes/class-wp-script-modules.php | 45 ++++++++++-------------- wp-includes/version.php | 2 +- 3 files changed, 65 insertions(+), 28 deletions(-) diff --git a/wp-includes/class-wp-block.php b/wp-includes/class-wp-block.php index b498c11897..fc6cf86cfd 100644 --- a/wp-includes/class-wp-block.php +++ b/wp-includes/class-wp-block.php @@ -492,6 +492,14 @@ class WP_Block { 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,44 @@ class WP_Block { $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; } } diff --git a/wp-includes/class-wp-script-modules.php b/wp-includes/class-wp-script-modules.php index 08d08a5d1a..52537b1a34 100644 --- a/wp-includes/class-wp-script-modules.php +++ b/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 @@ class WP_Script_Modules { $this->registered[ $id ] = array( 'src' => $src, 'version' => $version, - 'enqueue' => isset( $this->enqueued_before_registered[ $id ] ), 'dependencies' => $dependencies, 'fetchpriority' => $fetchpriority, ); @@ -213,13 +212,11 @@ class WP_Script_Modules { * } */ 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 @@ class WP_Script_Modules { * @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 @@ class WP_Script_Modules { * @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 @@ class WP_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 @@ class WP_Script_Modules { */ 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 @@ class WP_Script_Modules { * @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 @@ class WP_Script_Modules { */ 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/wp-includes/version.php b/wp-includes/version.php index 93daccbeea..ffd232d8fd 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.9-alpha-60929'; +$wp_version = '6.9-alpha-60930'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.