Problem/Motivation

Every region template (eg. core/modules/system/templates/region.html.twig) uses {{ content }} to render the blocks in that region. But unlike node templates (eg. core/modules/node/templates/node.html.twig), where you can do things like {{ content|without('field_example') }}, the content of a region cannot be manipulated. This is because the content variable of a region is not an array of renderable elements, but just a string of rendered HTML.

Sometimes you want to be able to have access to the separate blocks, for instance to do something like this:

  <div class="grid-container">
    {{ content|without('special-block') }}
  </div>
  {{ content.special-block }}

Proposed resolution

The reason why blocks arrive in the template as an already rendered blob of HTML, can be found in the comments on line 439 and 440 of core/lib/Drupal/Core/Render/Renderer.php:

// If #theme is not implemented or #render_children is set and the element
// has an empty #children attribute, render the children now. [...]

This also points us in the direction of a solution: Build the render array of a region with a #theme property instead of a #theme_wrapper. As far as I can see, this results in the exact same output as the current situation, requiring no changes to twig templates, while adding additional flexibility for those who need it.

Remaining tasks

I did not find any downsides yet, but we have to think of reasons to not do this.
No reasons have been brought forward.

Are there performance implications?
None, except that a little more function nesting (still well within reasonable limits) might theoretically slow down xDebug.

Could caching be a problem when region output is made more dynamic? And if so, is that a problem for Drupal core to solve, or just a case of "with great power..."?
Manual testing has shown that caching works as expected, also when making use of the new capabilities we are adding. Also, all automated tests pass.

Decide what to do about regions that contain blocks, but eventually render to an empty string. Currently they skip rendering the div.region wrapper, but after the patch the wrapper is rendered. See comment #43.
This is not a problem if we target the 10.x-alpha release, see comment #47.

User interface changes

none

API changes

none (see also comment #34)

Data model changes

none

Release notes snippet

Do we need one?

CommentFileSizeAuthor
#79 render-region-blocks-later-3178202_79.patch23.95 KBgcb
#65 core-render-blocks-later-3178202-65.patch22.26 KBwatergate
#64 UmamiPages.zip39.73 KBtyler36
#61 3178202-61.patch22.26 KBidebr
#60 3178202-nr-bot.txt164 bytesneeds-review-queue-bot
#55 interdiff-36-55.txt2.49 KBmarcvangend
#55 3178202-55-render-blocks-later.patch27.41 KBmarcvangend
#55 block-render-array-before-after.png82.03 KBmarcvangend
#51 3178202-51-render-blocks-later.patch26.8 KBmarcvangend
#42 3178202-36-render-blocks-later-wget-diff.txt123.77 KBkristen pol
#37 3178202-36-render-blocks-later-TEST-ONLY-FAIL.patch3.25 KBmarcvangend
#37 interdiff-28-36.txt18.28 KBmarcvangend
#37 3178202-36-render-blocks-later.patch26.84 KBmarcvangend
#35 interdiff-28-35.txt17.48 KBmarcvangend
#35 3178202-35-render-blocks-later.patch25.88 KBmarcvangend
#35 3178202-35-render-blocks-later-TEST-ONLY-FAIL.patch3.25 KBmarcvangend
#28 3178202-28-render-blocks-later.patch4.94 KBmarcvangend
#27 interdiff-26-27.txt2.61 KBmarcvangend
#27 3178202-27-render-blocks-later-TEST-ONLY-FAIL.patch3.24 KBmarcvangend
#27 3178202-27-render-blocks-later.patch4.92 KBmarcvangend
#26 3178202-26-render-blocks-later.patch1.69 KBmarcvangend
#25 interdiff-14-25.txt724 bytesmarcvangend
#25 3178202-25-render-blocks-later.patch1.69 KBmarcvangend
#14 interdiff-12-14.txt1.6 KBmarcvangend
#14 3178202-14-render-blocks-later.patch1.7 KBmarcvangend
#12 interdiff-10-12.txt531 bytesmarcvangend
#12 3178202-12-render-blocks-later.patch1.82 KBmarcvangend
#10 interdiff-9-10.txt1.57 KBmarcvangend
#10 3178202-10-render-blocks-later.patch1.81 KBmarcvangend
#9 3178202-9-render-blocks-later.patch1.79 KBmarcvangend
#7 3178202-7-render-blocks-later.patch1.8 KBmarcvangend
#2 3178202-2-render-blocks-later.patch1.32 KBmarcvangend

Issue fork drupal-3178202

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

marcvangend created an issue. See original summary.

marcvangend’s picture

Assigned: marcvangend » Unassigned
Status: Active » Needs review
StatusFileSize
new1.32 KB

Patch attached. I was surprised how little code I needed to change to make this work :-)

