diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index fbf4fe2c52c3e..b413273a64974 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -307,8 +307,44 @@ function wp_add_global_styles_for_blocks() { $tree = WP_Theme_JSON_Resolver::get_merged_data(); $block_nodes = $tree->get_styles_block_nodes(); + + $can_use_cached = ! wp_is_development_mode( 'theme' ); + if ( $can_use_cached ) { + // Hash global settings and block nodes together to optimize performance of key generation. + $hash = md5( + wp_json_encode( + array( + 'global_setting' => wp_get_global_settings(), + 'block_nodes' => $block_nodes, + ) + ) + ); + + $cache_key = "wp_styles_for_blocks:$hash"; + $cached = get_site_transient( $cache_key ); + if ( ! is_array( $cached ) ) { + $cached = array(); + } + } + + $update_cache = false; + foreach ( $block_nodes as $metadata ) { - $block_css = $tree->get_styles_for_block( $metadata ); + + if ( $can_use_cached ) { + // Use the block name as the key for cached CSS data. Otherwise, use a hash of the metadata. + $cache_node_key = isset( $metadata['name'] ) ? $metadata['name'] : md5( wp_json_encode( $metadata ) ); + + if ( isset( $cached[ $cache_node_key ] ) ) { + $block_css = $cached[ $cache_node_key ]; + } else { + $block_css = $tree->get_styles_for_block( $metadata ); + $cached[ $cache_node_key ] = $block_css; + $update_cache = true; + } + } else { + $block_css = $tree->get_styles_for_block( $metadata ); + } if ( ! wp_should_load_separate_core_block_assets() ) { wp_add_inline_style( 'global-styles', $block_css ); @@ -354,6 +390,10 @@ function wp_add_global_styles_for_blocks() { } } } + + if ( $update_cache ) { + set_site_transient( $cache_key, $cached, HOUR_IN_SECONDS ); + } } /** diff --git a/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php b/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php index 7d34e8d2a6b33..fb547db50b763 100644 --- a/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php +++ b/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php @@ -75,6 +75,87 @@ public function test_third_party_blocks_inline_styles_get_registered_to_global_s ); } + /** + * Ensure that the block cache is set for global styles. + * + * @ticket 59595 + */ + public function test_styles_for_blocks_cache_is_set() { + $this->set_up_third_party_block(); + + wp_register_style( 'global-styles', false, array(), true, true ); + + $cache_key = $this->get_wp_styles_for_blocks_cache_key(); + $styles_for_blocks_before = get_site_transient( $cache_key ); + $this->assertFalse( $styles_for_blocks_before ); + + wp_add_global_styles_for_blocks(); + + $styles_for_blocks_after = get_site_transient( $cache_key ); + $this->assertNotEmpty( $styles_for_blocks_after ); + } + + /** + * Confirm that the block cache is skipped when in dev mode for themes. + * + * @ticket 59595 + */ + public function test_styles_for_blocks_skips_cache_in_dev_mode() { + global $_wp_tests_development_mode; + + $orig_dev_mode = $_wp_tests_development_mode; + + // Setting development mode to theme should skip the cache. + $_wp_tests_development_mode = 'theme'; + + wp_register_style( 'global-styles', false, array(), true, true ); + + // Initial register of global styles. + wp_add_global_styles_for_blocks(); + + $cache_key = $this->get_wp_styles_for_blocks_cache_key(); + $styles_for_blocks_initial = get_site_transient( $cache_key ); + + // Cleanup. + $_wp_tests_development_mode = $orig_dev_mode; + + $this->assertFalse( $styles_for_blocks_initial ); + } + + /** + * Confirm that the block cache is updated if the block meta has changed. + * + * @ticket 59595 + */ + public function test_styles_for_blocks_cache_is_skipped() { + wp_register_style( 'global-styles', false, array(), true, true ); + + // Initial register of global styles. + wp_add_global_styles_for_blocks(); + + $cache_key = $this->get_wp_styles_for_blocks_cache_key(); + $styles_for_blocks_initial = get_site_transient( $cache_key ); + $this->assertNotEmpty( $styles_for_blocks_initial, 'Initial cache was not set.' ); + + $this->set_up_third_party_block(); + + /* + * Call register of global styles again to ensure the cache is updated. + * In normal conditions, this function is only called once per request. + */ + wp_add_global_styles_for_blocks(); + + $cache_key = $this->get_wp_styles_for_blocks_cache_key(); + $styles_for_blocks_updated = get_site_transient( $cache_key ); + $this->assertNotEmpty( $styles_for_blocks_updated, 'Updated cache was not set.' ); + + $this->assertNotEquals( + $styles_for_blocks_initial, + $styles_for_blocks_updated, + 'Block style cache was not updated.' + ); + } + /** * @ticket 56915 * @ticket 61165 @@ -253,4 +334,25 @@ private function get_global_styles() { $actual = wp_styles()->get_data( 'global-styles', 'after' ); return is_array( $actual ) ? $actual : array(); } + + /** + * Get cache key for `wp_styles_for_blocks`. + * + * @return string The cache key. + */ + private function get_wp_styles_for_blocks_cache_key() { + $tree = WP_Theme_JSON_Resolver::get_merged_data(); + $block_nodes = $tree->get_styles_block_nodes(); + // md5 is a costly operation, so we hashing global settings and block_node in a single call. + $hash = md5( + wp_json_encode( + array( + 'global_setting' => wp_get_global_settings(), + 'block_nodes' => $block_nodes, + ) + ) + ); + + return "wp_styles_for_blocks:$hash"; + } }