#63676 closed defect (bug) (fixed)
Blocks without rendered content (including blocks via block visibility) still enqueue scripts and styles
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 6.9 | Priority: | normal |
| Severity: | normal | Version: | 5.9 |
| Component: | Editor | Keywords: | has-patch needs-testing dev-feedback has-unit-tests |
| Focuses: | javascript, css, performance | Cc: |
Description
This is a follow-up to #50328.
Scripts and styles are being enqueued for blocks even when the block content is not being rendered. This can commonly happen, for example:
- The Featured Image block renders no output when there is no featured image assigned.
- The Comments block renders nothing when comments are disabled.
- The Site Logo and Tagline blocks do not render anything if no logo or tagline are defined.
- A block's rendering may be suppressed using a plugin like Block Visibility.
In all these cases, the scripts and styles are enqueued for the blocks even when the blocks are not rendered on the page. This is wasteful to included unused JS and CSS and it negatively impacts performance.
To fix this, I suggest that WP_Block::render(), wp_enqueue_block_style(), and enqueue_block_styles_assets() be updated to check whether the rendered block content contains any HTML tag, and if so to only then proceed with enqueueing the scripts and styles. In contrast, if the rendered block content is an empty string or an HTML comment, then nothing should be enqueued.
Change History (41)
#1
@
7 months ago
- Summary changed from Blocks without rendered output still enqueue scripts and styles to Blocks without rendered content still enqueue scripts and styles
This ticket was mentioned in PR #9213 on WordPress/wordpress-develop by @westonruter.
7 months ago
#3
- Keywords has-patch added
Trac ticket: https://core.trac.wordpress.org/ticket/63676
Given a published post on the single template on the Twenty Twenty-Five theme where:
- No featured image is assigned (so the Featured Image block renders nothing)
- Comments are disabled for the post (so the Comments block renders nothing)
- The Site Logo is not supplied, so its block content is empty.
- The Site Title is not defined, so its block content is also empty.
- 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:
-
.html
old new 528 528 text-decoration:inherit; 529 529 } 530 530 </style> 531 <style id='wp-block-post-featured-image-inline-css'>532 .wp-block-post-featured-image{533 margin-left:0;534 margin-right:0;535 }536 .wp-block-post-featured-image a{537 display:block;538 height:100%;539 }540 .wp-block-post-featured-image :where(img){541 box-sizing:border-box;542 height:auto;543 max-width:100%;544 vertical-align:bottom;545 width:100%;546 }547 .wp-block-post-featured-image.alignfull img,.wp-block-post-featured-image.alignwide img{548 width:100%;549 }550 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim{551 background-color:#000;552 inset:0;553 position:absolute;554 }555 .wp-block-post-featured-image{556 position:relative;557 }558 559 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-gradient{560 background-color:initial;561 }562 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-0{563 opacity:0;564 }565 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-10{566 opacity:.1;567 }568 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-20{569 opacity:.2;570 }571 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-30{572 opacity:.3;573 }574 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-40{575 opacity:.4;576 }577 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-50{578 opacity:.5;579 }580 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-60{581 opacity:.6;582 }583 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-70{584 opacity:.7;585 }586 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-80{587 opacity:.8;588 }589 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-90{590 opacity:.9;591 }592 .wp-block-post-featured-image .wp-block-post-featured-image__overlay.has-background-dim-100{593 opacity:1;594 }595 .wp-block-post-featured-image:where(.alignleft,.alignright){596 width:100%;597 }598 </style>599 531 <style id='wp-block-paragraph-inline-css'> 600 532 .is-small-text{ 601 533 font-size:.875em; … … 657 589 white-space:pre-wrap; 658 590 } 659 591 </style> 660 <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' />661 592 <style id='wp-block-list-inline-css'> 662 593 ol,ul{ 663 594 box-sizing:border-box; … … 697 628 } 698 629 .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"]{ 699 630 rotate:180deg; 700 }701 </style>702 <style id='wp-block-comments-inline-css'>703 .wp-block-post-comments{704 box-sizing:border-box;705 }706 .wp-block-post-comments .alignleft{707 float:left;708 }709 .wp-block-post-comments .alignright{710 float:right;711 }712 .wp-block-post-comments .navigation:after{713 clear:both;714 content:"";715 display:table;716 }717 .wp-block-post-comments .commentlist{718 clear:both;719 list-style:none;720 margin:0;721 padding:0;722 }723 .wp-block-post-comments .commentlist .comment{724 min-height:2.25em;725 padding-left:3.25em;726 }727 .wp-block-post-comments .commentlist .comment p{728 font-size:1em;729 line-height:1.8;730 margin:1em 0;731 }732 .wp-block-post-comments .commentlist .children{733 list-style:none;734 margin:0;735 padding:0;736 }737 .wp-block-post-comments .comment-author{738 line-height:1.5;739 }740 .wp-block-post-comments .comment-author .avatar{741 border-radius:1.5em;742 display:block;743 float:left;744 height:2.5em;745 margin-right:.75em;746 margin-top:.5em;747 width:2.5em;748 }749 .wp-block-post-comments .comment-author cite{750 font-style:normal;751 }752 .wp-block-post-comments .comment-meta{753 font-size:.875em;754 line-height:1.5;755 }756 .wp-block-post-comments .comment-meta b{757 font-weight:400;758 }759 .wp-block-post-comments .comment-meta .comment-awaiting-moderation{760 display:block;761 margin-bottom:1em;762 margin-top:1em;763 }764 .wp-block-post-comments .comment-body .commentmetadata{765 font-size:.875em;766 }767 .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{768 display:block;769 margin-bottom:.25em;770 }771 .wp-block-post-comments .comment-form input:not([type=submit]):not([type=checkbox]),.wp-block-post-comments .comment-form textarea{772 box-sizing:border-box;773 display:block;774 width:100%;775 }776 .wp-block-post-comments .comment-form-cookies-consent{777 display:flex;778 gap:.25em;779 }780 .wp-block-post-comments .comment-form-cookies-consent #wp-comment-cookies-consent{781 margin-top:.35em;782 }783 .wp-block-post-comments .comment-reply-title{784 margin-bottom:0;785 }786 .wp-block-post-comments .comment-reply-title :where(small){787 font-size:var(--wp--preset--font-size--medium, smaller);788 margin-left:.5em;789 }790 .wp-block-post-comments .reply{791 font-size:.875em;792 margin-bottom:1.4em;793 631 } 794 .wp-block-post-comments input:not([type=submit]),.wp-block-post-comments textarea{795 border:1px solid #949494;796 font-family:inherit;797 font-size:1em;798 }799 .wp-block-post-comments input:not([type=submit]):not([type=checkbox]),.wp-block-post-comments textarea{800 padding:calc(.667em + 2px);801 }802 803 :where(.wp-block-post-comments input[type=submit]){804 border:none;805 }806 807 .wp-block-comments{808 box-sizing:border-box;809 }810 632 </style> 811 633 <style id='wp-block-heading-inline-css'> 812 634 h1.has-background,h2.has-background,h3.has-background,h4.has-background,h5.has-background,h6.has-background{ … … 878 700 .wp-block-post-template-is-layout-constrained>li>.aligncenter,.wp-block-post-template-is-layout-flow>li>.aligncenter{ 879 701 margin-inline-end:auto; 880 702 margin-inline-start:auto; 881 }882 </style>883 <style id='wp-block-site-logo-inline-css'>884 .wp-block-site-logo{885 box-sizing:border-box;886 line-height:0;887 }888 .wp-block-site-logo a{889 display:inline-block;890 line-height:0;891 }892 .wp-block-site-logo.is-default-size img{893 height:auto;894 width:120px;895 703 } 896 .wp-block-site-logo img{897 height:auto;898 max-width:100%;899 }900 .wp-block-site-logo a,.wp-block-site-logo img{901 border-radius:inherit;902 }903 .wp-block-site-logo.aligncenter{904 margin-left:auto;905 margin-right:auto;906 text-align:center;907 }908 909 :root :where(.wp-block-site-logo.is-style-rounded){910 border-radius:9999px;911 }912 </style>913 <style id='wp-block-site-tagline-inline-css'>914 .wp-block-site-tagline{915 box-sizing:border-box;916 }917 704 </style> 918 705 <style id='wp-block-spacer-inline-css'> 919 706 .wp-block-spacer{ … … 1224 1011 :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;} 1225 1012 :root :where(.wp-block-post-title a:where(:not(.wp-element-button))){text-decoration: none;} 1226 1013 :root :where(.wp-block-post-title a:where(:not(.wp-element-button)):hover){text-decoration: underline;} 1227 :root :where(.wp-block-site-tagline){font-size: var(--wp--preset--font-size--medium);}1228 1014 :root :where(.wp-block-site-title){font-weight: 700;letter-spacing: -.5px;} 1229 1015 :root :where(.wp-block-site-title a:where(:not(.wp-element-button))){text-decoration: none;} 1230 1016 :root :where(.wp-block-site-title a:where(:not(.wp-element-button)):hover){text-decoration: underline;}
#4
@
7 months ago
- Keywords needs-testing needs-unit-tests added
- Status changed from assigned to accepted
#5
in reply to:
↑ 2
@
7 months ago
- Keywords dev-feedback added
Replying to 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.
I'd love to see an example of this in the wild. Otherwise, the current behavior seems more counter-intuitive: if I add a plugin that filters out all blocks of a given type, I wouldn't expect its CSS and JS to still be added to the page.
7 months ago
#6
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?
#7
@
7 months ago
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.
#8
follow-up:
↓ 9
@
7 months ago
That said, i think it would still make sense to do this. I would like to see it be overwritable by a filter though.
Likewise, I should've added that I'm not against it, only that it seems like something that could occur in the wild. For every reason someone has to prevent output from a block, there's a use-case that intentionally has no output.
The way I'd personally have implemented it is by an explicit return false from the render method than looking for HTML output, but that'll irritate purists as I'm pretty sure it currently only returns strings.
I'm curious why you'd use the HTML tag parser here to detect output rather than a more basic if ( $block_content ) { enqueue assets } though.
#9
in reply to:
↑ 8
@
7 months ago
Replying to dd32:
I'm curious why you'd use the HTML tag parser here to detect output rather than a more basic
if ( $block_content ) { enqueue assets }though.
This is to handle a case where a plugin filters the block content to return an HTML comment as opposed to a falsy value, for example:
<!-- This block was removed because you do not have access! -->
@westonruter commented on PR #9213:
7 months ago
#10
@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 commented on PR #9213:
7 months ago
#11
@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.
7 months ago
#12
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 commented on PR #9213:
7 months ago
#13
Thank you! I'll proceed with adding tests so this can be moved to review and commit.
@westonruter commented on PR #9213:
7 months ago
#14
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.
7 months ago
#15
The challenge is that the rendering happens bottom up. It starts with the deeply nested inner blocks before rendering their ancestors. That's why, at the time of rendering, you don't know whether one of the parent blocks would decide to render nothing for some reason.
#16
@
4 months ago
- Summary changed from Blocks without rendered content still enqueue scripts and styles to Blocks without rendered content (including blocks via block visibility) still enqueue scripts and styles
@westonruter commented on PR #9213:
4 months ago
#17
@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:
- No featured image is assigned (so the Featured Image block renders nothing).
- Comments are disabled for the post (so the Comments block renders nothing).
- The Site Logo is not supplied, so its block content is empty.
- The Site Title is not defined, so its block content is also empty.
- A Cover block is added to the post, but the Block Visibility plugin is active and the block is marked as hidden for everyone.
- New: A Breadcrumbs block is added to the
singletemplate ( - 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;
}
);
<details><summary><code>single</code> block template</summary>
<main class="wp-block-group" style="margin-top:var(--wp--preset--spacing--60)"> <div class="wp-block-group alignfull" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"> <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)"> <p>Written by </p> <p>in</p> </div> <div class="wp-block-group" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"></div> <div class="wp-block-group alignwide" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"> <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)"> </nav> </div> <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)"> <h2 class="wp-block-heading has-x-large-font-size">Comments</h2> <div class="wp-block-group" style="margin-top:0;margin-bottom:var(--wp--preset--spacing--50)"> <div class="wp-block-group"> <div class="wp-block-group"> <div class="wp-block-group"> </div> </div> </div> </div> </div> </div> <div class="wp-block-group alignwide" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"> <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> <div class="wp-block-query alignwide"> <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)"> </div> </div> </div> </main> {{{ </details> <details><summary><code>post_content</code></summary> }}}html <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> <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)"> <ul class="wp-block-social-links"></ul> </div> <p>And here is a Cover Block which is hidden using the Block Visibility plugin:</p> <div class="wp-block-cover is-light">[[Image(http://localhost:8000/wp-content/uploads/2025/08/PXL_20240828_233245085-1024x206.avif)]]<span aria-hidden="true" class="wp-block-cover__background has-background-dim" style="background-color:#90bee6"></span><div class="wp-block-cover__inner-container"> <p class="has-text-align-center has-large-font-size">This is some text.</p> </div></div> }}} </details> Diff on the page output between `trunk` and this branch after prettier reformatting: {{{#!diff --- before.html 2025-10-09 14:07:25 +++ after.html 2025-10-09 14:07:25 @@ -61,18 +61,6 @@ /> <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" @@ -91,18 +79,6 @@ /> <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" @@ -117,18 +93,6 @@ 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 @@ -165,18 +129,6 @@ 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 @@ -905,17 +857,6 @@ } :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); @@ -946,9 +887,6 @@ :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: -0.5px; }}}
@westonruter commented on PR #9213:
4 months ago
#18
This ticket was mentioned in Slack in #core-css by westonruter. View the logs.
4 months ago
@westonruter commented on PR #9213:
4 months ago
#21
@dmsnell FYI: Given your mission for block parsing efficiency, this seems like something you'd want to review.
@krupajnanda commented on PR #9213:
4 months ago
#22
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:
- Baseline: Created a page with a Gallery block inside the column block and also did some styling from the inspector tab
- When block is present: Exported a Chrome DevTools CSS Coverage Report when the Gallery block was fully visible and rendering content.
- 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|
Now, visually, I don't see any 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 commented on PR #9213:
4 months ago
#23
@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.
@krupajnanda commented on PR #9213:
4 months ago
#24
@westonruter Noted. And this is working as expected.
4 months ago
#25
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 🚢
This ticket was mentioned in Slack in #core by dd32. View the logs.
4 months ago
#28
@
4 months ago
- Resolution fixed deleted
- Status changed from closed to reopened
@westonruter This has caused breakage on WordPress.org - #meta8104, and the enqueue_empty_block_content_assets filter doesn't seem to be related at all (It didn't even fire).
I'm not 100% sure why this is happening, as it doesn't appear that it's directly related to this skipping logic, but rather that the queued data is being lost somehow - perhaps being overwritten with older data.
So I'm thinking this could be related to nested blocks, where a script/style is registered from within one of those subblocks.
For example, this plugin adds one of the styles that were missing:
https://github.com/WordPress/wordpress.org/blob/trunk/wordpress.org/public_html/wp-content/plugins/wporg-bbp-code-blocks-expand-contract/wporg-bbp-code-blocks-expand-contract.php#L27-L46
That's a fairly basic enqueue, but the only thing I can think of is it being fired from within a block wporg/global-header. https://github.com/WordPress/wporg-mu-plugins/tree/trunk/mu-plugins/blocks/global-header-footer
Here's a diff of two WordPress.org sites before/after that changeset.
Here's a diff of wordpress.org/support/forums/:
https://gist.github.com/dd32/a9441db9a3fac0ce2a962625f7ca2aef
Here's a diff of make.wordpress.org/updates/:
https://gist.github.com/dd32/6a495ca9617384a33ebd73997859fe69
I'm unable to figure out how to duplicate this on a stand alone site, https://github.com/wordpress/wporg-parent-2021 might reproduce it (but it's provisioning is broken for me right now)
One thing I've noticed (But I don't believe isn't entirely unrelated) is that I believe wp_script_is( 'admin-bar', 'enqueued' ) would now return false during the block render even if it was enqueued, but is not enqueued directly (or required by one of the currently enqueued scripts).
#29
@
4 months ago
@dd32 Thanks for the details. I'll investigate later today.
I assume you fixed the issue on Make/Updates by pinning core at a revision prior to [60930]?
#30
@
4 months ago
It does seem that the issue is that wp_head and wp_footer are themselves inside of blocks for this theme, which I admit I was not expecting (and which seems would be quite unusual). Because the queues are emptied before each block is rendered (and restored afterward), it makes sense that certain assets would not be printed because the queue is empty! So if wp_head and wp_footer are inside of blocks, they wouldn't actually be expected to print anything.
It is also true that wp_script_is( 'admin-bar', 'enqueued' ) would always return false with [60930].
#31
@
4 months ago
@westonruter Yes, I pinned WordPress.org to the prior revision to resolve. No harm in doing so.
While having wp_head/wp_footer within blocks would normally be unexpected, I expected that the enqueued assets would simply therefor get attached to that block as the enqueuer and get merged in - but that didn't seem to happen.
This ticket was mentioned in PR #10252 on WordPress/wordpress-develop by @westonruter.
4 months ago
#32
This is a follow-up to https://github.com/WordPress/wordpress-develop/pull/9213 (r60930 & 9d03e8e) which broke asset enqueues on WPOrg, as reported by @dd32:
@westonruter This has caused breakage on WordPress.org - #meta8104, and the
enqueue_empty_block_content_assetsfilter doesn't seem to be related at all (It didn't even fire).
I'm not 100% sure why this is happening, as it doesn't appear that it's directly related to this skipping logic, but rather that the queued data is being lost somehow - perhaps being overwritten with older data.
So I'm thinking this could be related to nested blocks, where a script/style is registered from within one of those subblocks.
For example, this plugin adds one of the styles that were missing:
https://github.com/WordPress/wordpress.org/blob/trunk/wordpress.org/public_html/wp-content/plugins/wporg-bbp-code-blocks-expand-contract/wporg-bbp-code-blocks-expand-contract.php#L27-L46
That's a fairly basic enqueue, but the only thing I can think of is it being fired from within a block wporg/global-header. https://github.com/WordPress/wporg-mu-plugins/tree/trunk/mu-plugins/blocks/global-header-footer
Here's a diff of two WordPress.org sites before/after that changeset.
Here's a diff of wordpress.org/support/forums/:
https://gist.github.com/dd32/a9441db9a3fac0ce2a962625f7ca2aef
Here's a diff of make.wordpress.org/updates/:
https://gist.github.com/dd32/6a495ca9617384a33ebd73997859fe69
I'm unable to figure out how to duplicate this on a stand alone site, https://github.com/wordpress/wporg-parent-2021 might reproduce it (but it's provisioning is broken for me right now)
One thing I've noticed (But I don't believe isn't entirely unrelated) is that I believe
wp_script_is( 'admin-bar', 'enqueued' )would now return false during the block render even if it was enqueued, but is not enqueued directly (or required by one of the currently enqueued scripts).
This PR takes an alternative approach to not clear out the queue before each block is rendered. Since blocks are rendered in depth-first order, simply capturing the enqueued assets before rendering and then comparing with the assets enqueued after a block (or nested block tree) is rendered should be sufficient. When no content is rendered in the block, then the logic has been updated to _dequeue_ what had newly been enqueued (rather than to merge queues of the previous queue and the new queue).
As an added bonus, this ensures that wp_script_is( 'admin-bar', 'enqueued' ) works as expected in the block render callbacks/filters.
Then there is a very special case added for the sake of themes which may include a block which specifically renders wp_head(): in the case of enqueueing a script there which is only printed in the footer, the result of wp_head() may be no output at all (since the script will be printed at wp_footer() instead. To account for this, now the logic for checking whether to dequeue assets is skipped if the wp_head action happened during the block's rendering.
Trac ticket: https://core.trac.wordpress.org/ticket/63676
@westonruter commented on PR #9213:
4 months ago
#33
#34
@
4 months ago
@dd32 I was able to reproduce the issue you described by hacking together some changes on top of twentytwentyone to use the header/footer blocks from that dotorg mu-plugin. This PR fixes the issue in my testing: https://github.com/WordPress/wordpress-develop/pull/10252
@dd32 commented on PR #10252:
4 months ago
#35
I've tested this on WordPress.org, and done a diff of the output before changes, verses with trunk with this PR, and the only diff is a \n meaning this PR does seem to resolve the issue encountered.
The approach of dequeuing instead of conditionally enqueueing assets does seem a bit weird at first, but It does seem like the best approach for back-compat.
The only alternative that I can think of would require a far more indepth change to how WP_Dependency operates, effectively adding a start transaction; render block; commit OR discard queue changes; within deps, which would introduce other edge-cases, but would keep the manipulation of deps within the script/style handlers rather than in the block renderer.
Having to check for wp_head running feels like a dirty hack here, but I understand the intention of it, and is probably best to remain included. _(for reference, this check isn't needed on WordPress.org, but I can imagine an edge-case where it would be - such as where no actual HTML output occured on the hook which seems rare enough to be frustrating to debug)_
@westonruter commented on PR #10252:
4 months ago
#37
4 months ago
#38
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_printable_block_type() ] = true;
}
return array_keys( $block_types );
This ticket was mentioned in Slack in #core by westonruter. View the logs.
4 months ago
#40
@
3 months ago
The miscellaneous developer-focused changes developer note mentioned the new enqueue_empty_block_content_assets hook introduced in [60930], but did not cover it's usage in depth.: https://make.wordpress.org/core/2025/11/17/miscellaneous-developer-focused-changes-in-6-9/.
#41
@
3 months ago
The new enqueue_empty_block_content_assets filter has been documented in the 6.8 Frontend Performance Field Guide post: https://make.wordpress.org/core/2025/11/18/wordpress-6-9-frontend-performance-field-guide/#omit-styles-and-scripts-for-hidden-blocks-by-default
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.