Skip to content

Conversation

@westonruter
Copy link
Member

@westonruter westonruter commented Jul 9, 2025

Trac ticket: https://core.trac.wordpress.org/ticket/63676

See new description in #9213 (comment)

Old Description

Given a published post on the single template on the Twenty Twenty-Five theme where:

  1. No featured image is assigned (so the Featured Image block renders nothing)
  2. Comments are disabled for the post (so the Comments block renders nothing)
  3. The Site Logo is not supplied, so its block content is empty.
  4. The Site Title is not defined, so its block content is also empty.
  5. A Cover block is added to the post, but the Block Visibility plugin is active and the block is marked as hidden for everyone.

This PR omits the CSS (and scripts) for the unrendered blocks, as seen from this diff:

--- before.html	2025-07-08 17:24:28
+++ after.html	2025-07-08 17:24:23
@@ -528,74 +528,6 @@
   text-decoration:inherit;
 }
 </style>
-<style id='wp-block-post-featured-image-inline-css'>
-.wp-block-post-featured-image{
-  margin-left:0;
-  margin-right:0;
-}
-.wp-block-post-featured-image a{
-  display:block;
-  height:100%;
-}
-.wp-block-post-featured-image :where(img){
-  box-sizing:border-box;
-  height:auto;
-  max-width:100%;
-  vertical-align:bottom;
-  width:100%;
-}
-.wp-block-post-featured-image.alignfull img,.wp-block-post-featured-image.alignwide img{
-  width:100%;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim{
-  background-color:#000;
-  inset:0;
-  position:absolute;
-}
-.wp-block-post-featured-image{
-  position:relative;
-}
-
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-gradient{
-  background-color:initial;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-0{
-  opacity:0;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-10{
-  opacity:.1;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-20{
-  opacity:.2;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-30{
-  opacity:.3;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-40{
-  opacity:.4;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-50{
-  opacity:.5;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-60{
-  opacity:.6;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-70{
-  opacity:.7;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-80{
-  opacity:.8;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-90{
-  opacity:.9;
-}
-.wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-100{
-  opacity:1;
-}
-.wp-block-post-featured-image:where(.alignleft,.alignright){
-  width:100%;
-}
-</style>
 <style id='wp-block-paragraph-inline-css'>
 .is-small-text{
   font-size:.875em;
@@ -657,7 +589,6 @@
   white-space:pre-wrap;
 }
 </style>
-<link rel='stylesheet' id='wp-block-cover-css' href='http://localhost:8000/wp-includes/blocks/cover/style.css?ver=6.9-alpha-60093-src' media='all' />
 <style id='wp-block-list-inline-css'>
 ol,ul{
   box-sizing:border-box;
@@ -697,116 +628,7 @@
 }
 .wp-block-post-navigation-link.has-text-align-left[style*="writing-mode: vertical-lr"],.wp-block-post-navigation-link.has-text-align-right[style*="writing-mode: vertical-rl"]{
   rotate:180deg;
-}
-</style>
-<style id='wp-block-comments-inline-css'>
-.wp-block-post-comments{
-  box-sizing:border-box;
-}
-.wp-block-post-comments .alignleft{
-  float:left;
-}
-.wp-block-post-comments .alignright{
-  float:right;
-}
-.wp-block-post-comments .navigation:after{
-  clear:both;
-  content:"";
-  display:table;
-}
-.wp-block-post-comments .commentlist{
-  clear:both;
-  list-style:none;
-  margin:0;
-  padding:0;
-}
-.wp-block-post-comments .commentlist .comment{
-  min-height:2.25em;
-  padding-left:3.25em;
-}
-.wp-block-post-comments .commentlist .comment p{
-  font-size:1em;
-  line-height:1.8;
-  margin:1em 0;
-}
-.wp-block-post-comments .commentlist .children{
-  list-style:none;
-  margin:0;
-  padding:0;
-}
-.wp-block-post-comments .comment-author{
-  line-height:1.5;
-}
-.wp-block-post-comments .comment-author .avatar{
-  border-radius:1.5em;
-  display:block;
-  float:left;
-  height:2.5em;
-  margin-right:.75em;
-  margin-top:.5em;
-  width:2.5em;
-}
-.wp-block-post-comments .comment-author cite{
-  font-style:normal;
-}
-.wp-block-post-comments .comment-meta{
-  font-size:.875em;
-  line-height:1.5;
-}
-.wp-block-post-comments .comment-meta b{
-  font-weight:400;
-}
-.wp-block-post-comments .comment-meta .comment-awaiting-moderation{
-  display:block;
-  margin-bottom:1em;
-  margin-top:1em;
-}
-.wp-block-post-comments .comment-body .commentmetadata{
-  font-size:.875em;
-}
-.wp-block-post-comments .comment-form-author label,.wp-block-post-comments .comment-form-comment label,.wp-block-post-comments .comment-form-email label,.wp-block-post-comments .comment-form-url label{
-  display:block;
-  margin-bottom:.25em;
-}
-.wp-block-post-comments .comment-form input:not([type=submit]):not([type=checkbox]),.wp-block-post-comments .comment-form textarea{
-  box-sizing:border-box;
-  display:block;
-  width:100%;
-}
-.wp-block-post-comments .comment-form-cookies-consent{
-  display:flex;
-  gap:.25em;
-}
-.wp-block-post-comments .comment-form-cookies-consent #wp-comment-cookies-consent{
-  margin-top:.35em;
-}
-.wp-block-post-comments .comment-reply-title{
-  margin-bottom:0;
-}
-.wp-block-post-comments .comment-reply-title :where(small){
-  font-size:var(--wp--preset--font-size--medium, smaller);
-  margin-left:.5em;
-}
-.wp-block-post-comments .reply{
-  font-size:.875em;
-  margin-bottom:1.4em;
 }
-.wp-block-post-comments input:not([type=submit]),.wp-block-post-comments textarea{
-  border:1px solid #949494;
-  font-family:inherit;
-  font-size:1em;
-}
-.wp-block-post-comments input:not([type=submit]):not([type=checkbox]),.wp-block-post-comments textarea{
-  padding:calc(.667em + 2px);
-}
-
-:where(.wp-block-post-comments input[type=submit]){
-  border:none;
-}
-
-.wp-block-comments{
-  box-sizing:border-box;
-}
 </style>
 <style id='wp-block-heading-inline-css'>
 h1.has-background,h2.has-background,h3.has-background,h4.has-background,h5.has-background,h6.has-background{
@@ -878,42 +700,7 @@
 .wp-block-post-template-is-layout-constrained>li>.aligncenter,.wp-block-post-template-is-layout-flow>li>.aligncenter{
   margin-inline-end:auto;
   margin-inline-start:auto;
-}
-</style>
-<style id='wp-block-site-logo-inline-css'>
-.wp-block-site-logo{
-  box-sizing:border-box;
-  line-height:0;
-}
-.wp-block-site-logo a{
-  display:inline-block;
-  line-height:0;
-}
-.wp-block-site-logo.is-default-size img{
-  height:auto;
-  width:120px;
 }
-.wp-block-site-logo img{
-  height:auto;
-  max-width:100%;
-}
-.wp-block-site-logo a,.wp-block-site-logo img{
-  border-radius:inherit;
-}
-.wp-block-site-logo.aligncenter{
-  margin-left:auto;
-  margin-right:auto;
-  text-align:center;
-}
-
-:root :where(.wp-block-site-logo.is-style-rounded){
-  border-radius:9999px;
-}
-</style>
-<style id='wp-block-site-tagline-inline-css'>
-.wp-block-site-tagline{
-  box-sizing:border-box;
-}
 </style>
 <style id='wp-block-spacer-inline-css'>
 .wp-block-spacer{
@@ -1224,7 +1011,6 @@
 :root :where(.wp-block-post-terms){font-size: var(--wp--preset--font-size--small);font-weight: 600;}:root :where(.wp-block-post-terms a){white-space: nowrap;}
 :root :where(.wp-block-post-title a:where(:not(.wp-element-button))){text-decoration: none;}
 :root :where(.wp-block-post-title a:where(:not(.wp-element-button)):hover){text-decoration: underline;}
-:root :where(.wp-block-site-tagline){font-size: var(--wp--preset--font-size--medium);}
 :root :where(.wp-block-site-title){font-weight: 700;letter-spacing: -.5px;}
 :root :where(.wp-block-site-title a:where(:not(.wp-element-button))){text-decoration: none;}
 :root :where(.wp-block-site-title a:where(:not(.wp-element-button)):hover){text-decoration: underline;}

This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@westonruter westonruter requested review from aristath and gziolo July 9, 2025 00:49
@github-actions
Copy link

github-actions bot commented Jul 9, 2025

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Copy link
Member

@aristath aristath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good and it makes sense.
Tested and works as expected.
This should reduce the CSS significantly for sites using block themes where the templates include blocks that don't actually print anything on the page because of the structure of individual posts/pages etc 👍

@gziolo
Copy link
Member

gziolo commented Jul 9, 2025

This should work great for CSS because you need at least one tag so the rules make sense.

I'm curious how it will play out with JavaScript. For the Interactivity API, you need to have a wrapping element that enables an interactive region so the proposed logic fits nicely. For regular scripts, there might be a more nuanced reality. In majority of cases it should work correctly, but I'm not entirely sure what the long tail could be. Is it possible that a custom block renders nothing but still runs some arbitrary JS code?

@westonruter
Copy link
Member Author

@gziolo:

I'm curious how it will play out with JavaScript. For the Interactivity API, you need to have a wrapping element that enables an interactive region so the proposed logic fits nicely. For regular scripts, there might be a more nuanced reality. In majority of cases it should work correctly, but I'm not entirely sure what the long tail could be. Is it possible that a custom block renders nothing but still runs some arbitrary JS code?

I think this corresponds to the feedback left by @dd32:

I can imagine someone could have a block that renders nothing, but enqueues scripts that affect the page in some manner. ¶ It would normally make sense that a block with scripts like that would render a placeholder tag for where the script would affect, but I guess it would be possible for a script to affect the entire page instead of needing that placeholder tag.

And likewise from @aaronjorbin:

My first thought is the same as @dd32. While I haven't done this or seen it, it is absolutely something I would do and something that I don't think should be completely blocked from happening. ¶ That said, i think it would still make sense to do this. I would like to see it be overwritable by a filter though.

I suppose then the value of $processor->next_tag() should be put into a variable, and then filtered so that plugins can override whether the scripts/styles are enqueued?

All: How about something like this?

$processor = new WP_HTML_Tag_Processor( $content );
$enqueue   = $processor->next_tag();

/**
 * Filters whether to enqueue assets for a block which has no rendered content.
 *
 * @since n.e.x.t
 *
 * @param bool   $enqueue    Whether to enqueue assets.
 * @param string $block_name Block name.
 */
$enqueue = (bool) apply_filters( 'wp_enqueue_empty_block_content_assets', $enqueue, $block_name ); 
if ( $enqueue ) {
   /* ... */
}

@westonruter
Copy link
Member Author

@gziolo @dd32 @aaronjorbin How about f3209f6? With that in place, you can force the assets for a rendered block to be enqueued via the enqueue_empty_block_content_assets filter. For example, to force the CSS for the Featured Image block to be printed even when there is no featured image assigned to the current page/post:

add_filter(
	'enqueue_empty_block_content_assets',
	function ( $enqueue, $block_name ) {
		if ( 'core/post-featured-image' === $block_name ) {
			$enqueue = true;
		}
		return $enqueue;
	},
	10,
	2
);

If that looks good, I'll proceed with adding tests so that this can be advanced for review.

@gziolo
Copy link
Member

gziolo commented Jul 29, 2025

The solution proposed, which includes a filter that allows for enqueuing assets even when no content is printed, seems to be spot on. There may be some backward compatibility considerations, but these should be noted in the dev note with details on how to address them, if necessary.

@westonruter
Copy link
Member Author

Thank you! I'll proceed with adding tests so this can be moved to review and commit.

@westonruter
Copy link
Member Author

westonruter commented Jul 29, 2025

In 59c3543 I've started adding tests, but I found a complication related to inner blocks. If an inner block has its assets enqueued, but the outer block is filtered to be hidden, then ideally the inner block's assets should be omitted from being rendered. This is not the case right now, however.

@gziolo
Copy link
Member

gziolo commented Jul 29, 2025

The challenge is that rendering occurs from the bottom up. It begins with the deeply nested inner blocks before rendering their ancestors. That's why, at the time of rendering an inner block, you don't know whether any of the parent blocks might decide to, for some reason, render nothing.

Copy link
Contributor

@peterwilsoncc peterwilsoncc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance question inline.

The change would need to be made in multiple locations.

Copy link
Member Author

@westonruter westonruter Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes here are discussed in Slack: https://wordpress.slack.com/archives/C02QB2JS7/p1759958061791369

They are needed to allow npm run test:php -- --group=blocks to run without errors. Otherwise, this occurs:

Tests_Blocks_Editor::test_get_block_editor_settings_overrides_default_settings_all_editors
Attempt to read property "registered" on null

/var/www/src/wp-includes/block-editor.php:317
/var/www/src/wp-includes/block-editor.php:633
/var/www/tests/phpunit/tests/blocks/editor.php:406

It seems _wp_get_iframed_editor_assets() is assuming the $wp_scripts and $wp_styles globals are defined when they may not be, and normally when the entire test suite runs one of the other tests will have populated these globals without cleaning up after itself. But when this group of tests runs in isolation, the globals aren't defined, and thus the error occurs.

I think the function should be made more robust:

diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php
index 6f5720ec21..19edc778f7 100644
--- a/src/wp-includes/block-editor.php
+++ b/src/wp-includes/block-editor.php
@@ -302,8 +302,8 @@ function _wp_get_iframed_editor_assets() {
 	global $wp_styles, $wp_scripts;
 
 	// Keep track of the styles and scripts instance to restore later.
-	$current_wp_styles  = $wp_styles;
-	$current_wp_scripts = $wp_scripts;
+	$current_wp_styles  = wp_styles();
+	$current_wp_scripts = wp_scripts();
 
 	// Create new instances to collect the assets.
 	$wp_styles  = new WP_Styles();

@westonruter
Copy link
Member Author

westonruter commented Oct 9, 2025

@gziolo:

The challenge is that rendering occurs from the bottom up. It begins with the deeply nested inner blocks before rendering their ancestors. That's why, at the time of rendering an inner block, you don't know whether any of the parent blocks might decide to, for some reason, render nothing.

I reverted my naïeve implementation and I've taken another stab at it to account for the depth-first traversal of nested blocks: 90a9b4e. What it does now is before a block is rendered, it captures the queues for the enqueued styles, scripts, and script modules and then empties them out. Then it goes forward with rendering the inner blocks and the block's own content. Then it captures the queues again to find out which new assets were enqueued and restores the original queues. Then it checks if the rendered block content is not empty, and if so (or else the filter allows), it will proceed to merge those newly enqueued assets with the assets previously-enqueued when the block was being initially rendered.

Re-testing my original scenario:

Given the Hello World post on the single template on the Twenty Twenty-Five theme where:

  1. No featured image is assigned (so the Featured Image block renders nothing).
  2. There are no comments on the post.
  3. The Site Logo is not supplied, so its block content is empty.
  4. The Site Tagline is not defined, so its block content is also empty.
  5. A Cover block is added to the post, but the Block Visibility plugin is active and the block is marked as hidden for everyone.
  6. New: A Breadcrumbs block is added to the single template (
  7. New: A Social Icons block is also added to the content, but it is marked as hidden using the new Gutenberg capability.

To make it easier to compare the difference in the amount of CSS being added to the page, I've eliminated the inline CSS limit:

add_filter(
	'styles_inline_size_limit',
	static function (): int {
		return 0;
	}
);
single block template
<!-- wp:template-part {"slug":"header","theme":"twentytwentyfive"} /-->

<!-- wp:group {"tagName":"main","style":{"spacing":{"margin":{"top":"var:preset|spacing|60"}}},"layout":{"type":"constrained"}} -->
<main class="wp-block-group" style="margin-top:var(--wp--preset--spacing--60)"><!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|60","bottom":"var:preset|spacing|60"}}},"layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"><!-- wp:post-title {"level":1} /-->

<!-- wp:post-featured-image {"aspectRatio":"3/2"} /-->

<!-- wp:group {"className":"has-link-color","style":{"spacing":{"blockGap":"0.2em","margin":{"bottom":"var:preset|spacing|60"}}},"textColor":"accent-4","fontSize":"small","layout":{"type":"flex","flexWrap":"wrap"}} -->
<div class="wp-block-group has-link-color has-accent-4-color has-text-color has-small-font-size" style="margin-bottom:var(--wp--preset--spacing--60)"><!-- wp:paragraph -->
<p>Written by </p>
<!-- /wp:paragraph -->

<!-- wp:post-author-name {"isLink":true} /-->

<!-- wp:paragraph -->
<p>in</p>
<!-- /wp:paragraph -->

<!-- wp:post-terms {"term":"category","style":{"typography":{"fontWeight":"300"}}} /--></div>
<!-- /wp:group -->

<!-- wp:post-content {"align":"full","layout":{"type":"constrained"}} /-->

<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|60","bottom":"var:preset|spacing|60"}}},"layout":{"type":"constrained"}} -->
<div class="wp-block-group" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"><!-- wp:post-terms {"term":"post_tag","separator":"  ","className":"is-style-post-terms-1"} /--></div>
<!-- /wp:group -->

<!-- wp:group {"align":"wide","style":{"spacing":{"margin":{"top":"var:preset|spacing|60","bottom":"var:preset|spacing|60"}}},"layout":{"type":"default"}} -->
<div class="wp-block-group alignwide" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"><!-- wp:group {"tagName":"nav","align":"wide","style":{"border":{"top":{"color":"var:preset|color|accent-6","width":"1px"}},"spacing":{"padding":{"top":"var:preset|spacing|40","bottom":"var:preset|spacing|40"}}},"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between"},"ariaLabel":"Post navigation"} -->
<nav class="wp-block-group alignwide" aria-label="Post navigation" style="border-top-color:var(--wp--preset--color--accent-6);border-top-width:1px;padding-top:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40)"><!-- wp:post-navigation-link {"type":"previous","showTitle":true,"arrow":"arrow"} /-->

<!-- wp:post-navigation-link {"showTitle":true,"arrow":"arrow"} /--></nav>
<!-- /wp:group --></div>
<!-- /wp:group -->

<!-- wp:comments {"className":"wp-block-comments-query-loop","style":{"spacing":{"margin":{"top":"var:preset|spacing|70","bottom":"var:preset|spacing|70"}}}} -->
<div class="wp-block-comments wp-block-comments-query-loop" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"><!-- wp:heading {"fontSize":"x-large"} -->
<h2 class="wp-block-heading has-x-large-font-size">Comments</h2>
<!-- /wp:heading -->

<!-- wp:comments-title {"level":3,"fontSize":"large"} /-->

<!-- wp:comment-template -->
<!-- wp:group {"style":{"spacing":{"margin":{"top":"0","bottom":"var:preset|spacing|50"}}}} -->
<div class="wp-block-group" style="margin-top:0;margin-bottom:var(--wp--preset--spacing--50)"><!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap","verticalAlignment":"top"}} -->
<div class="wp-block-group"><!-- wp:avatar {"size":50} /-->

<!-- wp:group -->
<div class="wp-block-group"><!-- wp:comment-date /-->

<!-- wp:comment-author-name /-->

<!-- wp:comment-content /-->

<!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap"}} -->
<div class="wp-block-group"><!-- wp:comment-edit-link /-->

<!-- wp:comment-reply-link /--></div>
<!-- /wp:group --></div>
<!-- /wp:group --></div>
<!-- /wp:group --></div>
<!-- /wp:group -->
<!-- /wp:comment-template -->

<!-- wp:comments-pagination {"layout":{"type":"flex","justifyContent":"space-between"}} -->
<!-- wp:comments-pagination-previous /-->

<!-- wp:comments-pagination-next /-->
<!-- /wp:comments-pagination -->

<!-- wp:post-comments-form /--></div>
<!-- /wp:comments --></div>
<!-- /wp:group -->

<!-- wp:group {"align":"wide","style":{"spacing":{"padding":{"top":"var:preset|spacing|60","bottom":"var:preset|spacing|60"}}},"layout":{"type":"constrained"}} -->
<div class="wp-block-group alignwide" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"><!-- wp:heading {"align":"wide","style":{"typography":{"textTransform":"uppercase","fontStyle":"normal","fontWeight":"700","letterSpacing":"1.4px"}},"fontSize":"small"} -->
<h2 class="wp-block-heading alignwide has-small-font-size" style="font-style:normal;font-weight:700;letter-spacing:1.4px;text-transform:uppercase">More posts</h2>
<!-- /wp:heading -->

<!-- wp:query {"queryId":1,"query":{"perPage":4,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false,"taxQuery":null,"parents":[]},"align":"wide","layout":{"type":"default"}} -->
<div class="wp-block-query alignwide"><!-- wp:post-template {"align":"full","style":{"spacing":{"blockGap":"0"}},"layout":{"type":"default"}} -->
<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|30","bottom":"var:preset|spacing|30"}},"border":{"bottom":{"color":"var:preset|color|accent-6","width":"1px"},"top":[],"right":[],"left":[]}},"layout":{"type":"flex","flexWrap":"nowrap","verticalAlignment":"center","justifyContent":"space-between"}} -->
<div class="wp-block-group alignfull" style="border-bottom-color:var(--wp--preset--color--accent-6);border-bottom-width:1px;padding-top:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30)"><!-- wp:post-title {"level":3,"isLink":true,"fontSize":"large"} /-->

<!-- wp:post-date {"textAlign":"right","isLink":true,"metadata":{"bindings":{"datetime":{"source":"core/post-data","args":{"key":"date"}}}}} /--></div>
<!-- /wp:group -->
<!-- /wp:post-template --></div>
<!-- /wp:query --></div>
<!-- /wp:group --></main>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","theme":"twentytwentyfive"} /-->
post_content
<!-- wp:paragraph -->
<p>You will not be able to see this Social Icons block inside this Group due to the Group block being hidden using the new “Hide” functionality in Gutenberg for WP 6.9:</p>
<!-- /wp:paragraph -->

<!-- wp:group {"metadata":{"blockVisibility":false},"style":{"spacing":{"padding":{"top":"var:preset|spacing|30","bottom":"var:preset|spacing|30","left":"var:preset|spacing|30","right":"var:preset|spacing|30"}}},"backgroundColor":"accent-1","layout":{"type":"constrained"}} -->
<div class="wp-block-group has-accent-1-background-color has-background" style="padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--30)"><!-- wp:social-links -->
<ul class="wp-block-social-links"><!-- wp:social-link {"url":"http://wordpress.org/","service":"wordpress"} /--></ul>
<!-- /wp:social-links --></div>
<!-- /wp:group -->

<!-- wp:paragraph -->
<p>And here is a Cover Block which is hidden using the Block Visibility plugin:</p>
<!-- /wp:paragraph -->

<!-- wp:cover {"url":"http://localhost:8000/wp-content/uploads/2025/08/PXL_20240828_233245085-1024x206.avif","id":242,"dimRatio":50,"customOverlayColor":"#90bee6","isUserOverlayColor":false,"isDark":false,"sizeSlug":"large","layout":{"type":"constrained"},"blockVisibility":{"hideBlock":true}} -->
<div class="wp-block-cover is-light"><img class="wp-block-cover__image-background wp-image-242 size-large" alt="" src="http://localhost:8000/wp-content/uploads/2025/08/PXL_20240828_233245085-1024x206.avif" data-object-fit="cover"/><span aria-hidden="true" class="wp-block-cover__background has-background-dim" style="background-color:#90bee6"></span><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} -->
<p class="has-text-align-center has-large-font-size">This is some text.</p>
<!-- /wp:paragraph --></div></div>
<!-- /wp:cover -->

Diff on the page output between trunk and this branch:

--- before.html	2025-10-09 14:12:20
+++ after.html	2025-10-09 14:12:25
@@ -17,26 +17,18 @@
 <link rel='stylesheet' id='wp-block-navigation-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/navigation/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-group-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/group/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-post-title-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-title/style.css?ver=trunk' media='all' />
-<link rel='stylesheet' id='wp-block-breadcrumbs-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/breadcrumbs/style.css?ver=trunk' media='all' />
-<link rel='stylesheet' id='wp-block-post-featured-image-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-featured-image/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-paragraph-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/paragraph/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-post-author-name-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-author-name/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-post-terms-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-terms/style.css?ver=trunk' media='all' />
-<link rel='stylesheet' id='wp-block-social-links-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/social-links/style.css?ver=trunk' media='all' />
-<link rel='stylesheet' id='wp-block-cover-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/cover/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-post-content-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-content/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-post-navigation-link-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-navigation-link/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-heading-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/heading/style.css?ver=trunk' media='all' />
-<link rel='stylesheet' id='wp-block-comment-template-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/comment-template/style.css?ver=trunk' media='all' />
-<link rel='stylesheet' id='wp-block-comments-pagination-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/comments-pagination/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-post-comments-form-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-comments-form/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-buttons-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/buttons/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-button-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/button/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-comments-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/comments/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-post-date-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-date/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-post-template-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/post-template/style.css?ver=trunk' media='all' />
-<link rel='stylesheet' id='wp-block-site-logo-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/site-logo/style.css?ver=trunk' media='all' />
-<link rel='stylesheet' id='wp-block-site-tagline-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/site-tagline/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-spacer-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/spacer/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-columns-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/columns/style.css?ver=trunk' media='all' />
 <link rel='stylesheet' id='wp-block-navigation-link-css' href='http://localhost:8000/wp-content/plugins/gutenberg/build/block-library/blocks/navigation-link/style.css?ver=trunk' media='all' />
@@ -50,9 +42,6 @@
 :root :where(.wp-block-columns-is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.wp-block-columns-is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.wp-block-columns-is-layout-flow) > *{margin-block-start: var(--wp--preset--spacing--50);margin-block-end: 0;}:root :where(.wp-block-columns-is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.wp-block-columns-is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.wp-block-columns-is-layout-constrained) > *{margin-block-start: var(--wp--preset--spacing--50);margin-block-end: 0;}:root :where(.wp-block-columns-is-layout-flex){gap: var(--wp--preset--spacing--50);}:root :where(.wp-block-columns-is-layout-grid){gap: var(--wp--preset--spacing--50);}
 :root :where(.wp-block-buttons-is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.wp-block-buttons-is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-flow) > *{margin-block-start: 16px;margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > *{margin-block-start: 16px;margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-flex){gap: 16px;}:root :where(.wp-block-buttons-is-layout-grid){gap: 16px;}
 :root :where(.wp-block-post-comments-form){font-size: var(--wp--preset--font-size--medium);padding-top: var(--wp--preset--spacing--40);padding-bottom: var(--wp--preset--spacing--40);}:root :where(.wp-block-post-comments-form textarea, .wp-block-post-comments-form input:not([type=submit])){border-radius:.25rem; border-color: var(--wp--preset--color--accent-6) !important;}:root :where(.wp-block-post-comments-form input[type=checkbox]){margin:0 .2rem 0 0 !important;}:root :where(.wp-block-post-comments-form label){font-size: var(--wp--preset--font-size--small);}
-:root :where(.wp-block-comments-pagination){font-size: var(--wp--preset--font-size--medium);margin-top: var(--wp--preset--spacing--40);margin-bottom: var(--wp--preset--spacing--40);}
-:root :where(.wp-block-comments-pagination-next){font-size: var(--wp--preset--font-size--medium);}
-:root :where(.wp-block-comments-pagination-previous){font-size: var(--wp--preset--font-size--medium);}
 :root :where(.wp-block-post-date){color: var(--wp--preset--color--accent-4);font-size: var(--wp--preset--font-size--small);}
 :root :where(.wp-block-post-date a:where(:not(.wp-element-button))){color: var(--wp--preset--color--accent-4);text-decoration: none;}
 :root :where(.wp-block-post-date a:where(:not(.wp-element-button)):hover){text-decoration: underline;}
@@ -60,7 +49,6 @@
 :root :where(.wp-block-post-terms){font-size: var(--wp--preset--font-size--small);font-weight: 600;}:root :where(.wp-block-post-terms a){white-space: nowrap;}
 :root :where(.wp-block-post-title a:where(:not(.wp-element-button))){text-decoration: none;}
 :root :where(.wp-block-post-title a:where(:not(.wp-element-button)):hover){text-decoration: underline;}
-:root :where(.wp-block-site-tagline){font-size: var(--wp--preset--font-size--medium);}
 :root :where(.wp-block-site-title){font-weight: 700;letter-spacing: -.5px;}
 :root :where(.wp-block-site-title a:where(:not(.wp-element-button))){text-decoration: none;}
 :root :where(.wp-block-site-title a:where(:not(.wp-element-button)):hover){text-decoration: underline;}

@westonruter
Copy link
Member Author

westonruter commented Oct 9, 2025

CSS Coverage Report

Coverage report taken after page reload on a desktop viewport without doing any interactions.

Before: 39% of 136 kB used

image

After: 53% of 100 kB used

image

@github-actions
Copy link

github-actions bot commented Oct 9, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter, aristath, peterwilsoncc, gziolo, krupajnanda.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@westonruter westonruter requested a review from dmsnell October 10, 2025 00:32
@westonruter
Copy link
Member Author

@dmsnell FYI: Given your mission for block parsing efficiency, this seems like something you'd want to review.

@westonruter westonruter requested a review from aristath October 11, 2025 21:17
@kjnanda
Copy link

kjnanda commented Oct 12, 2025

Hello @westonruter
I was testing this PR in preventing unnecessary CSS from loading when a block is hidden. I wanted to verify if I have followed the right approach.

My Testing Approach:

  1. Baseline: Created a page with a Gallery block inside the column block and also did some styling from the inspector tab
  2. When block is present: Exported a Chrome DevTools CSS Coverage Report when the Gallery block was fully visible and rendering content.
  3. Hidden Test: Used the new block visibility controls to hide the Gallery block. Exported a second CSS Coverage Report.

My Finding:

When the block is hidden When the block is visible
image image

Now, visually, I don't see any major differences here, but I observed that style.css is not loading when the block is hidden.

@westonruter Please confirm if the approach is correct.

@westonruter
Copy link
Member Author

@kjnanda yes, that is correct! You can also try nesting some block deep inside of another block, like a Video inside of a column inside of a Group. If you hide the Group block, then the CSS for the Columns block and Video block should both be omitted from the page.

In all cases, the visual appearance of the page should remain the same (except for whether the block is hidden or not of course).

You could also try adding an Image block with lightbox enabled (expand on click). If that is the only Image block on the page, if you hide the the block then you should that it's view script module is omitted from the page.

Copy link
Member

@aristath aristath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 👍 ❤️

@kjnanda
Copy link

kjnanda commented Oct 13, 2025

@westonruter Noted. And this is working as expected.

@gziolo
Copy link
Member

gziolo commented Oct 13, 2025

What it does now is before a block is rendered, it captures the queues for the enqueued styles, scripts, and script modules and then empties them out. Then it goes forward with rendering the inner blocks and the block's own content. Then it captures the queues again to find out which new assets were enqueued and restores the original queues. Then it checks if the rendered block content is not empty, and if so (or else the filter allows), it will proceed to merge those newly enqueued assets with the assets previously-enqueued when the block was being initially rendered.

Great idea! It should cover all types of enqueuing I can think of for styles, scripts, and script modules when rendering blocks. It maps enqueued assets to the specific block well, enabling detailed control over what gets added to the global queue once the block tree is marked as non-empty and needs these assets. That fully addresses all the concerns raised previously 🚢

* @covers WP_Script_Modules::queue
* @covers WP_Script_Modules::dequeue
*/
public function test_direct_queue_manipulation() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this contain multiple assertions, each of them will need a custom message parameter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 29a46d4


/**
* Holds the script module identifiers that were enqueued before registered.
* An array of IDs for queued script modules.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a need to switch from keying by script ID to an array of script ID values?

It's adding a bunch of work to avoid duplicates that I think could be more easily covered by using a method to add the new asset in the wp_block class.

As developer interfaces go, I also think that an ::add_to_queue method would be more helpful that requiring third party devs do an array_unique( array_merge() ) each time they modify the queue.

Even if we advice that manipulating the queue in this manner is discouraged, it will be done.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This brings WP_Script_Modules in alignment with WP_Scripts and WP_Styles which both expose a $queue member variable. It allows us to easily obtain the list of IDs enqueued, and then merge them.

For WP_Scripts and WP_Styles it is primarily for reading, but it is also used for writing here (now in 6.9):

// Make sure inline style is printed first since it was previously printed at wp_head priority 1 and this preserves the CSS cascade.
array_unshift( wp_styles()->queue, $handle );

In reality, the array_unique() is not needed because WP_Scripts, WP_Styles, and WP_Script_Modules all obtain the unique items when processing anyway (or else keep track via done).

We could introduce a magic setter function for the queue member (which we could then make private). This would have the benefit of not only ensuring uniqueness, but we could also throw an error if anything other than an array of strings is attempted to be set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, let's go with consistency.

pento pushed a commit that referenced this pull request Oct 14, 2025
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 #9213.

Props westonruter, aristath, peterwilsoncc, gziolo, krupajnanda, dd32, jorbin.
See #50328.
Fixes #63676.


git-svn-id: https://develop.svn.wordpress.org/trunk@60930 602fd350-edb4-49c9-b593-d223f7449a82
@github-actions
Copy link

A commit was made that fixes the Trac ticket referenced in the description of this pull request.

SVN changeset: 60930
GitHub commit: 9d03e8e

This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.

@github-actions github-actions bot closed this Oct 14, 2025
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Oct 14, 2025
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 WordPress/wordpress-develop#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
github-actions bot pushed a commit to platformsh/wordpress-performance that referenced this pull request Oct 14, 2025
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 WordPress/wordpress-develop#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: https://core.svn.wordpress.org/trunk@60266 1a063a9b-81f0-0310-95a4-ce76da25c4cd
$this->registered[ $id ]['enqueue'] = false;
}
unset( $this->enqueued_before_registered[ $id ] );
$this->queue = array_diff( $this->queue, array( $id ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter

Quick Q: After the change it will bypass the updating on $this->registered[ $id ]['enqueue'] to false. Does it accepted behaviour?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enqueue property of the script module array has been removed entirely. Now the enqueued state is exclusively captured in the enqueue class member variable, same as with WP_Scripts. Since all of this was private before, there shouldn't be any back-compat issues.

@westonruter
Copy link
Member Author

This caused a breakage on dotorg: https://core.trac.wordpress.org/ticket/63676#comment:28

Follow-up PR to fix: #10252

@dmsnell
Copy link
Member

dmsnell commented Oct 18, 2025

for block parsing efficiency, this seems like something…to review.

thanks for the ping, @westonruter. if I’m understanding it properly, this quieting activity is occurring after the blocks have already been parsed, in which case I don’t think it speaks to that area of efficiency gains.

now, on the other hand, if I missed something because it’s not in the diff context and we are making a separate pass with parse_blocks() to get this then yes, it would be highly worth our time to see if we can’t look to WP_Block_Parser. one thing that gives me pause here are your comments about this work depending not on the post_content, but rather what comes out of render_block(). if the information isn’t available from post_content then I don’t see any obvious room for improvement.

// Get the list of unique block types present within a post.

$processor   = new WP_Block_Processor( $post_content );
$block_types = array();
while ( $processor->next_block() ) {
	$block_types[ $processor->get_block_type() ] = true;
}

return array_keys( $block_types );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants