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 <mike.jolley@me.com>

---------

Co-authored-by: Mike Jolley <mike.jolley@me.com>
This commit is contained in:
Joshua T Flowers 2024-10-18 07:37:08 -04:00 committed by GitHub
parent 7125986053
commit 67fb81095f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 318 additions and 2 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: performance
Cache block asset resource hints

View File

@ -1,4 +1,6 @@
<?php <?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks; namespace Automattic\WooCommerce\Blocks;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
@ -31,7 +33,7 @@ final class AssetsController {
/** /**
* Initialize class features. * Initialize class features.
*/ */
protected function init() { protected function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingPublic
add_action( 'init', array( $this, 'register_assets' ) ); add_action( 'init', array( $this, 'register_assets' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'register_and_enqueue_site_editor_assets' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'register_and_enqueue_site_editor_assets' ) );
add_filter( 'wp_resource_hints', array( $this, 'add_resource_hints' ), 10, 2 ); add_filter( 'wp_resource_hints', array( $this, 'add_resource_hints' ), 10, 2 );
@ -196,6 +198,50 @@ final class AssetsController {
return $urls; return $urls;
} }
/**
* Get the block asset resource hints in the cache or null if not found.
*
* @return array|null Array of resource hints.
*/
private function get_block_asset_resource_hints_cache() {
if ( wp_is_development_mode( 'plugin' ) ) {
return null;
}
$cache = get_site_transient( 'woocommerce_block_asset_resource_hints' );
$current_version = array(
'woocommerce' => 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. * Get resource hint for a block by name.
* *
@ -206,6 +252,13 @@ final class AssetsController {
if ( ! $filename ) { if ( ! $filename ) {
return array(); return array();
} }
$cached = $this->get_block_asset_resource_hints_cache();
if ( isset( $cached[ $filename ] ) ) {
return $cached[ $filename ];
}
$script_data = $this->api->get_script_data( $script_data = $this->api->get_script_data(
$this->api->get_block_asset_build_path( $filename ) $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'] ) ) ), array( esc_url( add_query_arg( 'ver', $script_data['version'], $script_data['src'] ) ) ),
$this->get_script_dependency_src_array( $script_data['dependencies'] ) $this->get_script_dependency_src_array( $script_data['dependencies'] )
); );
return array_map(
$data = array_map(
function ( $src ) { function ( $src ) {
return array( return array(
'href' => $src, 'href' => $src,
@ -222,6 +276,10 @@ final class AssetsController {
}, },
array_unique( array_filter( $resources ) ) array_unique( array_filter( $resources ) )
); );
$this->set_block_asset_resource_hints_cache( $filename, $data );
return $data;
} }
/** /**

View File

@ -0,0 +1,254 @@
<?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Tests\Blocks;
use Automattic\WooCommerce\Blocks\Assets\Api;
use Automattic\WooCommerce\Blocks\AssetsController as TestedAssetsController;
/**
* Unit tests for the PatternRegistry class.
*/
class AssetsController extends \WP_UnitTestCase {
/**
* Holds the mock Api instance.
*
* @var Api The mock API.
*/
private $api;
/**
* Holds the AssetsController under test.
*
* @var TestedAssetsController The AssetsController under test.
*/
private $block_types_controller;
/**
* Sets up a new TestedAssetsController so it can be tested.
*
* @return void
*/
protected function setUp(): void {
parent::setUp();
$this->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' => '<!-- wp:woocommerce/checkout -->',
);
$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,
);
}
}