Drupal 8's page rendering pipeline has evolved several times now in the course of the last 3 years. Originally part of the WSCCI initiative, it split off to the SCOTCH initiative in 2012. After that effort was largely abandoned the WSCCI team has been attempting to complete at least the baseline of work. Unfortunately that has not been as well-organized as it could have been due to lack of resources.

This writeup documents the "8.0-target according to Crell" of where we are still trying to push the page building mechanism. Not all of this may be completed by release, but hopefully by laying it out big-picture we'll get a bit closer than we would have otherwise. I hope...

Overview

There are a few key underlying principles:

  • Above the controller/block level, render arrays have no meaning. Data at this level should be represented by coarser grained objects.
  • Controllers MAY return responses, but in the more typical case they will return a meaningful domain object.
  • Turning a meaningful domain object into a Response is the responsibility of the View event.
  • View listeners in Symfony's HttpKernel are not required to completely handle a controller result. It's entirely allowed for them to modify the result but not set a Response, and then that modified result is available for later-firing listeners. That allows for a very flexible pipeline of enhancing or converting an object from one to another in flexible stages.

The key meaningful domain objects for HTML-based output are, in increasing coarseness:

Render arrays
This isn't properly an "object" but for Drupal purposes we sort of treat it as such. I will refer to it as such for the purposes of this description.
HtmlFragment
This represents a post "turn render array into a string" string of HTML, plus associated metadata. This metadata for example consists of assets as well as cache information.
HtmlPage
This represents an HtmlPage with the entire contents between the body tags as a string, plus associated metadata (HTML links, assets etc.).
Response
A Symfony response object, whose body is now a string.

Note that, in concept, this list is not exhaustive. Any controller may return any meaningful domain object it wants, provided that there is a view listener registered that will turn it into a Response. That is implicit in the design of the Symfony HttpKernel pipeline. We're simply leveraging it. (Symfony does too in some cases; with FrameworkExtraBundle you can annotate a controller to specify a twig template to use, then return an array. A view listener will take the array and run it through the twig template specified.)

For each of those meaningful domain objects there is a free-standing service that converts it to the next level up:

