Part 1 | Part 2 | Part 3 | Part 4 | Part 5 |

Table of Contents


A debate has been ongoing for several years about how Drupal's front end can compete against the primacy of JavaScript frameworks that are rapidly gaining steam in the wider web development community. Decoupled Drupal, for instance, may be the theme of my book Decoupled Drupal in Practice as well as an annual conference in New York City, but it by no means resolves the myriad issues that hinder the implementation of features like reactivity, offline capabilities, and real-time functionality in Drupal.

Recently, Fabian Franz (Senior Technical Architect and Performance Lead at Tag1 Consulting) gave a stirring talk about how Drupal's front end needs to evolve in order to catch up to the competition and ensure that it continues to be a dominant player in the front-end development landscape. In Fabian's ideal presentation layer, Web Components are the atomic unit that enables encapsulation, and the ecosystem can open the door to the reactivity and real-time features it needs to survive in an increasingly unrecognizable front-end world.

In this multi-part blog series, we review the most important concepts behind this potential future for Drupal's front end, including Web Components, virtual DOMs, and what Drupal can learn from other ecosystems. In this second installment in the series, we examine how Drupal's render tree bears striking similarities to virtual DOMs in other frameworks and what future Drupal versions could look like under the hood.

How Drupal's render tree is like a virtual DOM

Before we turn to some of the newer concepts in the front-end landscape, we should examine some historical background. Drupal has long relied on a render tree that dictates what and how elements should be rendered for Drupal's presentation layer. Drupal's render tree demonstrates particular similarities to the virtual DOMs (document object models) currently in vogue in the JavaScript landscape, but it has one key flaw that the Drupal community has seldom discussed, according to Fabian.

A bit of history from Drupal 5 through Drupal 8

In Drupal 5, for example, we had the following approach to rendering using theme functions, which facilitated early rendering:

    $html = theme('item-list', $vars);

The template housed all logic, and it was not what we could consider a pure component. In Drupal 6, however, this changed thanks to the work of Earl Miles (the creator of the Views module) to introduce preprocess functions. Now, in addition to the theme functions available in Drupal 5, developers could also write preprocess functions to modify rendering output.

In short, preprocess functions in Drupal are responsible for preparing the data ahead of time and displaying it in a new way. In Drupal 7, however, Drupal's approach to rendering evolved significantly but still lacked pure templates. Rather than rendering early, Drupal 7 allowed developers to build a render tree, render that tree, and yield PHP arrays like the following:

    $build = [
      '#themes' => 'item-list',
      '#items' => $items
    ]

During the Drupal 8 development cycle, core contributors resolved to implement Twig, the templating language now in use across Drupal 8 sites around the world. Along with the release of Drupal 8 came BigPipe, placeholders, and Dynamic Page Cache, all of which were intended to improve perceived front-end performance. And through these efforts, rendering was shunted to progressively later points in the Drupal bootstrap.

However, one of the most critical issues contributors found was that objects remained in the render tree, which was a significant problem. Drupal 8 then introduced Lazy Builder, which has no dependency on the current page request. The callback loads the data, and this approach was therefore well-suited for edge-side includes (ESI) and serving Ajax requests. As Drupal 8 progressed, rendering moved even later in order to improve performance outcomes for a variety of situations in the presentation layer.

Decoupling Drupal in the back end

Fabian argues that in subsequent versions of Drupal after Drupal 8, we can perform rendering even later to continue to improve front-end performance. We can also render asynchronously on the client, and this is an important feature available in decoupled Drupal architectures. In fact, I cover asynchronous rendering at length in a variety of technologies in my book Decoupled Drupal in Practice, and it is the subject of many sessions at Decoupled Days each year.

However, Fabian contends that the most important takeaway from his experimentation is that the Drupal community needs to decouple Drupal in the back end in order to realize these aforementioned benefits. This is because the render tree is too complex in his opinion, as Drupal now includes substantial surface area occupied by templates, preprocess functions, and even access control during the rendering process.

Virtual DOMs under the hood

In order to demonstrate how similar Drupal's render tree and virtual DOMs in JavaScript technologies like React and Vue are to one another, Fabian encourages us to consider a typical React component, for instance a favorite_content block which would be relatively trivial to implement in a monolithic Drupal architecture.

Consider Vue, for instance. A Vue component has a function in which the component is mounted. This means that in your single-page application, your component gets mounted—in other words, starts to be rendered in it—but before the mounting is complete it retrieves data from a store. In this case, we wish to display a list of our favorite books in a block. In the following example component, this.favorites would fetch the favorite books from an API such as GraphQL or JSON:API in Drupal.

    // Component mounting
    mounted() {
      this.favorites = fetch('/favorites');
    }

    // Template
    <ul>
      <li v-for={item in favorites}>{{ item.name }}</li>
    </ul>

In Drupal, however, things look rather different. In the Drupal approach, we have a foreach loop that allows us to build a render array:

    // Drupal
    $build = [];
    foreach ($favorites as $item) {
      $build[] = [
        '#theme' => 'favorite',
        '#favorite' => $item,
      ];
    }

In the end, we return an item list with another array nested within:

    // Drupal
    return [
      '#theme' => 'item_list',
      '#items' => $build,
    ];

But in this scenario, Fabian argues that Drupal mixes data and state with presentation. React and Vue, however, retrieve the data from the same data source.

Data decoupling is the key to success

Decoupling data from presentation in the same way as React and Vue is what Fabian considers to be the key to success for Drupal's future approaches to reactivity. For example, we can then take the same block that we considered above, a standard Drupal block, and provide it with a data function that returns from a Drupal store such as Entity Manager or even from other unexpected sources like Google Docs. Thereafter, during the rendering process, we can use the data in the list of favorites and export this same data to the drupalSettings object in the front end.

By doing this, we can achieve the lofty goal of having the front end and the back end share data and have the same data available no matter where in the stack one is located. To summarize this approach in briefer form, we now have a data generator that could be calling from an internal callback in core such as a subrequest, GraphQL, or REST API—in short, whatever data source we need, including from Drupal's render tree itself.

Thereafter, we export the data to drupalSettings and now can depend on the same data driving the front end as the back end. Though we still need one template for the front end and another template for the back end, we now have the same data and can render the same markup on both. Fortunately, according to Fabian, we can implement this precise pattern today without significant issue by simply decoupling our data from Drupal's rendering process. However, we still have a significant blocker: the fact that we still have two distinct templates and have to write the same template twice in order to use the same data on both the front end and the back end.

Conclusion

In this second installment of our multi-part blog series on how Drupal's front end can benefit from implementing components everywhere by separating data from presentation, we covered some of the fascinating ways in which Drupal's render tree is similar to the virtual DOMs available in popular JavaScript technologies like React and Vue. In addition, we examined some of the ways in which decoupling data from presentation during Drupal's render pipeline can yield unexpected dividends when it comes to both ease of use and efficiencies in code. Nonetheless, one crucial obstacle remains: the fact that we need to write two templates to rely on the same data.

In the next installment of this multi-part blog series about a component-based future for the Drupal front end, we turn to how Drupal can implement components like these in Twig and how adopting ideas from new approaches like JSX and its createElement() function can help us understand how to support universal components in Twig and Drupal.

Special thanks to Fabian Franz and Michael Meyers for their feedback during the writing process.

Part 1 | Part 2 | Part 3 | Part 4 | Part 5 |


For more on Web Components and how they're being used, see Web Components.


Photo by Evgeni Tcherkasski on Unsplash