Placeholders/#lazy_builder is not supported for block component rendering

Created on 11 April 2025, about 2 months ago

Overview

๐Ÿ“Œ Add support for Blocks as Components Active added support for block components.

However the block rendering directly renders the block plugin using $block->build() without support for automatic placeholdering of blocks.

Automatic placeholdering of blocks is supported by block module in BlockViewBuilder::viewMultiple().

It is not supported in layout builder, but there is an open issue since 2019 to fix that ๐Ÿ“Œ Support auto-placeholdering for blocks placed in Layout Builder Needs work .

Proposed resolution

I have a feeling that the logic from BlockViewBuilder::viewMultiple() to build the render array could be copied more or less into the block component plugin, doesn't seem like it would need a huge refactor or anything.

Note that BlockPluginInterface now supports a ::createPlaceholder() method since ๐Ÿ“Œ Create placeholders for more things Active as of 11.2, that might need a method exists check for earlier Drupal versions but would be good to respect that since it leads to significant performance improvements with the current state of 11.2 as well as various in-progress issues.

User interface changes

๐Ÿ› Bug report
Status

Active

Version

0.0

Component

Component sources

Created by

๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom catch

Live updates comments and jobs are added and updated live.
  • Performance

    It affects performance. It is often combined with the Needs profiling tag.

Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @catch
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Well-spotted!

  • ๐Ÿ‡ช๐Ÿ‡ธSpain penyaskito Seville ๐Ÿ’ƒ, Spain ๐Ÿ‡ช๐Ÿ‡ธ, UTC+2 ๐Ÿ‡ช๐Ÿ‡บ
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Two observations:

    1. We can't use exactly as-is, because that uses Block config entities; whereas XB uses the underlying block plugins directly. Still, exactly the same pattern can be applied! ๐Ÿ‘
    2. With one important exception (identified by @penyaskito!): block plugins whose rendering is delayed (by the use of Render API placeholders to execute #lazy_builders as late as possible) prevent emptiness checks necessary only when rendering XB previews

    So, I think we'll want to do something conditional: use the current code path when previewing, use what @catch proposes for end users. That'd look roughly like what's in the attached PoC patch โ€” but lots of details left to get right.

    Thoughts?

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Let's first land ๐Ÿ“Œ Test coverage to prove that BlockPluginInterface cacheability + BigPipe support work when rendered via XB Active , which will clearly demonstrate the problem ๐Ÿ‘

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
  • ๐Ÿ‡จ๐Ÿ‡ญSwitzerland berdir Switzerland

    I don't know too much about XB yet, but I tried a long time ago to do this with page manager, which turned out to be hard to impossible.

    A lazy builder must be able to pass all necessary information as a serialized string.

    block.module blocks are 100% standalone and isolated, identified just by their config entity ID, so that's easy. The problem for page manager was context (the block/plugin context system). For example, you could have a view with a term argument, and on the page manager page, you have multiple terms as context that you pass in to the various views blocks.

    I think XB does not currently have anything like that and not sure if it ever will. But even then, you don't just have a string/ID, You have the whole configuration set for a given block, which might very well include internal information, maybe you have a block with an API key in the config or something like that. So that can't just be serialized out into the HTML placeholder, might also be quite long. Maybe some kind of key-value lookup, a bit like the configuration for entity autocomplete form elements.

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom catch

    You have the whole configuration set for a given block, which might very well include internal information, maybe you have a block with an API key in the config or something like that.

    The lazy builder could take the entity ID of the config or content entity, load it, and then find the config for the block in there. It would need enough information to locate it though, not sure what all of that would be (field name at least on entities etc.) but should be finite.

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    The problem for page manager was context (the block/plugin context system)

    XB doesn't support contexts yet ๐Ÿ˜… For that, we have ๐Ÿ“Œ Handle block contexts Active .

    Very interesting observation though, @Berdir!

    The lazy builder could take the entity ID of the config or content entity, load it, and then find the config for the block in there. It would need enough information to locate it though, not sure what all of that would be (field name at least on entities etc.) but should be finite.

    Yes, I think this indeed doable.

    • Every (content or XB config) entity can only have a single XB component tree. We already have the infrastructure to load it for a given entity: \Drupal\experience_builder\Storage\ComponentTreeLoader
    • Every component instance in an XB component tree is identified by a UUID.
    • The explicit inputs for a component instance are stored in the inputs field property. The structure of that field property is essentially array<component-instance-uuid-string, array|PropSourceArray|AdaptedPropSourceArray|DefaultRelativeUrlPropSourceArray>. See docs/data-model.md#3.2.2 The `field prop` storing the `component input` values.
    • That means for a block component instance's lazy renderer, the only information needed would be:
      1. entity type ID
      2. entity ID
      3. component (config entity) ID
      4. component instance UUID.

      The first two are used to load the entity object and pass it to ComponentTreeLoader:

      $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id);
      $component_tree = $this->componentTreeLoader->load($entity);
      

      The last two are used to load the Component config entity, its source, and to load the stored explicit input from the stored tree's inputs:
      $component = Component::load($component_id);
      $source = $component->getSource();
      // @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\BlockComponent::getExplicitInput()
      $explicit_input = $source->get($component_instance_uuid, $component_tree);

      That should work! ๐Ÿ˜Š

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Point to #12 for implementation approach โ€” thanks for prompting, @Berdir & @catch!

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia Akhil Babu Chengannur

    akhil babu โ†’ made their first commit to this issueโ€™s fork.

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia Akhil Babu Chengannur

    I have created an MR based on the patch in #4 with some additional changes
    1. Got the following error for blocks
    When a #lazy_builder callback is specified, no properties can exist; all properties must be generated by the #lazy_builder callback. You specified the following properties: #prefix, #suffix, #access

    So updated the lazybuilder callback to include the #access, #prefix and #suffix properties. But the Drupal\Tests\experience_builder\Kernel\Plugin\ExperienceBuilder\ComponentSource\BlockComponentTest::testRenderComponentLive fails as #prefix and #suffix are not included in the assertion. Should the test be updated, or is there a better approach?

  • Pipeline finished with Failed
    19 days ago
    Total: 1026s
    #496960
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    I can think of one mechanism: for ComponentTreeHydrated to detect the presence of #lazy_builder or not, and if it exists, for XB to decorate the component source's lazy builder with its own, so that it's XB's "decorating lazy builder" that adds the prefix & suffix.

    P.S.: I doubt my #4 was as precise/accurate as my #12 ๐Ÿ˜…

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
Production build 0.71.5 2024