Part 1 | Part 2 | Part 3 | Part 4 | Part 5 |
Among the most pitched debates currently in the Drupal community is the discussion over the future of Drupal's front-end and whether decoupled Drupal (the subject of a book by this author and a yearly conference in New York City) marks how front-end development in one of the world's most popular content management systems (CMS) will look for years to come. After all, decoupled Drupal architectures are increasingly commonplace across the Drupal development landscape, despite the acknowledged disadvantages that accompany the perceived advantages.
As it turns out, there may be a path forward thanks to a fascinating session presented by Fabian Franz (Senior Technical Architect and Performance Lead at Tag1 Consulting) at DrupalCon Amsterdam about this very topic. In Fabian's view, with the right approach to Web Components, a specification now seeing wide support across modern browsers, and inspiration taken from server-side rendering approaches seen in newly popular JavaScript frameworks, Drupal can guarantee its place in the front-end future that user experiences built with reactivity and offline-enabled features would require.
In this multi-part blog series summarizing Fabian's content, we inspect how a future for the Drupal front end looks when armed with Web Components support and how we can adopt the best ideas from JavaScript frameworks without reinventing the wheel in the process. In this fourth installment of the series, we take a look at what would be required on both Drupal's back end and presentation layer to enable the sort of functionality Fabian wishes to see.
Why a Custom Elements shim is necessary in PHP
Before we begin, if you have not yet perused the other blog posts in this multi-part series, I strongly suggest you read the first, second, and third installments, because they will provide critical background that is essential to understanding what comes next. Subject matter that should already be familiar to readers of this series include Drupal's render pipeline, how it resembles the virtual DOM seen in React and Vue, and how Twig's ecosystem is beginning to enable shared components across client and server.
Fabian argues convincingly during his presentation in Amsterdam that one of the most important components for his vision to succeed is a Custom Elements shim in PHP that would support foundational Web Components functionality. He proposes adopting the approach that Vue takes by parsing the document object model (DOM) and register components as custom elements using such a shim library. By then searching for the registered custom elements, we can then parse the slot components and render the same components (even using PHP's built-in preg_match()
to do this more simply).
As Fabian contends, we already leverage this precise approach in the BigPipe module, now in Drupal 8 core, which supports better front-end performance by deferring less cacheable portions of the page to be loaded later with simple DOM parsing. BigPipe seldom incurs a significant performance penalty by employing this approach, which could function effectively for Drupal's case.
Server-side rendering in Drupal
The next step in the process is to enable true server-side rendering in Drupal. Though it is true that Drupal provides server-side rendering out of the box for monolithic architectures, it does not provide the ability for the output rendered server-side to be made available as shared components to the client side. In short, Fabian recommends that such an implementation in Drupal would entail providing two representations of all rendered output.
Two representations for everything
Consider, for instance, a scenario in which we have a template and its resulting output, as in the following example:
// Template
<menu-item ref="sec-1" />
// Output
<li href="#sec-1">Section 1</li>
At first, Fabian admitted his own confusion in mixing the two worlds provided by component templates and rendered output, but in the Web Components ecosystem, as a matter of fact, templates are simply written as the inner content (or shadow DOM) of the custom element, in this case a menu item. The browser is unaware of the menu-item component itself and is only aware of what is contained within.
<menu-item ref="sec-1">
<li href="#sec-1">Section 1</li>
</menu-item>
Inspiration from Drupal's own theme system
By doing this, we accept and retain the original markup as the inner content of the custom element and define that inner content within the shadow DOM. And in the process, we can thereby also provide debug output just as Drupal core provides when debug mode is enabled for Drupal themes.
<!-- <menu-item ref="sec-1"> -->
<li href="#sec-1">Section 1</li>
<!-- </menu-item> -->
This is a graceful approach, because much like the debugging output of themes, we can expose the template in drupalSettings
, register every component automatically for each custom element, subsequently mount every Vue component for client-side rendering, and also retrieve the data that we need for each component from the drupalSettings
object. Indeed, in Vue, server-side rendering requires a special attribute or a special parameter, as you can see in the following example.
// Vue attribute approach
<div data-server-rendered="true">Content</div>
// Vue parameter approach
app.$mount("#app", true);
Reactivity
However, this solution is only part of the answer to the question of how Drupal can provide the sort of shared rendering that has made JavaScript technologies like React and Vue renowned among developers. In short, this is because we are still missing reactivity even with the server-side rendering that we just described.
Universal data stores
If we attempt to modify the data contained in drupalSettings.favorites
after server-side rendering is complete, nothing changes to reflect that the data has been updated. This is because when we mount this component in Vue, Vue creates a copy of that component rather than using the component we provide.
mounted() {
this.favorites = drupalSettings.favorites; // This creates a copy
}
data () {
return { favorites: {} }
}
Whenever Vue uses this component internally, it recognizes whenever you are changing the component itself rather than the drupalSettings
object, which is expected behavior, as Vue is entirely unaware that a drupalSettings
object exists in the first place. In the front-end development world, however, this is a solved problem thanks to existing approaches using Flux or Vuex alongside a universal data store.
Leveraging a universal data store is an effective approach, because we can simply refer to it in the Vue template and update using Ajax commands using a method such as store.setState()
. Though implementation details will differ depending on the chosen JavaScript technology, this provides the underpinnings for our data to be reactive.
// This is a solved problem in Flux, Vuex, etc.
{{ item in this.$store.favorites }}
// Update via Ajax command.
store.setState(
{ "favorites": newFavorites }
);
Adding real-time and offline support
Now that we have a universal data store available to us, we can update the data store from within the client-side framework and benefit from the user experience benefits that reactivity offers. We can also make that universal data store real-time and offline-first, by which we mean that the client has a copy of the data in question.
A universal data store represented by $store will typically also be connected to a real-time system such as Yjs (the subject of a multi-part blog series and Tag1 Team Talks), Firebase, or Gun.eco. This means that remote state changes, in which the store is informed about changes that are happening on Google Firebase, as an example, are synchronized automatically. In other words, this means that we no longer need to output the Ajax at all any longer; we can simply update the real-time store directly.
This achieves our dream when it comes to user experience, in that all of our devices, whether we are using mobile, tablet, or desktop, update automatically based on updates to the store. And we know from countless conversations with clients that this sort of user experience is table stakes for many customer projects. Though Drupal does not have an approach that satisfies this demand currently, we can leverage this vision to bridge the gap to bonafide real-time functionality. Fabian recommends that readers take a look at Firebase, a commercial real-time offering, as well as the prospect of using a free and open-source real-time editor with Yjs to use in conjunction with Drupal.
Conclusion
In this fourth installment of our multi-part blog series on a vision for Drupal that includes shared rendering across client and server and all of the benefits of reactivity and offline-enabled functionality in Drupal, we took a closer look at how to enable server-side rendering in Drupal and how we can implement real-time and offline support thanks to universal data stores and novel technologies like Yjs, which we previously covered in its own blog series.
Join me for the next installment of this multi-part blog series, when we will inspect some of the ways in which this visionary approach to Drupal's front-end future can yield outsized dividends not only for user experience but also for a front-end developer experience that, according to Fabian, could challenge the primacy of JavaScript frameworks and ensure the continued longevity of Drupal in the front end and back end alike.
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 Nienke Burgers on Unsplash