render array->HtmlFragment
RenderHtmlRenderer (coming in #2256365: Factor render->fragment code out to a service)
HtmlFragment->HtmlPage
\Drupal\Core\Page\HtmlFragmentRendererInterface (although in practice I don't see any alternate implementations to DefaultHtmlFragmentRenderer)
HtmlPage->Response
\Drupal\Core\Page\HtmlFragmentRendererInterface\HtmlPageRendererInterface (and its core default, DefaultHtmlPageRenderer

The latter two, realistically, will only be used once per request but are still isolated operations unto themselves and thus their own services. The first could conceivably be used multiple times. (See next section.) They are collectively called "Renderers" because... that's the name we came up with when they went in. :-) (And they "render" one object into the next.) I am not opposed to a dedicated issue to rename them collectively, as long as it's consistent and not confusing with other subsystems.

Those services are entirely divorced from HttpKernel. However, they are wired into it as view listeners that fire in that order. The listener is simply a bridge to the functionality, as it should be. (See Drupal\Core\EventSubscriber\HtmlViewSubscriber.) There are also ample "gaps" between the priority of those listeners where other subsystems (core or contrib) may interject their own logic to either conditionally pre-empt the defaults, inject additional data into those objects (eg, additional metadata), and so on.

Note that this means, in concept, that one could return an entity from a controller, or a Form object itself, or some other classed object from a controller and as long as there's a service wired in via a View listener with higher priority than HtmlFragmentRenderer, and it converts whatever that object is to an HtmlFragment, everything will still work. It would not even, technically, be an API change. That's kinda nice. :-) That would allow for the pipeline described in https://www.drupal.org/node/2092647#comment-8874069 and following (which is a longer term goal but we cannot remove the alternate _-properties before 8.0.0, which means they must remain supported until at least 9.0.0).

It also means that those steps may vary depending on any conditions desired. For instance, an HtmlFragment (containing “the main content”) could be turned into an HtmlPage (with surrounding blocks) if the request format is HTML, but it could also be turned into an AjaxResponse (with just the main content, and no surrounding blocks, since that’s probably not useful when wanting an AJAX response) if the mime type is something else. We actually have an enormous amount of flexibility in how we manage the "thing that gets returned" by putting all of the logic between a very thin controller and the point that we have an actual response object.

Metadata

The above description referred to "associated metadata" several times. The term changes in just about every issue; I am using the term "associated metadata" for this writeup because I don't believe we have a firmly established noun Defined(tm).

Associated metadata refers, generically, to "things that this string of HTML needs in the head tag of the page or in a Response header". That includes, but is not limited to, a title (for the page), CSS, Javascript, Links, Meta tags, the HTTP response code, etc. Render arrays store that data somewhat haphazardly at the moment. HtmlFragment and HtmlPage incorporate it as structured objects.

For Drupal-specific purposes, cache tag information is also part of "associated metadata".

Note that this process allows for very robust "metadata alter" process with no additional work. Such alteration is just another View listener that happens to sit at the right position in the pipeline. The most obvious use case for that is CSS/JS aggregation. However, it is also the appropriate place for modules like Google Analytics, Metatags, and others to inject page-level information. (Eg, inject Google Analytics-required header tags, but only for HTTP 200 pages.) All they need do is register a listener at the right level of coarseness to modify the object at the appropriate level.

Why HtmlFragments?

Strictly speaking, there's nothing that HtmlFragments can do that a render array couldn't. They are both data structures that hold information. However, HtmlFragments have a key advantage: formality. Render arrays are an entirely implicit unstructured data structure. That makes them, by design, difficult to document, impossible to "discover", and difficult to reason about.

A few related links are, I think, helpful. The first is a short piece by chx on discoverability. The second is a longer piece on strong typing, which classes give but render arrays naturally are not:

http://www.drupal4hu.com/node/403
http://techblog.realestate.com.au/the-abject-failure-of-weak-typing/

Being easier to reason about and work with is at one level "just" a DX problem. However, that also makes it conceptually feasible to have, for example, the multi-stage conversion process described above. While hypothetically possible to represent HtmlFragment and HtmlPage as just anonymous arrays, that would make the conversion code more complex because you'd have to determine which type of array something is at any given time. Having structured, purpose-built data objects has a knock-on effect similar to having cleanly separated services. It allows for thought processes and conceptualizations that would have been too difficult to be feasible previously by abstracting concepts out to the right level.

For the moment, the primary use of that is to make the page layout process more consistent and simpler. (See next section.) However, it also makes it conceptually easier to reason about rendering blocks in isolation (necessary for ESI, for hInclude, for Ajax callbacks to refresh just one block on a page, etc.). That conceptually simpler page rendering process, in turn, will enable more experimentation and innovation in contrib and later core versions.

There are, I expect, many other benefits that we simply cannot see now, but just as splitting up our logic to clean services has enabled easy-peasy backend switching (https://www.drupal.org/node/2306083) or the normalization of the entity/field system was a prerequisite to generic REST support, I am confident that a commitment to a clean, object-oriented renderable/output pipeline will pay off dividends we haven't even thought of yet.

Page rendering

Obviously the most complex step in that process is the HtmlFragment->HtmlPage conversion process, also known as "rendering the page".

A Page, conceptually, consists of one or more HtmlFragments arranged in a layout. One of those HtmlFragments is the one returned by the controller or created by a previous View listener from whatever was returned by the controller. The others are... blocks. Blocks are just HtmlFragments, or rather HtmlFragmentInterface objects, whose content and metadata are calculated on the fly rather than assigned externally.

There is nothing in a page that is not an HtmlFragment or part of the layout. Nothing.

A layout consists of a template, regions, and configuration for what HtmlFragments go into what regions.

Because blocks are themselves HtmlFragments, no special handling is needed to merge them. At most, there may be interesting logic happening for loading blocks but that's it. And probably not even that as all blocks are plugins, so they should be fairly consistent.

Caching

Caching is, of course, a big deal for Drupal. We have a lot of it, and it's not always consistent. For the moment we're speaking specifically of partial output caching. Render arrays already have cache tags and callbacks in them but they are often difficult to use.

HtmlFragments already have the ability to carry cache tags and similar information, which represent the relevant cache tags that were used to generate the fragment's body. When a render array is converted to an HtmlFragment, those cache tags can be transferred over to it. Thus, future cache clears will affect a cached fragment. As of yet we do not

While HtmlFragment is coarser grained than render arrays, that's fine. Very often we don't need to go more coarsely grained, as a given page body or a given block do not vary on much at all. As the underlying render array's caching is still present even if we do bust the cache on a particularly context-sensitive HtmlFragment the render array caching below it is still in place.

However, having the fragment cached on its own allows us to then load and reuse it very cheaply. Think Panels pane caching, but for every block equally. Or for the regular go-to example, ESI/hInclude. That cache information can even be then listed in the URI of a solo-block. That likely won't be included in core in 8.0.0 but it opens up the potential to do so in 8.1.

Differences from status quo, circa August 2014

We are not quite at the big picture described above. There are three main areas where we are still falling short.

1) Wrapping controllers

This architecture predates the introduction of HtmlFragment. In this model (currently implemented in HEAD), the negotiation for what to do with "the thing the controller returns" happens before the controller is called. In the model described above, it happens after. That made sense when we had nothing more robust than render arrays to describe the return value. Now that we do, however, I believe post-controller is, in the long run, the better approach.

The work happening in #2256365: Factor render->fragment code out to a service is step one of transitioning to post-controller negotiation; all it attempts to do is create RenderHtmlRenderer as described above: no more, no less. Follow-ups (again, non-API breaking) would be to deprecate and remove HtmlControllerBase entirely. That of course brings up the question of what to do with forms (wrap them in an HtmlFragmentInterface-implementing object themselves, perhaps?) and the other wrapping controllers. What to do with those is a more interesting question than just moving code around, hence why it is not included in that issue.

For API-stability reasons we cannot and will not remove the alternate _-properties from routes, so those will remain supported. How we go about handling those, however, is not an API change and flexible for us to refactor as long as the promised behavior (eg, "a form will appear at this URL as the body of the page") remain intact.

It may also be wise to have another sub-type of HtmlFragmentInterface specifically for the controller fragment (HtmlMainFragmentInterface? Eh, naming is hard) that is uniquely able to carry things like the status code and page title, so that blocks cannot. It could also potentially be leveraged in the future for ordering of assets so that, e.g., main body CSS and JS can be loaded first, or prioritized in an HTTP 2.0 resource stream.

The other caveat is that currently controllers may return strings, not just render arrays. Strings are an even more primitive version of a render array, essentially. If we go all the way to removing the wrapping controllers then we will need some step that converts a string controller response to a render array or straight to an HtmlFragment. While that could be done as its own step, that seems unnecessary to me. Instead, I believe folding that if statement into RenderHtmlRenderer is the cleanest path. That does tacitly mean that strings are sort-of a render array, kind of, but I think that's acceptable in order to maintain current functionality (ie, no API break) and avoid a pointless wrapper class or extra event listener that would do nothing other than return ['#markup' => $parameter];

2) Assets

Currently, HtmlFragment still does not handle CSS and Javascript. Those are the "interesting" associated metadata as they require non-trivial preprocessing. Sam Boyer has a considerable amount of work done to build a framework for declaring and mangling those assets built on Assetic (#1762204: Introduce Assetic compatibility layer for core's internal handling of assets), but that work was abandoned months ago and my attempts to get someone else to pick it up have been unsuccessful.

At this time, I think it is best to just ignore aggregation and build the rest of it, relegating aggregation to a View listener with a big message saying "insert aggregation here". That would be fairly straightforward to do, in fact, as there's little hard logic there. It's just shuffling data around. I do not know if catch would support such an approach, however, but I do not have any better approach at this time that is actionable given the resources available. I do believe that HtmlFragment not supporting CSS/JS is a release-blocking issue.

3) Page building

I have no expectation whatsoever that the full vision of SCOTCH (multiple contextually-defined layouts) is going to happen in 8.0.0. However, in order to make that easy to implement via contrib we must, I believe, still finish lining up the dominos. That means two things, primarily:

  1. Making blocks implement HtmlFragment directly (or via a child interface, but $block instanceof HtmlFragmentInterface should return true) so that page renderers (core's default or otherwise) can treat blocks and the main content area the same. To avoid massively rewriting all blocks, again, Sam Boyer suggested a trait that we shove into all existing blocks that lets them continue to work as they do now. That would mostly replace BlockBase (which is already extremely problematic as the API for its child classes is nothing at all like BlockInterface), but the patch would consist of 1-2 lines for each existing block, nothing significant. We could then rewrite core blocks at our leisure, even post 8.0.0, to use the updated interface directly.
  2. Adapt DefaultHtmlPageRenderer to have no external sideways dependencies. That is, we do not need to turn DefaultHtmlPageRenderer into Panels. We just need to insulate it so that the rest of the system can't depend on its implementation details. That means, primarily, eliminate hook_page_alter() and hook_page_build(). The former should be easier, but the latter would need to be done in concert with the changes to blocks from point A (since blocks currently alter themselves onto the page via hook_page_build().) We would likely also need to internalize drupal_prepare_page(), as that logic is really part of the "default" page building process.

The resulting code doesn't have to be pretty! It need only limits its surface area to HtmlMainFragment and HtmlFragment-based blocks. It should still be hard coded to the current block configuration process. As long as that is done we can easily gut and rewrite it post-8.0.0 without an API break, and contrib can drop in alternate implementations, including a Chained implementation, without disrupting anything.

The primary blocker for all of these steps is resources. I do not personally have the time to do most of it; maybe 10% of it. If others want to help, and have the domain-area skills to do so, please let me know because I desperately need volunteer hours to get this done.

The steps

I've tried to create at least a few stub issues that move us along this path. They do, in many cases, have dependencies. That is sadly unavoidable. If we can agree on the general vision then some of them may be able to break down to smaller issues that on their own do nothing useful, but help move an issue forward without conflicting with another patch. That's up to the committers to decide if they're OK with.

Crell thinks are beta target:

#2256365: Factor render->fragment code out to a service - Create RenderHtmlRenderer service
#2334207: Revisit naming of "renderers" in the page building pipeline - Revisit naming of "renderers" in the page building pipeline
#2334029: Add js/css/libraries/head links to HTML fragments and pass it along to the page. Assigned to: dawehner - Add CSS/JS support to HtmlFragment
#2296951: Expand the support of head elements to allow every common head element. - Add arbitrary HEAD element support to HtmlFragment

Can probably be post-beta:

#2295609: Break the dependency of MaintenanceModeSubscriber on DefaultHtmlPageRenderer Assigned to: damiankloip - Eliminate usage of DefaultHtmlPageRenderer::renderPage() (which is a fugly BC hack) from maintenance page handling, so we can get closer to eliminating DefaultHtmlPageRenderer::renderPage().
#2334219: Make blocks expose HtmlFragmentInterface directly - Make blocks expose HtmlFragmentInterface directly (some variations of this could be post-beta, see discussion in the thread)
#2334225: Move CSS/JS preprocessing to a View listener - Move CSS/JS preprocessing to a View listener
#2334213: Split HtmlMainFragment off of HtmlFragment for the page body - Split HtmlMainFragment off of HtmlFragment for the page body
#2334217: Internalize drupal_prepare_page() - Eliminate drupal_prepare_page() (no module dev should be touching this anyway)
#2334229: Move controller render array -> HtmlFragment conversion to a View listener; eliminate HtmlPageController - Move controller render array -> HtmlFragment conversion to a View listener; eliminate HtmlPageController
#2334233: Move Modal/Dialog detection to a View listener - Move Modal/Dialog detection to a View listener
#2334235: Replace AjaxController with a View listener - Replace AjaxController with a View listener
#2334247: Replace FormController and children with wrapping closures and View listeners - Replace FormController and children with wrapping closures and View listeners
#2334191: Move main content fallback into the block configuration level - Eliminate special handing for missing main content block
#2334221: Move block building from hook_page_build() to DefaultHtmlPageRenderer - Move block building from hook_page_build() to DefaultHtmlPageRenderer

Comments

Wim Leers’s picture

dawehner’s picture

Crell has an actual plan in mind, which is roughly like this:

  • Each element on the page is rendered as html fragment, together with its attached assets (you can pretty much think of blocks)
  • These html fragments are either generated by the block system or by the main html page controller
  • These fragments are put together to represent a html page
  • This html page is then rendered
sdboyer’s picture

round and round we go!

Crell’s picture

sdboyer: The high-level plan hasn't changed since 2009. Just the implementation details.

Wim: I will try to write up a more complete picture of the ideal pipeline shortly. You're correct that there's still some cruft lying about from earlier steps toward it. As always, labor shortage is the primary reason we're not there yet.

The biggest challenge is finding someone with the time and expertise to handle turning blocks into HtmlFragments, and finishing the asset management work. Suckers volunteers welcome.

Michael Hodge Jr’s picture

@Crell, is there instructions on how to go about making these changes? I've been a drupal module developer for years, but am new to core contrib, so it may very well be over my head. That being said, I do have time, am a glutton for punishment and am willing to at least take a look and see if I can help out.

Crell’s picture

Michael: Stop by #drupal-wscci in IRC sometime today and I'll see if I can explain the context. It's not the best first-time-in-core type task, though, I fear.

Wim Leers’s picture

I will try to write up a more complete picture of the ideal pipeline shortly.

Awesome, thank you! :)

You're correct that there's still some cruft lying about from earlier steps toward it.

… and that's completely understandable.

Crell’s picture

Title: Untangle Drupal 8 page rendering » [Meta] Untangle Drupal 8 page rendering
Issue summary: View changes
Crell’s picture

Issue summary: View changes
almaudoh’s picture

Really interested in this. I would like to volunteer some time in the next week to help with this.

Crell’s picture

Issue summary: View changes
Crell’s picture

Issue summary: View changes
marcvangend’s picture

Crell, thanks for the extensive issue summary.

For instance, an HtmlFragment (containing “the main content”) could be turned into an HtmlPage (with surrounding blocks) if the request format is HTML, but it could also be turned into an AjaxResponse (with just the main content, and no surrounding blocks, since that’s probably not useful when wanting an AJAX response) if the mime type is something else.

Does this mean that any request, for instance an AJAX request for just the main content, will still build all other HtmlFragments (ie. the surrounding blocks), even if they will not be included in the response? Or is there some smart system elsewhere to make sure only the necessary page elements are built? I guess this can have a significant impact on performance, especially on systems that make lots of AJAX / ESI / etc calls.

RainbowArray’s picture

Overall, I think this is fantastic.

One thing I'm trying to get my head around (which woke me up early this morning) is how to square this with c4rl's plans for a OO render API, which could admittedly be a fair distance down the pike.

See:
https://austin2014.drupal.org/session/markup-aint-easy-or-how-i-learned-...
http://www.slideshare.net/c4rl/markup-aint-easy-or
https://github.com/c4rl/renderapi
#1843798: [meta] Refactor Render API to be OO

Specifically, I'm wondering how this will work in conjunction with the desire for drillable variables in Twig, which is one possible benefit of a OO render API. There was an effort to make that work without OO in #2008450: Provide for a drillable variable structure in Twig templates, but that seems pretty stalled.

My understanding from the issue summary is that a render array is turned into string when an HtmlFragment is created, and my tentative understanding is that for content this primarily happens at the block level. Once a render array (or a render object) is a string of markup, I can't see how drillability is possible above that. If I'm right and this conversion is happening at the block level, then really the only place where drillability wouldn't be possible is in the page and html templates. That's probably fine, although in theory there could be some edge cases where that's annoying.

If turning render info into a HtmlFragment happened below the block level, I would image drillability could become more of an issue depending on what level the conversion to string happened.

This thought was bugging me, so just wanted to get it down somewhere.

Crell’s picture

marcvangend: Nope. The blocks would not get built until HtmlFragmentRenderer gets called (that's already the case). If an Ajax listener, or dialog listener, or whatever fires before the listener for HtmlFragmentRenderer and generates a Response then HtmlFragmentRenderer will never even be called. In fact the subscriber class it's called from will never even be instantiated. That's a natural part of the Symfony event system that we benefit from; no separate "smart system" needed.

mdrummond: Render-OOP is not on the 8.0.0 roadmap, even remotely. As for drillability, you're correct that would only work at the level at which a render array is turned into a fragment, which should be, generally, the block/controller level. So yes, hook_page_alter() becomes a meaningless concept, as it should. However, most of the themers I know would rather have more "Shallow and broad" templates anyway rather than deep drillable trees of arbitrary arrays accessed in dozens of micro-templates. I actually encourage people in D8 to make their controllers and blocks trivially simple, just tossing data to the template, and let the template, at the block/controller level, do all the hard work. (This is a Morten.dk-approved position.) Either way, the writeup above doesn't greatly change the status quo in this regard as the content area is already rendered pre-page-build today, so we no longer have the entire page in one massive abstract syntax tree anyway. (And that's a very good thing.)

joelpittet’s picture

However, most of the themers I know would rather have
more "Shallow and broad" templates anyway rather than deep drillable trees of
arbitrary arrays accessed in dozens of micro-templates. -- Crell

I want to highlight that quote from @Crell, maybe even put it on a wall somewhere and chant it every morning:)

One key thing in my mind that makes this quite tricky to achieve is the Field UI's ability to sort items aka #weight

I'm fine with formatters, love them even sometimes.. {{ entity.field.view() }} {{ entity.field.value() }} {{ entity.field.raw() }} getting at the field's data representation is awesome and I feel I'm learning new tricks thanks to a talk with @Berdir. Though sorting element children is not a light operation, it happens a lot and if I template designer tries to target these fields and mark them up within the templates markup all that looping element_children and sorting was an expensive endevour and they've just unintentionally(or maybe not) prevented a potential site builder from controling the field output order within a given region of an entity. I've not come up with a reconsiliation here, nor if there needs to be one but if there is no solution it may need to be made a bit clearer what the trade offs are when using fields in templates instead of {{ content }}

I digress and say thank you for that quote @Crell :)

yched’s picture

@joelpittet: yeah, control in the UI vs. control in the template is one long-standing dilemma running from CCK. Historically, Drupal is UI driven.

Basically {{ content }} is what allows you as a site admin, or you as a contrib module, to add new fields to a node and have them displayed out of the box without requiring a manual edit of your templates... The whole render() thing in D7 was introduced to allow granular placement of individual fields in a template while keeping this crucial "catch all / works out of the box" behavior - which then in turns breaks UI ordering...

In short: long and tricky story...

(also, careful with entity.field.view(), it's slower than the regular rendering using formatters configured in the UI)

joelpittet’s picture

Thanks @yched, I'll look for you and @crell in Amsterdam to help out with this any way I can. Splitting my time finishing up auto escape issues, the ~25 theme functions left in core before and helping banana to keep truckin and front end with Lewis will I'm there.

joelpittet’s picture

Slow mobile dupe

joelpittet’s picture

Ditto

catch’s picture

If hook_page_build() goes, there needs to be a sensible place to add assets for HTML responses. See the implementations at: https://api.drupal.org/api/drupal/core%21modules%21system%21system.api.p...

There's also a handful of core implementations that are doing things other than adding assets, couldn't find one that looked very valid, but they'd need to be moved/refactored regardless.

even if we do bust the cache on a particularly context-sensitive HtmlFragment

This suggests that HtmlFragment would implement application-level caching in a similar way to render caching, I haven't seen that discussed anywhere that I can remember.

Cache tags are currently used in two places for caching of HTML:

Render arrays (via render caching)
Page caching.

In the case of page caching, we don't directly pass the tags up to that level, but rely on a custom header instead. If HtmlFragment passed the cache tags upwards we might be able to use that instead of the header. If HtmlFragment get rendered outside of a full page context (i.e. an *SI callback) then they also have the cache tag custom header for invalidation via varnish or similar. Where would application-level caching of HtmlFragment (as a single unit) get used?

Fabianx’s picture

IMHO, a HtmlFragment would still use render caching for the caching itself.

It is just a stepping stone for allowing caching and a HtmlFragment can have cache information, like ttl, granularity, tags, etc.

IMHO (and that has not been discussed yet) an HtmlFragment however should work more like a placeholder and replace the #pre_render pattern alltogether.

e.g. a BlockHtmlFragment should be able to render the contained block, like an ESI address with a context, etc.

That would allow to store e.g. a page plus 'placeholders' that are then replaced based on different strategies (ESI, AJAX, Big Pipe, etc.).

#post_render_cache is nice, but IMHO what Drupal really needs is a concept of placeholders that are referencing HtmlFragments.

I really should talk with Crell sometime :).

Crell’s picture

catch: Yes, that would be a View listener that fires after the fragment renderer (produces an HtmlPage) and before page renderer (which produces a Response). And technically one could weight it before or after the CSS/JS aggregation step depending on which was preferred. (Most people would want to go before it, presumably, but it would be no harder to go after it if it made sense to do so.) That is already possible and works for links and meta tags. All that's missing is the CSS/JS support on fragment/page.

For caching, in concept one could cache an HtmlFragment object itself based on the tags it has on it. Then load it, and it it's not available in the cache rebuild it again from the block or controller or whatever. There are likely far more intricate issues with that than I am thinking of right now and it's definitely not a first-round thing, so if for the near-term the cache tags on fragments are "just" for passing on up to turn into an HTTP header for varnish that's good enough for now. We can investigate more advanced things with it later, probably post-8.0.0.

Wim Leers’s picture

Thus, future cache clears will affect a cached fragment. As of yet we do not

The sentence ends abruptly there. What is missing? :)

