Over the last six months, the Drupal front-end community has had a lot of discussion about how to best use our theme system to work with design components. What would it mean to have a component-based theme system, and why does that matter?
With Drupal 8, we have several major improvements to our theme system.
- We can change markup much more easily with Twig templates.
- We don't need to worry about security issues in templates nearly as much due to Twig's autoescape.
- We can include CSS and JS and their associated dependencies only where necessary through the library system.
- We have fewer processing layers in PHP to worry about, and theme functions have been deprecated, making it easier to override markup through templates.
I could go on, but the main point is that there's a lot to be excited about for Drupal 8 theming, and many people are thrilled to work with Twig. So isn't that enough? Why are we already looking ahead to more changes to the theme system?
In recent years, many of the discussions in the front-end world beyond Drupal have been based around components. So what's a component?
If you look at a web page, you can break down most into a few major components. You'll probably see a header with a logo, some navigation, maybe a search box. There's probably a footer with some copyright information and maybe some links. If you're on a home page, you might see a list of news articles in the main content area. That header, footer, and article list are all components in their own right, and they also contain smaller components.
The article list serves as a great example. The list itself might have certain features like a heading and a pager, maybe an RSS feed icon. The pager is a component that can be further broken down into the individual page numbers, and probably a previous and next link. Finally each of the article teasers is a component in itself, with a headline, byline, a text snippet, maybe a photo, and probably a link to the article.
In short, a component serves as one definable chunk of a page. That chunk can contain discrete pieces of data as well as other smaller chunks that are components of their own.
Benefits of components
Breaking down an overall design into components helps improve the process of front-end development. In a team environment, assigning different front-end developers to work on one component at a time helps to break up a huge scope of work into manageable chunks. Depending on the size of a site, a CSS methodology like BEM (Block-Element-Modifier)—where each element of a component has one class with a certain set of styles applied to it—can help isolate one component from another. Keeping component code isolated makes it easy to change one component without worrying how it affects another component.
In some cases, components can also help in terms of reusability. One component like an article teaser might show up in multiple contexts—a home page and a landing page, for example—but look the same in each context. Reusing the same markup pattern for a component in multiple places can help reduce the amount of work necessary on a project.
In practice, I typically find that isolation is often the biggest benefit of components, rather than reusability. It's not uncommon for a designer to need to tweak the look of a component in a slightly different context. Sometimes a component modifier class is all that's necessary to handle that difference, sometimes the difference is significant enough to require a separate component altogether.
Elements of components
One common feature of components is that the component is not just a particular markup pattern, but the associated assets for a component: most commonly CSS, JS and images. Grouping all of the elements of a component in one place, typically one folder, helps for somebody looking at a component for the first time to know how it works in its entirety.
Ideally all the markup for a particular component can be contained in one template. In that way, you can look in one file to see everything that goes into a component. Spreading markup across multiple files for a component makes it much more difficult to understand how a component works.
The final element of a component is trickier to understand than assets or markup. Components need context, which can be thought of as the data necessary for a particular instance of a component. An article teaser for example needs data about a particular article. If an article teaser included topic tags, the component might also need context for the taxonomy terms used in those tags.
Context matters not just for the component itself, but also for any sub-components contained within the component. The article list, for example, needs to not only have the context necessary to create the list heading, but it also needs to pass on context so that the RSS feed icon has the information necessary to create a feed link. The pager needs know how many pages there are. The list has to pass on information to each of the individual article teasers.
You can think of context as how components communicate with each other. A parent component passes on context data to its child components. Conversely, a child component might need to let a parent component know, hey, here's the data I need in order to work properly.
Some front-end development frameworks further break down context into two separate types: state and props. React does this, for example. Props are data that will stay the same no matter how somebody interacts with a component. State, however, can change, based on interactions somewhere on the page. When that interaction happens, state data needs to be passed down from parent components to child components so that state-dependent data can trigger a re-render of a component, if necessary.
Components also often have logic associated with them that takes care of transforming raw context data into the exact format necessary for variables that appear within a component. In Drupal's theme system, the preprocess layer takes care of this context logic.
So, to sum up, components contain:
- Markup, ideally in one template
- Assets: CSS, JS, images, etc.
- Context (possibly both props and state data)
- Used for properties displayed within this component, or
- Passed on to child components
- Context logic
Where components are used
I mentioned earlier that components are a focus of the front-end development world these days. Let's be a little more explicit about that.
We already looked at how components can be useful when implementing a design. Breaking a monolithic design into individual design components that can be encapsulated and sometimes reused helps to improve workflow on a team project, ease maintainability and can potentially boost site performance.
If you are using http/2 (and you probably should be!), delivering the css/js for a particular component only when it is used on the page is much more feasible. Previously, best practice was to typically aggregate css and js into one big file. That would get cached by the browser and usable on future pages you visited. You might download a lot of unused css/js on that first request, but because there was a lot of overhead for each browser connection, that made sense. Within reasonable limits, that's not as much of a constraint in http/2. By all means, aggregate truly global css/js used on all pages. However, only downloading assets for components that are actually in use avoids pulling down unnecessary code for any given page.
That leads us to an emerging technology, web components, which allow the markup, css, js and other assets to be bundled up and scoped to work within a custom HTML element. This promises further benefits for encapsulation and isolation. Google has been developing some performance patterns for working with web components and service workers, a way to handle ongoing JS tasks like custom caching behaviors. This can allow both for super-fast page delivery as well as being able to use a site when it's offline and not connected to the Internet. Web components and service workers are likely the next big leap for performance.
Front-end developers also tend to work with components a lot through style guides like KSS and pattern libraries like Pattern Lab. These tools can help to demonstrate how components work on their own, and how they look in conjunction with each other. These tools can also help show work in progress to designers and stakeholders. This also allows front-end developers to implement the design of the site without being dependent on back-end work being completed before they do so. On larger projects that are used by multiple teams where people may come and go, style guides and pattern libraries also function as ongoing project documentation. This helps to prevent a new team member from re-implementing an existing component, because they didn't know it already existed. New team members can also see what components are available and how to make use of components.
Finally, the JS world makes heavy use of components. React, Angular and Ember all make use of their own flavor of components to build out modular functionality. ES2015, a newer standard for JS, has its own definition of modules that work much like components. React paired with Redux for state management can provide a particularly powerful way to handle interactions with components, propagating state changes from parent to child components for re-render, as mentioned earlier.
Components and site builders
The great thing about components is that it potentially lines up really well with how site builders think about assembling a site. Drupal excels at modeling content and structuring data, both for site builders and developers. However, that data often ends up as the starting point for building the appearance of a site.
Content type view modes are great, don't get me wrong. That's often the heart of where we start creating the look of a particular page. Blocks then get added conditionally in regions around that main piece of content, or sometimes around a View or a term page or an entity queue.
A similar thing happens on the code side of things, with a site's markup built up from render arrays, which are often structured around data for a site, rather than a particular element of a site's design.
There's been a lot of discussion lately about "outside-in", focusing on building up a page from the outside, rather than starting with the inside. It makes intuitive sense to start by looking at adding a header and footer to a site, then adding smaller components inside that header and footer, and so on and so forth.
Whether or not we develop new site building tools to help build up a site's appearance in a component-based way, it's important to remember that site builders are at the heart of Drupal's success. As a front-end developer, I'm as prone as anyone else to want complete control of my markup. I sometimes get annoyed by the site building tools that get mixed in with my markup and make it more difficult to implement a design.
Yet it's important to take a step back and recognize that Drupal's ability to let site builders assemble a page is what helps to keep Drupal accessible to a much wider audience. We could build a Drupal where site appearance was only controllable through code, and where front-end developers had complete control. That might work on certain enterprise sites that can afford to keep developers on staff to make any change to a site. More often, even on enterprise sites, there needs to be editorial control that necessitates UI changes to a site appearance.
Point being, that if we work to make Drupal more component-based, it's essential to ensure that work doesn't interfere with site builders' ability to affect the appearance of a site. Doing so would undermine one of Drupal's key strengths.
Challenges for component-based theming in Drupal
Many people are currently building Drupal sites in a way that focuses on components. So this work isn't impossible, but there are challenges where we could use improvements.
The basic structure for theming within Drupal, the render array, has a number of similarities with components. A render array has a template with markup associated with it. You can attach CSS and JS to a render array. Data can be passed from one render array down to a child render array.
However, it's challenging to get all of the markup for a component in one template with the way render arrays currently work.
View modes for content types are a typical stand-in for components. We looked at article teasers earlier, for example. That could easily be a teaser view mode for an article. A view mode typically consists of a number of fields and their formatters. You can create a template for a view mode by using a suggested node template name like node--article--teaser.html.twig. The markup for each of the fields for the view mode lives within separate templates, however. Customizing the markup for each of those fields can lead to a plethora of templates for just one component.
While in theory you can manually place a field's value within the node template in order to keep the markup for that field in the same template as the rest of the component's markup, that has a number of challenges.
If you extract the value of a field from a render array, that removes the cacheability metadata and attachments associated with the field. You can manually extract that information and associate it with the node render array, but doing so is no easy feat.
Another complication: preprocess functions only run on render arrays in preparation for rendering that render array within its associated template. If you manually place a field's value within a node, preprocess functions will never run on that field's render array, potentially resulting in missed processing. This causes problems for Quick Edit for example.
Finally, manually placing field values within a node template harms the association of that field with the UI on the view mode administration page.
These are just some of the challenges for getting all of the markup for a component into one template because of challenges with the drillability of Drupal's render arrays. There are a whole other set of challenges if you want to use Twig templates outside of Drupal for use in style guide and pattern libraries, or if you wanted to use Twig templates in conjunction with JS.
Drupal's PHP preprocess functions serve an important role in preparing variables for templates. Clearly those aren't going to run in a non-Drupal environment. We've discussed some solutions for that like replacing preprocess functions with Twig templates that focus solely on variable preparation. However after working with Drupal 8 for a while, I don't really think that's a viable solution. It's pretty common in preprocess to do complicated PHP like making using of the service container, and I don't see how that can be replicated in Twig.
Our theme system for Drupal has a lot of complicated moving pieces that are designed for extensibility and flexibility, so that both modules and themes can influence Drupal's markup and assets like CSS and JS. Replicating that outside of Drupal has numerous challenges. I'm not going to go into all of those here, but trust me, it would be tricky!
Key reasons for making Drupal more component-based
To start to pull all of this together, I want to sum up three of the most important reasons why we would want to make Drupal's theme system friendlier for components.
- Components are here to stay in the front-end world. Aligning our theme system with components will make it easier for front-end developers to theme a site in a component-based way. This will also prepare Drupal for a future where web components rule the roost.
- Keeping site building and front-end template in sync is key! We need to make sure site building tools don’t break while theming a site. Eventually we could also improve mental models for how site builders can change a site’s appearance.
- It would be great to be able to easily use Drupal’s markup outside of Drupal in JS, style guides and pattern libraries.
So far a lot of effort has gone into the first and third goals. Many people have been working on making it easier to theme a Drupal site in a component-based way, and that's great. There's been a lot of work done to get Drupal to play well with KSS and Pattern Lab, and that's also awesome.
However, I think the biggest challenges are in getting Drupal's theme system to work outside of Drupal. Conversely, that's also where there's been the greatest interest.
What I'm most concerned about is how we haven't focused as much on making sure component-based theme changes don't cause problems for site builders. I believe it's essential for Drupal to be friendly for both front-end developers and site builders. Improvements for front-end devs shouldn't wreck the site building experience. And conversely, making Drupal friendly for site builders can't make front-end development more challenging. If site building tools get too much in the way of theming a site, they often get tossed to the side and broken. We need tools that work well for both site builders and front-end developers to preserve all the things that make Drupal great.
Ways to move forward
The more I've thought about how to make Drupal more component-based, the more I've thought that one of the best models for doing so is Panels. Panels has concepts for multiple page types, each with separate layouts of sub-components. Those sub-components—panes—can contain a number of pieces of data that can be themed. When those sub-components are mini-panels, they can even contain their own sub components in a parent-child relationship that passes context data from the parent to the child.
Each pane or mini-panel can have a layout that communicates what slots are available for placing data or sub-components. Those layout slots can work like regions, where multiple items can be placed within them. However, you can also treat a layout slot as the place where you can place one particular piece of data, which is then manually placed in markup. In theory this could be set up in a way that communicates to a site builder, "Hey, you can put whatever you want in here and change the order in which it appears, or, this is a spot where you can put one particular piece of data we need, but you won't be able to move it around."
The blocks and layout initiative has been working on bringing layouts into Drupal core, starting with layouts on view mode pages like Display Suite has been able to do. Eventually if we're able to apply layouts to custom blocks, and we can place blocks within a block's layout slot, we'd be a long way to making Drupal more component-based.
I think we also need to find ways to make Drupal's render array system more drillable, so that it's easier to have one template for the entirety of a component's markup.
Overall, I'd like to see us focus on the first two of the three goals I outlined. Work to make Drupal's theme system itself more component-based, and make sure that we do so in a way that is friendly for site builders.
While I'd like to see Drupal play well with components in JS and style guides/pattern libraries, I think doing so is more complicated. We'll get more benefit from a smaller time investment focused on the first two goals. If we achieve that, then we'll have the ability to have one template per component, and that alone will make Drupal play well with other systems. If we need to do more at that point, we can do so, but let's focus on improving Drupal's own theme system first.
If you want to learn more about some of the challenges of making Drupal component-based, you can view the video or slides from my session at Twin Cities Drupal Camp, "Won't You Take Me To Chunk-y Town?". I also recently had a discussion with Mike Anello and Kelley Currey about components on the DrupalEasy podcast.
There are a lot of challenges with making Drupal more component-based, and at times I've gotten too sucked into focusing on what seemed like impossible challenges. I wanted to refocus our efforts on what concrete steps we can take to move things forward. By honing in on the most achievable goals, I think we can start moving things forward towards a brighter future for Drupal and component-based theming.