From 67fb81095fe65ec8a1c70a90943b8f55fb529f29 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Fri, 18 Oct 2024 07:37:08 -0400 Subject: [PATCH] Cache block asset resource hints (#51874) * Cache block asset resource hints * Combine resource hints under single cache transient * Add AssetController tests * Add changelog entry * Skip cache use in development mode * Fix up lint errors * Revert init method back to protected and ignore linting Co-authored-by: Mike Jolley --------- Co-authored-by: Mike Jolley --- plugins/woocommerce/changelog/update-49230 | 4 + .../src/Blocks/AssetsController.php | 62 ++++- .../tests/php/src/Blocks/AssetsController.php | 254 ++++++++++++++++++ 3 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-49230 create mode 100644 plugins/woocommerce/tests/php/src/Blocks/AssetsController.php diff --git a/plugins/woocommerce/changelog/update-49230 b/plugins/woocommerce/changelog/update-49230 new file mode 100644 index 00000000000..6c259f575fb --- /dev/null +++ b/plugins/woocommerce/changelog/update-49230 @@ -0,0 +1,4 @@ +Significance: patch +Type: performance + +Cache block asset resource hints diff --git a/plugins/woocommerce/src/Blocks/AssetsController.php b/plugins/woocommerce/src/Blocks/AssetsController.php index 13236dc8145..e5420837568 100644 --- a/plugins/woocommerce/src/Blocks/AssetsController.php +++ b/plugins/woocommerce/src/Blocks/AssetsController.php @@ -1,4 +1,6 @@ WOOCOMMERCE_VERSION, + 'wordpress' => get_bloginfo( 'version' ), + ); + + if ( isset( $cache['version'] ) && $cache['version'] === $current_version ) { + return $cache['files']; + } + + return null; + } + + /** + * Set the block asset resource hints in the cache. + * + * @param string $filename File name. + * @param array $data Array of resource hints. + */ + private function set_block_asset_resource_hints_cache( $filename, $data ) { + $cache = $this->get_block_asset_resource_hints_cache(); + $updated = array( + 'files' => $cache ?? array(), + 'version' => array( + 'woocommerce' => WOOCOMMERCE_VERSION, + 'wordpress' => get_bloginfo( 'version' ), + ), + ); + + $updated['files'][ $filename ] = $data; + set_site_transient( 'woocommerce_block_asset_resource_hints', $updated, WEEK_IN_SECONDS ); + } + /** * Get resource hint for a block by name. * @@ -206,6 +252,13 @@ final class AssetsController { if ( ! $filename ) { return array(); } + + $cached = $this->get_block_asset_resource_hints_cache(); + + if ( isset( $cached[ $filename ] ) ) { + return $cached[ $filename ]; + } + $script_data = $this->api->get_script_data( $this->api->get_block_asset_build_path( $filename ) ); @@ -213,7 +266,8 @@ final class AssetsController { array( esc_url( add_query_arg( 'ver', $script_data['version'], $script_data['src'] ) ) ), $this->get_script_dependency_src_array( $script_data['dependencies'] ) ); - return array_map( + + $data = array_map( function ( $src ) { return array( 'href' => $src, @@ -222,6 +276,10 @@ final class AssetsController { }, array_unique( array_filter( $resources ) ) ); + + $this->set_block_asset_resource_hints_cache( $filename, $data ); + + return $data; } /** diff --git a/plugins/woocommerce/tests/php/src/Blocks/AssetsController.php b/plugins/woocommerce/tests/php/src/Blocks/AssetsController.php new file mode 100644 index 00000000000..a3b02f02023 --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Blocks/AssetsController.php @@ -0,0 +1,254 @@ +api = $this->createMock( Api::class ); + $this->assets_controller = new TestedAssetsController( $this->api ); + + // A block checkout or cart page must exist in order to have resource hints. + $page = array( + 'name' => 'block-checkout', + 'title' => 'Block Checkout', + 'content' => '', + ); + $page_id = wc_create_page( $page['name'], 'woocommerce_checkout_page_id', $page['title'], $page['content'] ); + + // Ensure a product exists in the cart unless the test specifies otherwise. + $product = \WC_Helper_Product::create_simple_product(); + wc()->cart->add_to_cart( $product->get_id() ); + + // Set up some mock dependencies. + global $wp_scripts; + $wp_scripts->registered['mock-dependency'] = (object) array( + 'ver' => '1.2.3', + 'src' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/mock-dependency.js', + 'deps' => array( + 'mock-sub-dependency', + ), + ); + $wp_scripts->registered['mock-sub-dependency'] = (object) array( + 'ver' => '1.2.3', + 'src' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/mock-sub-dependency.js', + 'deps' => array(), + ); + } + + /** + * Clean up mock dependencies and pages after each test. + * + * @return void + */ + public function tearDown(): void { + parent::tearDown(); + + wp_delete_post( get_option( 'woocommerce_checkout_page_id' ), true ); + delete_option( 'woocommerce_checkout_page_id' ); + + wc()->cart->empty_cart(); + + global $wp_scripts; + unset( $wp_scripts->registered['mock-dependency'] ); + unset( $wp_scripts->registered['mock-sub-dependency'] ); + } + + /** + * Tests that no additional resource hints are added on non-prefetch, non-prerender relations. + * + * @return void + */ + public function test_no_additional_resource_hints_added() { + $mock_urls = array( + array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/blocks/mock.js?ver=1.1.0', + 'as' => 'script', + ), + ); + $urls = $this->assets_controller->add_resource_hints( $mock_urls, 'mock_relation' ); + + $this->assertEquals( $mock_urls, $urls ); + } + + /** + * Tests that no additional resource hints are added on non-prefetch, non-prerender relations. + * + * @return void + */ + public function test_no_additional_resource_hints_added_on_non_prefetch_prerender_relation() { + $mock_urls = array( + array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/blocks/mock.js?ver=1.1.0', + 'as' => 'script', + ), + ); + $urls = $this->assets_controller->add_resource_hints( $mock_urls, 'mock_relation' ); + + $this->assertEquals( $mock_urls, $urls ); + } + + /** + * Tests that no additional resource hints are added on empty carts. + * + * @return void + */ + public function test_no_additional_resource_hints_added_on_empty_cart() { + wc()->cart->empty_cart(); + $mock_urls = array( + array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/blocks/mock.js?ver=1.1.0', + 'as' => 'script', + ), + ); + $urls = $this->assets_controller->add_resource_hints( $mock_urls, 'prefetch' ); + + $this->assertEquals( $mock_urls, $urls ); + } + + /** + * Tests that no additional resource hints are added on empty carts. + * + * @return void + */ + public function test_additional_resource_hints_added_when_block_cart_exists() { + $this->api->expects( $this->once() ) + ->method( 'get_script_data' ) + ->willReturn( + array( + 'version' => '1.2.3', + 'src' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/checkout.js', + 'dependencies' => array( + 'mock-dependency', + ), + ) + ); + + $mock_urls = array( + array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/blocks/mock.js?ver=1.1.0', + 'as' => 'script', + ), + ); + $urls = $this->assets_controller->add_resource_hints( $mock_urls, 'prefetch' ); + + $this->assertEquals( + array_merge( + $mock_urls, + array( + array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/checkout.js?ver=1.2.3', + 'as' => 'script', + ), + array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/mock-dependency.js?ver=1.2.3', + 'as' => 'script', + ), + array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/mock-sub-dependency.js?ver=1.2.3', + 'as' => 'script', + ), + ) + ), + $urls, + ); + } + + /** + * Tests that the additional resource hints uses the cache when available. + * + * @return void + */ + public function test_additional_resource_hints_cache() { + $mock_cache = array( + 'files' => array( + 'checkout-frontend' => array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/mock-cached.js?ver=1.2.3', + 'as' => 'script', + ), + ), + 'version' => array( + 'woocommerce' => WOOCOMMERCE_VERSION, + 'wordpress' => get_bloginfo( 'version' ), + ), + ); + set_site_transient( 'woocommerce_block_asset_resource_hints', $mock_cache ); + + $urls = $this->assets_controller->add_resource_hints( array(), 'prefetch' ); + + $this->assertEquals( + $mock_cache['files']['checkout-frontend'], + $urls, + ); + } + + /** + * Tests that the additional resource hints don't use the cache when the version is invalid. + * + * @return void + */ + public function test_additional_resource_hints_invalid_cache() { + $this->api->expects( $this->once() ) + ->method( 'get_script_data' ) + ->willReturn( + array( + 'version' => '1.2.3', + 'src' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/checkout.js', + 'dependencies' => array(), + ) + ); + + $mock_cache = array( + 'files' => array( + 'checkout-frontend' => array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/mock-cached.js?ver=1.2.3', + 'as' => 'script', + ), + ), + 'version' => array( + 'woocommerce' => WOOCOMMERCE_VERSION, + 'wordpress' => '0.1.0-old', + ), + ); + set_site_transient( 'woocommerce_block_asset_resource_hints', $mock_cache ); + + $urls = $this->assets_controller->add_resource_hints( array(), 'prefetch' ); + + $this->assertEquals( + array( + array( + 'href' => 'http://test.local/wp-content/plugins/woocommerce/assets/client/block/checkout.js?ver=1.2.3', + 'as' => 'script', + ), + ), + $urls, + ); + } +}