Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

"new" and "updated" marker no longer breaking render cache

The comment "new" marker is based on the user's last viewed timestamp, which means it's forced to be per-user. That made comments only cacheable per-user. Hence it broke the render cache.

This was solved by the following measures:

  1. Embed metadata that is the same for all users in the HTML as data- attributes: the node's last comment timestamp, the node's ID, the comment user ID, each comment timestamp.
  2. Attach JavaScript that adds the "new" and "updated" markers, which has to perform a HTTP request to know the latest time the current user read a node (and hence also its comments). Use heuristics (i.e. any content >30 days old doesn't get a "new" or "updated" marker) and client-side caching (localStorage) to avoid performing a HTTP request in most scenarios.
  3. See #1991684-40: Node history markers (comment & node "new" indicator, "x new comments" links) forces render caching to be per user for a comprehensive overview of the heuristics used to minimize HTTP requests.

There are quite a few different places where this is used: comment "new" indicators, node "new comments" links, tracker "new/updated" node indicators, tracker "x new replies" links, plus use cases in the forum module. All of them should use the same heuristics and client-side caching, to prevent duplicate logic and maximize client-side cache hit ratio.

This enables entity render caching: #1605290: Enable entity render caching with cache tag support.

History JS API

That's why there's now a new History JS API, in the drupal.history library. It has the following methods:

  1. needsServerCheck(nodeID, contentTimestamp: determines whether a server check is necessary. This can be used to consistently apply the heuristics. Any content that is >30 days old never gets a "new" or "updated" indicator, because it is assumed to have been read already. Any content that was published before the known "last read" timestamp (as cached in localStorage) also is assumed to have been read already.
    In other words: old content and content that's already been read can be ignored completely if this function returns false.
  2. fetchTimestamps(nodeIDs, callback): fetches the "last read" timestamps for the given nodes, stores them in localStorage and calls the callback, which can then call getLastRead(nodeID). You should only pass in node IDs for which needsServerCheck() returned true — i.e. the node IDs that need a server check.
  3. getLastRead(nodeID): gets the "last read" timestamp for the given node, as stored in localStorage. Should be called after fetchTimestamps() has done its thing and called the callback.
  4. markAsRead(nodeID): to mark the given node as read on the server side; automatically updates the client-side cache (to minimize HTTP requests further).

Look at the drupal.comment-new-indicator or drupal.node-new-comments-link libraries for sample code that uses the History JS API.

User-specific styling ("comment by viewer") no longer breaking render cache

Similarly, the by-viewer class was being set in PHP, and is user-specific, hence also broke the render cache. The solution there was to add some JavaScript (see the drupal.comment-by-viewer library), that combined with a data-comment-user-id attribute on each comment and a new drupalSettings.user.uid setting knows whether the current user is also the commenter.

Impacts: 
Site builders, administrators, editors
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done

Comments

cosmicdreams’s picture

Do we typically include code examples for new APIs? If so it would be great to distill the above information into a few code examples of the API in use.