Commit 5f95f9774cf for woocommerce
commit 5f95f9774cf2072549d9b9570012dc160503eea8
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date: Tue Oct 20 07:53:26 2026 +0200
[Performance] Lazy-load block template files content in the registry (#69749)
This PR optimizes \Automattic\WooCommerce\Blocks\BlockTemplatesRegistry::init to skip eager loading (8 files are read per request, which are not backed by OPcache). Instead, we lazily load the contents as needed.
diff --git a/plugins/woocommerce/changelog/performance-69369-blocks-lazy-get-block-file-template b/plugins/woocommerce/changelog/performance-69369-blocks-lazy-get-block-file-template
new file mode 100644
index 00000000000..c81faefbc97
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-69369-blocks-lazy-get-block-file-template
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Lazy-load block template files content in the registry.
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 978bbbd236d..659fd3ce65a 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -48471,12 +48471,6 @@ parameters:
count: 1
path: src/Blocks/BlockTemplatesRegistry.php
- -
- message: '#^Parameter \#2 \$args of function register_block_template expects array\{title\?\: string, description\?\: string, content\?\: string, post_types\?\: array\<string\>, plugin\?\: string\}, array\{title\: string, description\: string, content\: string\|false\} given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTemplatesRegistry.php
-
-
message: '#^Method Automattic\\WooCommerce\\Blocks\\BlockTypes\\AbstractBlock\:\:enqueue_assets\(\) has no return type specified\.$#'
identifier: missingType.return
diff --git a/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php b/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php
index ac5717e4c08..b2aa4e5a6ce 100644
--- a/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php
+++ b/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php
@@ -38,10 +38,18 @@ class BlockTemplatesRegistry {
*/
private $templates = array();
+ /**
+ * Authoritative map for registered template name ('woocommerce//slug') to its HTML file path.
+ *
+ * @var array<string,string>
+ */
+ private $template_paths = array();
+
/**
* Initialization method.
*/
public function init() {
+ $templates = array();
if ( BlockTemplateUtils::supports_block_templates( 'wp_template' ) ) {
$templates = array(
ProductCatalogTemplate::SLUG => new ProductCatalogTemplate(),
@@ -55,10 +63,10 @@ class BlockTemplatesRegistry {
OrderConfirmationTemplate::SLUG => new OrderConfirmationTemplate(),
SingleProductTemplate::SLUG => new SingleProductTemplate(),
);
- } else {
- $templates = array();
}
$templates[ ComingSoonTemplate::SLUG ] = new ComingSoonTemplate();
+
+ $template_parts = array();
if ( BlockTemplateUtils::supports_block_templates( 'wp_template_part' ) ) {
$template_parts = array(
MiniCartTemplate::SLUG => new MiniCartTemplate(),
@@ -82,28 +90,27 @@ class BlockTemplatesRegistry {
}
}
}
- } else {
- $template_parts = array();
}
- // Init all templates.
$directory = BlockTemplateUtils::get_templates_directory( 'wp_template' );
foreach ( $templates as $template ) {
$template->init();
- // Taxonomy templates are registered automatically by WordPress and are made available through the Add Template menu.
+ // Taxonomy templates are registered automatically by WordPress and are added through the Add Template menu.
if ( ! $template->is_taxonomy_template ) {
+ $template_name = 'woocommerce//' . $template::SLUG;
+ $this->template_paths[ $template_name ] = $directory . '/' . $template::SLUG . '.html';
register_block_template(
- 'woocommerce//' . $template::SLUG,
+ $template_name,
array(
'title' => $template->get_template_title(),
'description' => $template->get_template_description(),
- // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
- 'content' => file_get_contents( $directory . '/' . $template::SLUG . '.html' ),
)
);
}
}
+ add_filter( 'pre_get_block_templates', array( $this, 'lazy_load_requested_templates_content' ), 0, 3 );
+ add_filter( 'pre_get_block_file_template', array( $this, 'lazy_load_single_template_content' ), 0, 3 );
foreach ( $template_parts as $template_part ) {
$template_part->init();
@@ -129,6 +136,52 @@ class BlockTemplatesRegistry {
return array_merge( $default_area_definitions, array( $add_to_cart_with_options_template_part_area ) );
}
+ /**
+ * Lazily load registered WooCommerce template file contents into the registry.
+ *
+ * @since 11.4.0
+ * @internal
+ *
+ * @param mixed $result Pass-through (null to let WP continue).
+ * @param array $query Query arguments.
+ * @param string $template_type Template type.
+ * @return mixed Unmodified $result.
+ */
+ public function lazy_load_requested_templates_content( $result, $query, $template_type ) {
+ if ( 'wp_template' === $template_type && ! empty( $this->template_paths ) && ! isset( $query['wp_id'] ) ) {
+ // On front-end pages slug__in defines the target slugs; in other contexts we need to load all templates.
+ $target_templates = array_map( static fn( $slug ) => 'woocommerce//' . $slug, $query['slug__in'] ?? array() );
+ $target_templates = empty( $target_templates ) ? $this->template_paths : array_intersect_key( $this->template_paths, array_flip( $target_templates ) );
+ if ( ! empty( $target_templates ) ) {
+ $registry = \WP_Block_Templates_Registry::get_instance();
+ foreach ( $target_templates as $template_name => $template_path ) {
+ $template = $registry->get_registered( $template_name );
+ if ( $template && '' === $template->content ) {
+ $template->content = (string) file_get_contents( $template_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions
+ }
+ unset( $this->template_paths[ $template_name ] );
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Covers the singular resolution path (get_block_file_template) which bypasses get_block_templates() entirely.
+ *
+ * @since 11.4.0
+ * @internal
+ *
+ * @param mixed $result Pass-through (null to let WP continue).
+ * @param string $id Template identifier (e.g. 'theme_name//slug').
+ * @param string $template_type Template type.
+ * @return mixed Unmodified $result.
+ */
+ public function lazy_load_single_template_content( $result, $id, $template_type ) {
+ return $this->lazy_load_requested_templates_content( $result, array( 'slug__in' => array( explode( '//', $id, 2 )[1] ?? '' ) ), $template_type );
+ }
+
/**
* Returns the template matching the slug
*
@@ -137,10 +190,6 @@ class BlockTemplatesRegistry {
* @return AbstractTemplate|AbstractTemplatePart|null
*/
public function get_template( $template_slug ) {
- if ( array_key_exists( $template_slug, $this->templates ) ) {
- $registered_template = $this->templates[ $template_slug ];
- return $registered_template;
- }
- return null;
+ return $this->templates[ $template_slug ] ?? null;
}
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTemplatesRegistryTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTemplatesRegistryTest.php
new file mode 100644
index 00000000000..031d0af6272
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTemplatesRegistryTest.php
@@ -0,0 +1,36 @@
+<?php declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks;
+
+use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
+use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the BlockTemplatesRegistry lazy-load content population.
+ */
+final class BlockTemplatesRegistryTest extends WC_Unit_Test_Case {
+ /**
+ * @testdox lazy_load_requested_templates_content populates a template matching slug__in.
+ */
+ public function test_lazy_load_populates_template_by_slug(): void {
+ $sut = wc_get_container()->get( BlockTemplatesRegistry::class );
+ // On classic themes Bootstrap skips init(), so register the hook directly.
+ add_filter( 'pre_get_block_templates', array( $sut, 'lazy_load_requested_templates_content' ), 0, 3 );
+
+ // Inject the checkout template path (init() on classic theme only registers ComingSoonTemplate).
+ $directory = BlockTemplateUtils::get_templates_directory( 'wp_template' );
+ $reflection = new \ReflectionProperty( BlockTemplatesRegistry::class, 'template_paths' );
+ $reflection->setAccessible( true );
+ $reflection->setValue( $sut, array_merge( $reflection->getValue( $sut ), array( 'woocommerce//page-checkout' => $directory . '/page-checkout.html' ) ) );
+
+ register_block_template( 'woocommerce//page-checkout', array( 'title' => 'Checkout' ) );
+ $template = \WP_Block_Templates_Registry::get_instance()->get_registered( 'woocommerce//page-checkout' );
+
+ $this->assertSame( '', $template->content, 'Template content should start empty before lazy-load.' );
+
+ get_block_templates( array( 'slug__in' => array( 'page-checkout' ) ), 'wp_template' );
+
+ $this->assertStringContainsString( '<main class="wp-block-group">', $template->content, 'Content should contain WooCommerce markup.' );
+ }
+}