Updated: Comment #118
HTML links generated by either l(), theme_links() or linkGenerator() all dynamically check whether they are currently "active" by comparing the current URL's structure against the parameters being used to construct the link tag. This "active" class is used very commonly by themers to provide visual cues for users navigating the site, and there is no "browser native" or other CSS equivalent - this functionality can't be achieved without modifying the DOM.
The problem is that we cannot cache these links with any better granularity than "per page". Ideally, we would want to be able to cache links globally as link generation functions can be called hundreds of times on each page load and most sites would get a decent cache "hit rate" as it's very common for the same link to be shown on multiple pages in a site.
This problem existed in Drupal 6 and 7, but there are two main reasons why it is becoming more important to resolve this issue in Drupal 8:
- Render caching is something that we're trying to support as much as possible in D8 core (it exists in D7 but is not widely supported "out of the box"). Any content containing a link "inherits" the per-page cache granularity restriction, even if that content would otherwise be globally cacheable. If the content wrapping the link is only cacheable per-user, we end up with a per-page+per-user limitation potentially due to a single link. .
- The new routes system in Drupal 8 has been profiled a few times with respect to link generation, and while results are preliminary and hard to refine, the pattern seems to be that it is significantly slower than the soon-to-be-deprecated l() function has been historically. This puts pressure on core to avoid uncacheable links wherever possible if we're to stay competitive performance-wise with previous versions of Drupal. , .
There is a very closely related issue with the "active-trail" class that is also applied to links, but not directly from within l(), linkGenerator() or theme_links(). This is also important to address but is out-of-scope here and the two classes should not be confused as they serve distinct purposes.
There are two proposed approaches to a resolution here. There was a third, CSS based approach presented in #64 and #92 but it hasn't gained much momentum as it introduces too many new problems/limitations, see #93.
- Guarantees global cacheability for all links.
- Unblocks future support for "active" class on "raw"
<a>tags in Twig templates - something Twig initiative members and Core Maintainers have requested time and time again over the past few years
- The "old" behaviour can be re-implemented in contrib relatively easily by removing the JS file adding "active" classes and implementing hook_link_alter()
- "active" class logic can be disabled globally pretty easily for sites that don't need it by removing the JS file
- Potential "FOUC" issues, comment #63
Approach #2: "Opt-in" server-side active class logic
This approach adds an extra flag in the parameters passed to link generation functions that will determine whether Core bypasses the "active" class calculations. This means that some links will be globally cacheable and others will retain the per-page limit. Some examples of links that we can be reasonably sure either need or don't need the class can be found in comments #16 and #18.
- Makes an assumption that we know ahead of time what a themer will want to do with a link. Wherever we have uncertainty, we limit our cache granularity unecessarily or we limit what the themer can achieve.
- Introduces complexity and new API features into the system.
- All navigation HTML elements are still only cacheable per-page, but this content would benefit from global caching as it would have a high cache hit rate across different pages.
- Continues to block "proper" usage of
<a>tags in Twig templates.
- It's unclear how the content containing a link determines whether it contains a link that has a "check active" flag set to TRUE or not. Likely it can't, and so this approach only unblocks global caching of more "hard coded" content.
The "opt-in" server-side logic has limitations that will likely prevent us from achieving as much as we would like in the area of render caching and introduces new complexity into the link generation API but does not introduce new client-side dependencies.
Both of JS and the server-side logic. We have some numbers in #78 and #79, some discussion in #74 and #84.
So far, hypothetical numbers like "100-200ms" have been thrown around, unfortunately there is little hard evidence to discuss. The evidence provided actually showed a maximum of 25ms overhead from the JS - but there were no steps to reproduce, so it's hard to discuss this objectively - for example, does this 25ms represent 1 link in the page or 1000?
There is no XHProf profiling of the PHP from either of the JS or server-side approaches.
Both approaches have WIP patches that are close to RTBC-able, both have pros and cons. Profiling of both the server-side and client-side impacts of each approach will hopefully clearly favour a single approach - if not, we're going to have to make some hard decisions.
User interface changes
It is possible that, depending on the final implementation, links that are not "navigation" links will never have the "active" class applied. Otherwise, there will be no UI changes.
The "opt-in" server-side approach introduces a new boolean flag for link generation functions that can be used to bypass/invoke "active" class logic.
- , - profiling shows that route-based link generation is slower than anything we've had in core previously
Original report by @catch
The active class added by l() means that any content rendered with an l() call in it can only be cached per page - otherwise the .active class will be wrong.