Problem/Motivation
- The Views output cache does not track attached libraries, only attached CSS/JS.
- The Views output cache no longer successfully tracks HTML
<head>elements. - The Views output cache fails to track attached CSS/JS correctly.
- The Views output cache does not track associated
#post_render_cachecallbacks (and hence e.g. node links break). - The Views output cache does not track associated cache tags.
So basically, it's completely broken for all associated metadata: #attached, #post_render_cache and cache tags ($build['#cache']['tags']).
I'm pretty sure I at least partially caused this in my work on the render pipeline, particularly in #2273277: Figure out a solution for the problematic interaction between the render system and the theme system when using #pre_render. The Views output cache logic originally worked with the statics of drupal_get_(css|js)(), which explains point 1. There never was test coverage for point 2, which explains point 2. And I don't understand why, but at some point, the gathering of the attached assets was moved to happen before rendering, which could never ever have worked.
The #post_render_cache and cache tags missing is definitely my fault (points 4 & 5).
Proposed resolution
As of #2273277: Figure out a solution for the problematic interaction between the render system and the theme system when using #pre_render, this entire problem space became absolutely trivial. The very act of calling drupal_render() now causes all attachments to be bubbled up to the root level of the render array. So if you call drupal_render($build), then after drupal_render() has finished, $build['#attached'] contains all attachments. Similarly for #post_render_cache and cache tags.
Hence all we need to do is not only cache the return value of drupal_render() (a HTML string), but also the associated attachments, #post_render_cache callbacks and cache tags.
Remaining tasks
Review.
User interface changes
None.
API changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #5 | views_cache_plugin_bubbleable_render_metadata-2368797-5.patch | 9.04 KB | wim leers |
Comments
Comment #1
wim leersI discovered this while working on #2368797: Optimize ajaxPageState to keep Drupal 8 sites fast on high-latency networks, prevent CSS/JS aggregation from taking down sites and use HTTP GET for AJAX requests. That issue only cares about the assets being handled correctly, but we might as well solve all bubbleable render metadata in one go.
Comment #2
wim leersComment #3
wim leersThis is wrong, because Views' output cache does more than what render caching does. E.g. it allows the results to not be cached but to still use the output cache.
Hence removed that from the IS.
Comment #5
wim leersComment #6
catchComment #7
wim leersOpened an unrelated issue in the same area: #2378815: DisplayPluginBase::elementPreRender() and View::preRenderViewElement() are called even when the Views output cache is being used, causing notice.
Comment #8
fabianx commentedSo why not store the render array in $data directly?
Why still have different storage keys?
That would simplify the code a lot.
--
Also the having:
drupal_render() markup
AND
$elements
really warrants a helper function (perhaps in the new Renderer service) to just do:
$cacheable_output = $this->renderer()->getCacheableOutput($markup, $elements);
OR
just have a function that returns a cacheable rendered array:
like
$cacheable_array = $this->renderer()->renderToArray($element);
Comment #9
wim leers#8: only because I don't know what other things want to be stored in there. I don't want to impose render array thinking on Views. Hence I stuck to the original pattern.
Comment #10
wim leerscatch suggested it by marking #2183017: Views doesn't bubble up rendered entities' cache tags as a related issue, on which #2350551: Views fields that have attached assets are lost when Views output caching is enabled was quite recently marked as a related issue… which sparked an IRC discussion with Fabianx, dawehner, damiankloip and I about this. We thought that #2273277: Figure out a solution for the problematic interaction between the render system and the theme system when using #pre_render might have fixed the bubbling of cache tags of rendered entities (or rendered anything, really). So I set out to investigate…
After thoroughly reading through #2183017, reading the Views render pipeline code, stepping through it with a debugger and verifying it through testing, I'm now absolutely certain that #2273277 fixed it!
It's even easy to convince yourself of this as well. Just install a fresh Drupal 8, create an article node that's promoted to the front page, and load the front page, you should see the
node:1cache tag (amongst others, such as the associated taxonomy term's cache tag, the node owner's cache tag, etc.) listed in theX-Drupal-Cache-Tagsheader.If there's any lingering doubt, give it a try on simplytest.me, with the last commit before #2273277 and then the commit that landed #2273277:
BUT! But, the above only verifies that it's working for uncached Views. Since Views has "output caching", which is its own form of render caching, that also needs to support cache tags. So far that wasn't the case. Hence it's not working for cached Views. This issue fixes that too! (Simply by supporting the bubbling of all render metadata, just like
drupal_render()bubbles it.)Conclusion:
Therefore, once this issue lands, #2183017: Views doesn't bubble up rendered entities' cache tags is fully fixed, and #2350551: Views fields that have attached assets are lost when Views output caching is enabled already is!
Comment #11
moshe weitzman commentedOK, Views caching is finally being whipped into submission. Code looks good.
After this, I think we need to look at the UX and implementation of Views cache plugins. Could someone point me to a couple popular Views cache plugins in Contrib (D7 is fine)?
Comment #12
fabianx commentedI want this in as much as anyone else, but:
Views should not need to know about the internal of the render array structure and it is also setting a bad example doing so.
This needs at least a helper function in the Cache class.
See #8.
Damian was okay to use render arrays actually for storage.
If we care about BC break, could use storage['render'] as a new key ...
Comment #13
fabianx commented#11:
views_content_cache
views_row_cache (heavily hacks around views)
would be popular examples.
Comment #14
wim leersI agree in principle, but please look at what it used to do (i.e. does in HEAD):
_drupal_add_html_head()$render_array['#attached']['css']$render_array['#attached']['js']So… it already knows the internals of render arrays. We traded 3 details (head, CSS, JS) for 3 others (attached, cache tags, post render cache), but this time 3 that cover everything. My point is: what you aim for is worthy, but is IMO definitely out of scope.
In the long run, I think that we should somehow get rid of Views' output cache, in favor of regular render caching. That'd address your concerns, remove this brittleness, remove duplication and actually even fix Views' output caching. Because Views currently does this:
… i.e. cache contexts avant-la-lettre. This is inherently wrong, because it:
This should inherit the cache contexts of the things it has rendered: those of the View and the things contained within the View. When converting Views to use regular render caching, we'll have to do that.
Comment #15
wim leersDiscussed #8 & #12 more with Fabianx & damiankloip in IRC; they agree we should do that work in a follow-up. I've created that follow-up: #2379741: Add Renderer::getCacheableRenderArray() to encapsulate which data is needed for caching a render array and have views use it.
Comment #16
fabianx commentedRTBC + 1
Comment #18
catchThere's more we can do here, but I'm happy doing that in the follow-ups and fixing the critical bug here - patch definitely doesn't make further refactoring harder.
Committed/pushed to 8.0.x, thanks!