However, having the fragment cached on its own allows us to then load and reuse it very cheaply. Think Panels pane caching, but for every block equally.

The implication here is that HTML fragment caching is going to be more performant than render array caching. I strongly doubt this is going to be the case. A cached render array also just stored the content in a an array key (just like HTML fragments store it in an object property), in #markup — it doesn't store the full array. So can you clarify this?

Or for the regular go-to example, ESI/hInclude. That cache information can even be then listed in the URI of a solo-block. That likely won't be included in core in 8.0.0 but it opens up the potential to do so in 8.1.

I think this conceptual clarity is the single most important thing about HTML fragments.

Making blocks implement HtmlFragment directly (or via a child interface, but $block instanceof HtmlFragmentInterface should return true) so that page renderers (core's default or otherwise) can treat blocks and the main content area the same.

What about first letting FullPageVariant (which is the thing that combines blocks into a page) return a HTML fragment so that we can make everything above that only HTML fragment-aware and then working on turning blocks into HTML fragments?
Also see #2334219-1: Make blocks expose HtmlFragmentInterface directly and #2334221-1: Move block building from hook_page_build() to DefaultHtmlPageRenderer.


#21:

This suggests that HtmlFragment would implement application-level caching in a similar way to render caching, I haven't seen that discussed anywhere that I can remember.

That's also something I pointed out at #2334219-1: Make blocks expose HtmlFragmentInterface directly.


#22:

IMHO (and that has not been discussed yet) an HtmlFragment however should work more like a placeholder and replace the #pre_render pattern alltogether.

e.g. a BlockHtmlFragment should be able to render the contained block, like an ESI address with a context, etc.

Hrm… isn't that kind of the opposite of what Crell wants to achieve? :)


#23: You didn't answer #22 AFAICT, but this made me frown:

For caching, in concept one could cache an HtmlFragment object itself based on the tags it has on it. Then load it, and it it's not available in the cache rebuild it again from the block or controller or whatever.

That's a lot of handwaving right there… You cannot cache something based on tags; tags are for invalidation, not for building a cache ID. Could you clarify this?

Crell’s picture

Issue tags: +WSCCI
Crell’s picture

Issue summary: View changes
catch’s picture

We discussed this at DrupalCon Amsterdam. I'm in the process of opening up the actionable issues that resulted from that discussion.

They are in most cases not the same as the issues linked here.

#2350943: [Meta] Untangle Drupal 8 page rendering will eventually take over as the meta critical issue (once all the sub-issues are in place and there's an issue summary). For now please review sub-issues of that issue.

catch’s picture

Title: [Meta] Untangle Drupal 8 page rendering » [Meta] Page rendering meta discussion
Version: 8.0.x-dev » 8.1.x-dev
Priority: Critical » Normal
Status: Active » Postponed

Following a long discussion at DrupalCon Amsterdam, Wim and I have opened #2350943: [Meta] Untangle Drupal 8 page rendering, to document the steps for getting 8.0.x page rendering system into a coherent state. That will address the 'critical' parts of this issue. There may be additional sub-issues to add to this and flesh out, but it roughly documents what was agreed.

Moving this to 8.1.x, some of the sub-issues linked from here may not be critical but we can evaluate them as DX improvements for minor versions or 9.x depending on scope.

andypost’s picture

looks that could be closed

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

Status: Postponed » Needs review

There's only one issue left here #2295609: Break the dependency of MaintenanceModeSubscriber on DefaultHtmlPageRenderer
Maybe fixed is a proper status?

Fabianx’s picture

Status: Needs review » Fixed

I do agree.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.