Status: Needs review » Needs work

The last submitted patch, 2: 3178202-2-render-blocks-later.patch, failed testing. View results

marcvangend’s picture

I'll look into that test failure tomorrow. At first sight I don't understand what the batch test has to do with this patch, but I guess there's some kind of logic to it.

joycelam’s picture

i've applied and tested the patch from #2 on one of our websites and it works. Thanks!

marcvangend’s picture

Thanks for testing, Joyce.

Meanwhile I did find a somewhat related issue: #2714509: Remove usages of #theme_wrappers

marcvangend’s picture

Status: Needs work » Needs review
StatusFileSize
new1.8 KB

This patch should fix the failing test. The previous patch could accidentally mess up render elements when a render array returned by a controller has '#type' => 'page' (as \Drupal\system\Controller\BatchController::batchPage does).

sutharsan’s picture

Status: Needs review » Needs work
+++ b/core/includes/theme.inc
@@ -1568,7 +1568,10 @@ function template_preprocess_install_page(&$variables) {
+  foreach (Element::children($variables['elements']) as $block) {

The variable name $block confused me. I suggest to change it to $key, as Element::children() returns 'The array keys of the element's children'.

marcvangend’s picture

Status: Needs work » Needs review
StatusFileSize
new1.79 KB

Thanks Sutharsan, fair point. I used $block because the keys are usually (but not even necessarily) block identifiers. Indeed $key is more correct. New patch attached.

marcvangend’s picture

StatusFileSize
new1.81 KB
new1.57 KB

Oops, made a silly mistake when I did the fix in #7.

BarisW’s picture

This could use another review

marcvangend’s picture

StatusFileSize
new1.82 KB
new531 bytes

Correcting another silly mistake.

Status: Needs review » Needs work

The last submitted patch, 12: 3178202-12-render-blocks-later.patch, failed testing. View results

marcvangend’s picture

Status: Needs work » Needs review
StatusFileSize
new1.7 KB
new1.6 KB

This should fix the test failures. (Again, BatchController::batchPage does things differently than normal pages. Yay for automated tests.)

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

karlshea’s picture

This patch is extremely helpful, thank you!

marcvangend’s picture

Thanks Karl :-)

If you find any problems caused by this patch, please post them here. Likewise, if you do not encounter issues, that's valuable feedback too!

karlshea’s picture

I did not find any issues at all. I needed to output blocks individually in a footer template, and the patch didn't seem to affect any of the existing output.

casey’s picture

Nice one! I've been wondering the same.

#60552: Add region.tpl.php for all regions in themes is where it was introduced; https://git.drupalcode.org/project/drupal/-/blame/7.x/modules/system/sys...

The usage of #theme instead of #theme_wrapper doesn't seem to be discussed. The main reason for the region wrapper to be introduced was to have a wrapper element with an ID.

#60552-5: Add region.tpl.php for all regions in themes

The advantages of a having a wrapper tpl are that you don't have to tell all themes to include a <div id="region-NAME"> in their page.tpl and you could ensure that the IDs are the same across all themes.

It was also considered important to be able to not show the region wrapper if the region is empty.

Both is possible when using a #theme and more.

One issue I can think of is that it will increase the function nesting level, which might be an issue when using xdebug; but it wouldn't be much, I think it would increase the nesting level with 1 or 2.

marcvangend’s picture

Thanks for the historical research, Casey. I think the advantage of having a region template still applies today, and indeed this proposal does not change that. In fact the patch doesn't change anything about the way the region wrapper is rendered, it's all about the rendering of the region content (blocks).

I also found that the idea to early-render elements that do not have a #theme property, goes way back to at least 2009. This is before the release of Drupal 7: Before the introduction of render arrays and the paradigm that we add flexibility by rendering them at the last possible moment.

Regarding the function nesting level and xDebug, I put a breakpoint in a block Twig template to test this. Drupal seems to add 9 function calls to the stack between the rendering of the region template and the rendering of the block template. The total depth of the call stack was 60 at that point, so that's still well within the default value of 256.

All this leads me to believe that it was never a deliberate choice to pass pre-rendered HTML to the region template, but rather the unintentional effect of evolving code, never updated to reflect the new approach. I guess it's finally time to fix that.

KarlShea has already reported that this change works without any issues and my organization is running this patch in multiple production environments. I don't want to set my own patch to RTBC, but if someone else would, we might attract the attention of core maintainers.

karlshea’s picture

Status: Needs review » Reviewed & tested by the community

I'm also now using this on several sites and the patch lgtm.

quietone’s picture

Adding test with 9.3.x

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -274,12 +274,16 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
-        $page[$region]['#theme_wrappers'][] = 'region';
+        $region_content = $page[$region];
+        $page[$region] = [
+          '#theme' => 'region',
+          'region_content' => $region_content,
+        ];
         $page[$region]['#region'] = $region;

This can be written like:

        $page[$region] = [
          '#theme' => 'region',
          'region_content' => $page[$region],
          '#region' => $region
        ];

For less local variable and a single assignment.

alexpott’s picture

Issue tags: +Needs tests

Pinged this issue to @catch (as someone who groks the cache system and know the drupal tea-leaves) and @lauriii (as frontend framework manager) for more thoughts. I think this is pretty interesting and it's great to see that it possible.

It would be good to add some form of test coverage of the new capabilities this gives us.

marcvangend’s picture

Status: Needs work » Needs review
StatusFileSize
new1.69 KB
new724 bytes

Thanks for the review, Alex.

I wasn't quite sure if it is expected and acceptable to both overwrite and wrap $page[$region] in a single statement, but I made that change now. The new patch has also been checked/rerolled against 9.3.x (not that it made any difference).

Good point about tests too. I'm setting this to needs review now, so the new patch can be tested. After that it can go back to needs work for the tests.

marcvangend’s picture

StatusFileSize
new1.69 KB

Oops, missed a comma.

marcvangend’s picture

StatusFileSize
new4.92 KB
new3.24 KB
new2.61 KB

New patch with tests, otherwise unchanged.

marcvangend’s picture

StatusFileSize
new4.94 KB

Yet another patch... Only changes a single comment that I copy-pasted and forgot to edit.

marcvangend’s picture

Issue summary: View changes

To be sure, I did some manual testing regarding cacheability. As far as I can see, everything looks good.

During these manual tests I was not logged in. The page_cache module was not installed because it does not support cache contexts for anonymous users, but dynamic_page_cache was enabled.

To test the cacheability of blocks, I took the following steps:

  • Create a block plugin which prints a timestamp and the value of date_default_timezone_get().
  • Implement getCacheContexts() to add the timezone context.
  • Implement getCacheTags() to add a cache tag based on the timezone.
  • Render the front page multiple times, sometimes overriding the timezone in settings.php.
  • Verify that the block output is cached (timestamp remains the same when the page is reloaded).
  • Verify that the block always shows the correct timezone string (cache varies by timezone context).
  • Verify that the cache tag can be invalidated.

The above works as it should, both with and without the patch.

To test the cacheability of regions, I took the following steps:

  • Implement hook_preprocess_region to do the following:
    • Add a template variable containing the timezone string and a timestamp.
    • Add the 'timezone' cache context to $variables['#cache']['contexts'].
    • Add a boolean template variable which is TRUE when the timezone is 'UTC'.
  • Add a region--footer.html.twig template and adjust it to do the following:
    • Render the timezone/timestamp string.
    • Wrap the "powered by Drupal" block in a div if the timezone is UTC.
  • Render the front page multiple times, sometimes overriding the timezone in settings.php.
  • Verify that the region output is cached (timestamp remains the same when the page is reloaded).
  • Verify that the region always shows the correct timezone string (cache varies by timezone context).
  • Verify that the powered-by block is wrapped in a div only if the timezone is UTC.

The above works as it should when the patch is applied. This could not work without the patch, because you would not have a way to manipulate the region content and wrap a single block in a div.

As you can see, I had to add the 'timezone' cache context in the region preprocessor to make it work. However that is not a shortcoming of this change; as always it is the developer's responsibility to add cacheability metadata when the rendered output is made dependent on a variable such as the timezone.

neclimdul’s picture

Poked at this in blackfire and there is definitely some shifting around of function calls.

First test was with all render and page caching disabled(null backends). Seems to be mostly the same call graphs. Fewer calls on one branch, more similar calls on another branch. The most noticeable change I think seems to be more calls to BubbleableMetadata:merge. The cost of which seems pretty minimal though I don't immediately understand the reason for them.

I ran 3 more comparisons, adding a layer of caching each time with the render cache, dynamic page cache, and full page caches. The call graphs seem identical which is what I would have expected.

marcvangend’s picture

@andypost I'm sorry, I don't see how those issues are related. Can you elaborate?

Please note that this issue doesn't change anything about what is cached and how it is cached. It merely delays the point at which region content (usually blocks) is rendered. The only new way this relates to the caching system, is the situation where the new possibilities are used to A) vary the output of the region or B) change the output using some value from the database. Case A would need a cache context added to the region, while case B would require a cache tag. That said, adding cache contexts and/or cache tags to a region is already supported in Drupal core. My manual testing in #30 just shows that the patch doesn't break it and that it plays nice with the new possibilities.

marcvangend’s picture

Issue summary: View changes

As @lendude asked me in Slack:

I’m just wondering about possible BC implications. If somebody has a hook_preprocess_region somewhere could this lead to a break since the passed $variables might be differently build up now? And if yes, should we care?

In templates, I consider the variable names to be an API (as developer you can assume they don't change) and I expect the output of those variables to be stable. If such a variable is documented to support drilling down (eg. {{ content.field_foo }} in a node template) I consider that part of the API too.

As you can see in template_preprocess_region, we don't change the names of the available variables, only what's inside $variables['content']. Also, the output of {{ content }} is identical after the change. The change does not effect any other variables that may have been added to the template. In other words: we are not breaking any API.

The whole point of this issue is that currently content is a pre-rendered string of HTML. I can only imagine the patch breaking someone's preprocessor if they rely on str_replace, regex etc. to manipulate the HTML. But that's not something Drupal core can support.

marcvangend’s picture

When I wrote "documented to support drilling down" in #34 I realized that we should probably document this in region(-*).html.twig files. The new patch is identical to #28, except that it changes this:

 * - content: The content for this region, typically blocks.

...to this, similar to what is found in node templates:

 * - content: The content for this region, typically blocks. Use {{ content }}
 *   to print them all, or print a subset such as {{ content.page_title }}.
 *   Use {{ content|without('page_title') }} to temporarily suppress the
 *   printing of a given child element.

...in the two dozen places where it occurs.

Status: Needs review » Needs work

The last submitted patch, 35: 3178202-35-render-blocks-later.patch, failed testing. View results

marcvangend’s picture

Status: Needs work » Needs review
StatusFileSize
new26.84 KB
new18.28 KB
new3.25 KB

I never knew we also test file hashes of Classy templates. Fixed the test to match the documentation change introduced in #35.

Status: Needs review » Needs work
marcvangend’s picture

Status: Needs work » Needs review

Test failure in #37 was intentional.

marcvangend’s picture

Version: 9.3.x-dev » 9.4.x-dev
sutharsan’s picture

Issue tags: -Needs tests

Removing the "Needs tests" tag since a test has been added.

kristen pol’s picture

StatusFileSize
new123.77 KB

Thanks for the patch and reviews.

I have tested the patch in #37 as follows:

  1. Installed Drupal 9.3 using Umami profile
  2. Cleared cache
  3. Used wget to get static version of site
  4. Cleared cache
  5. Applied patch
  6. Used wget to get static version of site
  7. Diff'ed the files between the two static sites

The only differences were:

  1. Dom id changes due to cache being cleared
  2. In some places, <div class="region region-tabs">...</div> was added

Example snippet:

diff -r wgetbefore/drupal93.ddev.site/en/node/6 wgetafter/drupal93.ddev.site/en/node/6
130c130,133
<       
---
>         <div class="region region-tabs">
>     
>   </div>
> 
375c378
<         <div><div class="view view-recipe-collections view-id-recipe_collections view-display-id-block js-view-dom-id-48aac50fcefc968128423537f224e650ca69c59b6aa9a94f6866b18f68de0f0e">
---
>         <div><div class="view view-recipe-collections view-id-recipe_collections view-display-id-block js-view-dom-id-686a654ae3ac76ecd01481ca2681daee700aba3bb43c722eb6d673f1542ec50f">

I've attached the full file. The extra div shows up in 77 places. I don't see any mention of this getting added in the comments above but maybe I missed it.

marcvangend’s picture

Issue summary: View changes

Thank you Kristen for your review. The extra div turns out to be a logical effect of the change, but you are right this has not explicitly been mentioned before and I was not aware of this. This only happens in case the content of a region renders to an empty string.

To clarify (because I initially misread your comment): in the case of the Umami demo it is only a single div, the same 'tabs' region, which appears on 77 different URLs.

Analysis

The Umami demo site places one block in the tabs region: block ID 'umami_local_tasks', an instance of the LocalTasksBlock block plugin. For anonymous users there are no local tasks, so the block renders to an empty string. Note that this block is rendered with a #lazy_builder callback, as is the case with most blocks (except instances of MainContentBlockPluginInterface or TitleBlockPluginInterface, see \Drupal\block\BlockViewBuilder::viewMultiple).

Current Drupal core

Currently, the region content is flattened to HTML rather early (the lazy builder isn't as lazy as it could be!). The region.html.twig template then skips rendering the div.region wrapper if that string of HTML is empty ({% if content %}).

After the patch

The essence of this patch is that the region content (ie. blocks) is not rendered before the region template is being processed. Therefore, in the new situation the {% if content %} check in region.html.twig returns TRUE if there is a block to be rendered, regardless of whether that block actually returns HTML or an empty string. This causes a div.region wrapper to be rendered while it used to be skipped.

Possible solution

If this empty div is a problem, it is possible to avoid by changing {% if content %} to {% if content|render|striptags|trim is not empty %}. The Umami theme itself uses this pattern a lot in its page.html.twig, and Olivero does it too. However this method has downsides, see #953034: [meta] Themes improperly check renderable arrays when determining visibility. We could add this to the core region.html.twig templates by default, but I don't think we should be introducing sub-optimal code all over the place. That said, it might make sense to do it in the stable/stable9 themes, since unchanging output is what they promise.

marcvangend’s picture

Status: Needs review » Needs work

Discussed with @lauriii on Slack. His thoughts:

I don’t think we have to solve the empty region element problem here, it’s a pre-existing problem
what I am little concerned about is changing the behavior for existing sites
what could work is instead of replacing content with the render array, create new variable for the render array
then replace the usage of the content variable everywhere except stable and stable9
[...]
I think we could and should mark it [the content variable] as deprecated

Back to needs work.

neclimdul’s picture

Doesn't look like I included this in my previous comment but I'd considered the "change" in behavior for content.

I actually think the current approach cleverly deals with this because in my experience, its impossible to actually do anything in the region template largely because of this issue. And believe me I've seen people try... Because of that I've only seen usable templates that limit themselves to general wrapping logic and {{ content }}

Alternatively, if I understand the suggested change correctly, we're going to have a fully rendered variable and a second render array variable. Then the "correct" way to render a region is to ignore the rendered version and double render all blocks by using the render array.

From a performance standpoint this seems really bad but I'll check the patch when its up.

marcvangend’s picture

Re. #45: Thanks for bringing up performance. Yes, you understand the suggestion correctly. There would be two variables in a region template: the old pre-rendered $content and the new render array variable (let's call it $elements for now). Indeed this approach comes with a performance hit because of double rendering. We should test how noticeable this is.

Actually, the more I think about the $content + $elements approach, the more downsides I see. It feels like we're not solving the problem, but we're trading it for another.

The thing is: at some point in the future we would have to get rid of the deprecated $content variable in favor of $elements. So what happens then? Do we simply remove $content, breaking all templates that still use it? Do we rename $elements to $content, breaking all templates that meanwhile switched to $elements? It seems to me that more sites will be affected at that point, and a removed template variable will probably cause more problems than an extra empty div would.

Of course one could argue that breaking things is more acceptable at the release of a new major version. If that is the consensus, let's just get this ready for Drupal 10, which should be less than 6 months away.

marcvangend’s picture

Version: 9.4.x-dev » 10.0.x-dev
Issue summary: View changes
Status: Needs work » Needs review

All right, let's make a decision before it's too late. The current situation:

  • We have a working patch that has proven itself in production environments, with test coverage.
  • In a very specific situation, the patch adds an empty div to the HTML markup.
  • Our core change policies say about changing markup:
    • "The following types of changes are allowed for minor releases [...] CSS, markup, or template changes (except for stable base themes)"
    • Changing the markup is allowed in alphas of major versions, or in betas "if the impact outweighs the disruption".
  • Avoiding the empty-div situation in the stable base themes is hard, because of the difficulties in determining visibility (#953034: [meta] Themes improperly check renderable arrays when determining visibility).
  • Other proposed workarounds are suboptimal for reasons of performance and DX further down the road.
  • If we wait too long figuring out how to fix this in 9.x, we may miss the opportunity to ship this with 10.x-alpha.

My conclusion: Let's target 10.x, preferably alpha, and get this in core.

marcvangend’s picture

effulgentsia’s picture

Overall, I like a lot what this issue is attempting to do. It's definitely weird that 'content' is a render array in most templates, but a string in region.html.twig.

However, with respect to BC:

+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -274,13 +274,16 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
-        $page[$region]['#theme_wrappers'][] = 'region';
-        $page[$region]['#region'] = $region;
+        $page[$region] = [
+          '#theme' => 'region',
+          'region_content' => $page[$region],
+          '#region' => $region,

There's a potential BC break here that I don't think was mentioned in earlier comments. Within hook_preprocess_region(), $variables['elements'] is currently a render array whose direct children are the render arrays of blocks, but with this patch, it is now a render array with a 'region_content' child that wraps those blocks. This will break uikit, for example.

Our BC policy does allow us to break BC of render arrays, but despite that policy we should think through the implications of that.

I don't know yet what my recommendation is for this. I'll try to comment in the next couple days about that if this hasn't already been committed by then. Just wanted to point out the break itself in the meantime.

marcvangend’s picture

Re #49: Thanks for your input @effulgentsia.

I agree, it would have been nicer if we didn't have to change the structure of the render array. The wrapping region_content was introduced in patch #7 to fix the test failure in #2. The wrapper is not even needed for "normal" pages, only for a very specific case (quoting comment #7):

when a render array returned by a controller has '#type' => 'page' (as \Drupal\system\Controller\BatchController::batchPage does)

So keeping the render array structure unchanged, as patch #2 did, is definitely an option if we find another way to solve the '#type' => 'page' bug.

marcvangend’s picture

Issue tags: +ddd2022
StatusFileSize
new26.8 KB

Trying out an alternative patch in an attempt to address #49. Tests pass locally so I'm curious if testbot agrees.

[edit] Please ignore this patch. It was a renewed attempt at the approach previously used in #2. Although the tests came back green, things actually break. [/edit]

alexpott’s picture

@marcvangend we definitely need a change record here.

alexpott’s picture

Issue tags: +Needs change record
marcvangend’s picture

Thanks for looking into it, Alex. I agree, especially for cases such as uikit, mentioned in #49, a change record is needed. (Not sure if the issue status should be "needs work" then.)

Anyway, I had a closer look at the uikit code to understand why it needs access to $variables['elements']. I found that uikit_preprocess_region aims to place different blocks in three distinct divs within the navbar region template. So that is exactly what this issue enables you to do (albeit without preprocessors and in a way that is consistent with other templates). With this patch applied, the uikit preprocessor could have less code and be more efficient, because currently it needs to load and render blocks that have already been loaded and rendered.

So while I understand that this change may require maintainers to adapt their code to the new render array, it may actually be a change for the better.

marcvangend’s picture

StatusFileSize
new82.03 KB
new27.41 KB
new2.49 KB

Here's a new patch, addressing the BC concerns raised in #49. I managed to "find another way to solve the '#type' => 'page' bug" as suggested in #50.

First let me explain where this '#type' => 'page' bug comes from. Most pages are built using the block system. This happens in \Drupal\block\Plugin\DisplayVariant\BlockPageVariant::build and it results in a nicely structured $page array, where every region element is an array of block render arrays, keyed by block ID ($page[region_name][block_id] = [block_render_array]). However, in some rare cases, controllers take full control by returning a render array that already has '#type' => 'page'. One example is \Drupal\system\Controller\BatchController::batchPage. Batch controller does not follow the $page[region_name][block_id] structure, but instead places a render array with '#type' => 'page_title' directly in $page['header']. As a result, we cannot safely set $page['header']['#theme'] = 'region' because it would break the page title render array.

This new patch detects the edge cases (like the batch page) by checking if the $page[region_name] element is a render array with a #theme or #type property. If so, it is wrapped in an array with key "region_content". In all other cases the structure is not altered: blocks remain the direct children of $variables['elements'].

So, does this mean that with this new patch $variables['elements'] is 100% identical? No. After all, the point of this patch is to render the blocks as late as possible. A block with a lazy builder will not yet be built when it passes through the region preprocessor (see image).

before and after of block render arrays

Status: Needs review » Needs work

The last submitted patch, 55: 3178202-55-render-blocks-later.patch, failed testing. View results

marcvangend’s picture

Status: Needs work » Needs review

I can't reproduce the test failure locally. Rerunning the test.

[update] It seems that the test fail in #56 is unrelated: #3210432: [random test failure] LayoutBuilderQuickEditTest::testQuickEditIgnoresDuplicateFields() Also, it passed on the second try.

catch’s picture

I need to catch up with the latest changes here, but it would be good to summarise if/how it affects #2828136: A single uncacheable block can make the entire website uncacheable.

borisson_’s picture

#58: I have no idea how this affects #2828136: A single uncacheable block can make the entire website uncacheable. It seems like probably does not make a difference, since nothing changes in the cacheability propagation of the blocks themselves? I'm not sure how to answer this question @catch.

+++ b/core/modules/system/tests/src/Functional/Render/RegionContentRenderTest.php
@@ -0,0 +1,66 @@
+ * @group block

I think this is biggest nitpick I've ever given on a patch review, so I'm sorry but I think the block group is not right here, while this does use blocks, I think it tests the render or theme systems more specifically. Looks like @group Theme and @group Render both also exist already.

I think render makes more sense here.

Feel free to disagree though

I also created a CR.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new164 bytes

The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

idebr’s picture

Version: 10.0.x-dev » 10.1.x-dev
Status: Needs work » Needs review
StatusFileSize
new22.26 KB

Reroll for 10.1.x

#59 still needs to be addressed.

borisson_’s picture

Status: Needs review » Needs work
Issue tags: -Needs change record

Needs work for the nitpick in #59

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

tyler36’s picture

StatusFileSize
new39.73 KB

Exactly what I was was looking for!
Confirmed working on Drupal 10.1.3 on demo site.

I'm attaching the results of a test output similar in scope to #43.
The zipfile contains HTML output of pages with Twig and cache debugging on so you can see the template, caching and rendering times.

## Test

1. Installed Drupal 10.1.3 using Umami profile
2. Cleared cache
3. Used wget to get static version of site (see UmamiPages.zip|pre-index.html)
4. Cleared cache
5. Applied patch #61
6. Cleared cache
7. Used wget to get static version of site (see UmamiPages.zip|post-index.html)
8. Used wget to get static version of site (see UmamiPages.zip|post2-index.html)

You can see the cache hashes and render time change between 3 and 7.
Steps 7 & 8 produced identical pages (no cache clear between).

watergate’s picture

Status: Needs work » Needs review
StatusFileSize
new22.26 KB

Thanks a lot for the patch(es); this functionality was/is missing. I can confirm that everything works perfectly on Drupal 10.1.6.

I agree with the nitpick in #3178202-59: Render blocks later, so they can be placed individually in a region template and think that @group Render makes the most sense (and is in line with the rest of the test classes in the namespace). I've updated the patch with the small change :)

mollux’s picture

Status: Needs review » Reviewed & tested by the community

patch in #65 works as expected, I'm able to prevent certain blocks in a region to be rendered

giuseppe87’s picture

Does the #65 requires some manual action?

If I install it, every region.html.twig in the site has the content variable valued as an emtpy string, killing pretty much the render of the site.

The site I've tried is multilanguage and with a good deal of patches\contrib modules, I don't know if something of that is liable.

I'll try as soon as possible on a clean install to see if I can replicate.

giuseppe87’s picture

Status: Reviewed & tested by the community » Needs work
watergate’s picture

Does the #65 requires some manual action?

After applying the patch, everything should work as before. Using {{ content }} should still produce the same output as before. The extra functionality of the patch is that you can output specific blocks like {{ content.page_title }} and {{ content|without('page_title') }}.

Please let us know if you can reproduce the problem using a clean install.

giuseppe87’s picture

I've tried with a D10 clean install and the patch doesn't cause the same problem.

I've identify that the bug I'm facing appears when using the Bootstrap theme.

Being this a core issue, I guess the theme needs to solve the incompatibility, after this patch is merged.

borisson_’s picture

Status: Needs work » Reviewed & tested by the community

I'll set this back to rtbc, after #68/#70 tested manually that with just core, this patches fixes the issue.

catch’s picture

Status: Reviewed & tested by the community » Needs review

Changing render arrays in a minor release is OK, but that's quite major breakage of the bootstrap theme - I think we need to figure out why it's not rendering anything after the change, and either look at whether there's a way to keep it working, or open an issue against bootstrap with how it should change (which will need to support versions of core before and after this change). That information should also go into the change record in case other themes have the same problem.

Moving back to needs review for that.

needs-review-queue-bot’s picture

Status: Needs review » Needs work

The Needs Review Queue Bot tested this issue.

While you are making the above changes, we recommend that you convert this patch to a merge request. Merge requests are preferred over patches. Be sure to hide the old patch files as well. (Converting an issue to a merge request without other contributions to the issue will not receive credit.)

luismagr’s picture

Hi all,

I'm running 10.2.5 and I can confirm it works as expected. I'm building a new theme started with the starterkit and this is working perfectly fine.

I'm not putting this issue back to 'Reviewed & tested by the community' because I can't confirm if it's working or not in a boostrap theme.

Thanks all for your work on this issue

tisteegz’s picture

Patch #65 is working fine with Drupal 10.5.2 using a custom theme based on UIkit.

meeni_dhobale made their first commit to this issue’s fork.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

gcb’s picture

StatusFileSize
new23.95 KB

Here's a patch version of the latest MR.