Chapter 17. Red Hat Documentation

As I’ve looked back on the year-long process of developing the new design system for the Red Hat website, I realized that the entire project started with a need for documentation, and grew from there. The original request from management was to develop a style guide of our website’s most commonly used bands, and make them available for other Red Hat web properties to use. It all started with a handful of Sass partials and a Hologram style guide, but it grew into so much more.

Stage 1: A Static Style Guide

As I wrote in Chapter 15, Hologram is a documentation tool that looks for specially marked comment blocks in your code, and converts the contents of those blocks into a style guide. Having heard about Hologram from Nicole Sullivan, I felt that it would be the perfect tool for us to document the layouts and components we were building.

Working inside of Hologram was quick and smooth. Each component or layout had its own Sass partial, so we placed our documentation block at the top of each file. The documentation included everything from our intentions for the component, to its capabilities and limitations, to a sample of the HTML markup. The markup allowed us to get a quick preview of the component without having to fire up the CMS, which made prototyping, cross-browser testing, and visual regression testing a lot easier.

Along with our library of components and layouts, we spent a great deal of time developing onboarding documentation, which explained not only how to set up this style guide, but how to use it, create new content for it, and push out new releases of it as well. Hologram was a great tool for this, as we could simply create a documentation folder, fill it up with Markdown files, and have Hologram pull them into our style guide based on how each page was categorized.

Everything was going just fine until we remembered that the original goal was to have a style guide that demonstrated how the code could be combined to create various commonly used patterns. This wasn’t too difficult, as we just created a Patterns folder and started to create Markdown files with links to the components and layouts used in each pattern, including descriptions of which grid, alignment, and theme values were used. Once that was created, we went about copying and pasting the markup into the Markdown files, showing what a logo wall looked like, what HTML made up a featured video band, or the code differences in a featured event band when there were two events in the band versus three.

The problem was that each time we copy and paste markup from one place to another, the chance of that markup changing increases by a factor of a billion. It didn’t take us very long to realize how unsustainable this approach was going to be, so we quickly looked for a solution where we could have a single canonical source of markup, and reuse that snippet anywhere we wanted to. Unfortunately, Hologram had no built-in support for any templating languages. I still have an open issue on the project’s GitHub page from October 2014 asking about “processing html_example as ERB, to allow for reuse and template inheritance.”

The solution we came up with was to use the Twig templating engine to process all of our documentation pages before passing them into Hologram. I was surprised to find that Twig had no issue processing a Markdown file, so here is a quick example of what the documentation would look like:

<!-- cta.docs.md -->

---
hologram: true
title: CTA Component
category: Component - CTA
---

- A "Call-to-Action" component contains one or more CTA buttons.

## Primary Button

```html_example
 {% include "cta.twig" with {'type': 'primary'} %}
```

## Secondary Button

```html_example
 {% include "cta.twig" with {'type': 'secondary} %}
```

```html_example
<!-- cta.twig -->
<div class="rh-cta" >
 <a class="rh-cta-link" data-rh-cta-type="{{type}}" href="#">
  CTA Button
</a>
</div>

```

As you can see with this single page of documentation, we were able to use Twig’s include function to print the same CTA markup in two different places, each with its own set of data. The resulting file was then passed on to Hologram to be processed normally:

<!-- cta.docs.md -->

---
hologram: true
title: CTA Component
category: Component - CTA
---

- A "Call-to-Action" component contains one or more CTA buttons.

## Primary Button

```html_example
<div class="rh-cta" >
 <a class="rh-cta-link" data-rh-cta-type="primary" href="#">
  CTA Button
</a>
</div>
```

## Secondary Button
```html_example
<div class="rh-cta" >
 <a class="rh-cta-link" data-rh-cta-type="secondary" href="#">
  CTA Button
</a>
</div>

```

If we needed to make a change to the CTA markup, we could update a single Twig file, and our entire system would start using that new markup.

Stage 2: We Just Reinvented Pattern Lab

This was all well and good for single components, but eventually we started to document some of our layouts, like our card layout. A card is a simple layout that allows you to place multiple components into a padded box, and apply a theme and background. But if we were using Twig to import the card markup, how could we specify which components went into the card? We could just create a bunch of card templates, each one demonstrating a different combination of components, but there we are again copying markup!

The solution came in the fact that Twig allows us to pass in a variable to the include statement:

{% set template = 'cta.twig' %}

{% include template  %}

This was exactly the power that we needed to create various examples of cards, each with different settings and content:

<!-- card.docs.md -->

---
hologram: true
title: Card Layout
category: Layout - Card
---

{%
 set data = {
   "theme": “dark”,
   "components": [
     {
       "template": "image_embed.twig",
       "src": "my-image.jpg"
     },
     {
       "template": "cta.twig",
       "type": "primary"
     }
   ]
 }
%}

{% include card.twig with data %}

<!-- end card.docs.md -->

<!-- card.twig -->

<div class="rh-card data-rh-theme="{{theme}}">
 {% for component in components %}
   {% include component.template with component only %}
 {% endfor %}
</div>

<!-- end card.twig -->

The preceding code does quite a bit of complex work, so let’s break it down, as this was the basis for our entire build system as we grew from cards, to groups, to bands and beyond:

{% set

 data = {
   "theme": "dark",
   "components": [
   ...
   ]
 }

%}

Our first task is to create a data set that will describe what we want to build. You can see we write some code that includes two properties: theme and components. While theme is just a string, our components property is an array holding two more objects, each with a “template” property and other properties related to that template. If you think this looks a whole lot like JSON, you are most certainly correct. Though we started by using Twig data objects,  we eventually converted to a JSON data workflow, but the logic remains the same.

The next thing we need to do is include our card template using the data that we just set:

{% include card.twig with data %}

Lastly, let’s take a look at the card’s Twig file, and see how it handles this data we send over:

<div class="rh-card data-rh-theme=”{{theme}}”>

 {% for component in components %}
   {% include component.template with component only %}
 {% endfor %}

</div>

First thing we hit is the {{theme}} variable. That one’s easy. We grab dark from the theme property, and that gets printed inside of our data attribute, just like we saw in the CTA Twig file.

The second bit of Twig we hit is slightly more complex. {% for component in components %} means for each item in our components array (which we call component), do the following: import the template found in each array’s template property, and pass the entire array into that template as the new data context. So first we import the image_embed.twig file and pass in an image source, then we import cta.twig and specify that we want the type to be primary:

 {
   "template": "image_embed.twig",
   "src": "my-image.jpg"
 },
 {
   "template": "cta.twig",
   "type": "primary"
 }

If this all sounds familiar to you, it’s because you might have just read in the previous chapter how Pattern Lab is able to pass in a new data context to each atom or molecule that a layout imports. So if you are wondering...yes, we ended up creating our very own Pattern Lab.

I had many coworkers chuckle when they made the same realization, and they always asked why I didn’t just use Pattern Lab in the first place. My original answer was simply that we came to this crossroads pretty organically. We started with one thing, and as our needs changed, we added functionality until we ended up with something that quite resembled Pattern Lab. But now I feel that what we built was much better suited for what we wanted to do.

Not only does Hologram provide us a place to store our Pattern Library, but we also were able to benefit from its ability to easily create a style guide and pages of pure documentation for things like installation, best practices, and deployment instructions. The other thing vastly different between our system and Pattern Lab is that in our system, all of our imports are determined by the data, and not hardcoded into the layout or organism like Pattern Lab.

This means that we were able to reuse our layouts for any combination of content we wanted to place inside of them, rather than having to rewrite the layout for each different combination of content we wanted to display. The other important thing to remember is that for us, once the rendering process begins, we have no control over the markup other than what is in this data. We don’t have the option to tweak the source order of a layout for this one case, or add an extra container div to a component in another. These limitations, or restraints, are what ended up making our system as robust and powerful as it turned out to be. But before we got to that point, we needed to shed a bit of weight.

Stage 3: Splitting the Pattern Library from the Style Guide

One of my growing frustrations as our Pattern Library and style guide continued to grow was that in using a single Git repo we were constantly pushing up minor tweaks along with major overhauls to the style guide that had absolutely no effect on the Pattern Library itself. We were now well into a release schedule each sprint, and were pushing out prerelease Git tags every time we had a change. This caused quite a bit of confusion, as we had to determine if the new code actually affected the templates, styles, or JavaScript before bothering our backend developers with yet another prerelease. It also just felt dirty making tons of changes to a Git repo that would eventually end up in production just so that we could reorganize a few pages in the style guide.

The solution to this problem ended up providing way more benefits than we had initially expected. We decided to split our Pattern Library and style guide into separate repositories.

The instant benefit of this decision was that we could push changes to our style guide all day long without worrying about the effect of our changes on the production server. The Pattern Library now received far fewer pull requests, and we could better focus on how those changes might affect both production and our style guide, which were both importing the library via Bower.

The second benefit of this split was that now our production environment and our style guide were equal consumers of the Pattern Library. When we created a new band design in the style guide, the only tool we had for manipulating the markup was our JSON data array. We had no way of altering the markup or adding custom CSS for some special, one-off use case. This meant that anything we built in the style guide, we were confident could reproduce in production without modification to the templates.

At this point, our production environment was ingesting our CSS and JavaScript, but it had its own template-rendering engine that relied on PHP templates to do the heavy lifting. We had been able to faithfully replicate each layout and component in PHP, but it was a very manual process, and any change we wanted to make to our Pattern Library templates would have to be replicated in production.

This isn’t an uncommon problem. Most Pattern Lab users, who are able to create various designs, patterns, and pages using the built-in Mustache templates, still pass over the compiled markup for the backend developer to implement. This doesn’t mean that they like it! I’m sure they wish they could update an atom or molecule in their library and have their CMS pick up those changes and instantly start rendering pages with those changes.

Fortunately for us, we were already using Twig, which was built to compile in PHP, and we had some developers that were willing to create the missing link between our Pattern Library and the CMS rendering engine.

Stage 4: Creating a Unified Rendering Engine

One of the greatest struggles facing Drupal frontend developers is that we cannot replicate the incredibly complex rendering process Drupal uses to create markup without using Drupal itself. We are forced to either create static markup and pass it off for implementation, or we have to work with the markup rendered by the CMS, and create content in the CMS before we can even start writing styles.

But once we split the Pattern Library away from the style guide, we realized that we had created a brand-new Twig-powered rendering engine that could be used by both Drupal and our style guide. Given a set of JSON data passed into a Twig render function, our markup-as-a-service, or design API, would return the same markup every time. Our Pattern Library could now be installed into any system that could compile Twig. If the JSON data was collected and formatted correctly, the rendered HTML would be identical regardless of whether it was passed in from a static file, or created inside of a complex CMS.

The challenge, of course, was knowing what correctly formatted data looked like. What properties did each template accept? Were they strings, numbers, or Booleans, and which ones were required? Did any of them have restrictions on what values they accepted? How could we be sure that the data we were sending to Twig was actually valid, not to mention whether it would produce the desired HTML? Attempting to answer all of these questions led us to one of our greatest discoveries: JSON schemas.

JSON Schemas

Simply put, JSON schemas describe a set of data. To be more specific, in our Pattern Library, JSON schemas describe the data that a template file needs to render properly. It lists each template variable, which ones are required, the variable data type, and any restrictions that the value has.

If our card has a theme variable, our schema informs us that the theme variable is required and must be a string value of dark or light. If we try to omit the theme, or to pass a value of gray to the template, the card schema will return a validation error.

We can also use JSON schemas to indicate that our card can contain a number of different components, and we can validate a complex JSON array that we might use to render a complex band, all in a single pass. The following code might contain a valid card, but the CTA component inside of it is not valid because tertiary isn’t an allowed type value:

{
   "template": "band.twig",
   "theme": "dark",
   "components": [
     {
       "template": "cta.twig",
       "type": "tertiary"
     }
   ]
 }

Because the JSON schema allows us to use references to describe the CTA you can put into a card, group, or band, we never have to duplicate our schema files, and just like Twig templates, we keep a single canonical source. Therefore, creating JSON schemas for everything from the smallest button to the largest layout, we now have an incredibly complex and comprehensive schema that can validate each and every band we want to render.

Armed with these schemas, our developers could now see at a glance the data needs for any template file, and could validate the data they were gathering before sending it over to our shared Twig rendering engine. This was a huge improvement from our older system in that they were no longer required to fiddle with the output markup until it matched what we had in the style guide. They could be confident that if their data validated, the rendering engine would return to the correct HTML. Unfortunately, there was still a problem with this approach.

Our developers were still saddled with creating the dozens of input fields required to capture the titles, images, and text paragraphs needed for each page. Once all of those fields were created, they would then laboriously map each one to the JSON array before rendering. They also had no way to reuse the fields used to capture data for each template file. Every time they gathered the text, href, and type of our CTA button, they had to create a brand-new set of fields in the CMS, which also required separate database entries.

Fortunately for us, JSON schemas had a few more tricks up their sleeves.

Stage 5: Automating the Creation of New Patterns

I am by no means an experienced backend PHP developer, but I hate repetitive tasks as much as the next person. I could really feel the pain of our developers who were configuring dozens and dozens of input fields just so that they would match the schemas I had already written. I also couldn’t help but wonder how powerful our design system could be if the work that we did in schemas, templates, and styles could be automatically and programmatically imported into our CMS without a single hour of backend dev intervention.

What if there was no more waiting until the next three-week sprint to find a dev resource to implement our new pattern? What if we could eliminate “lost in translation” issues where the developer had to make an assumption about how the pattern worked, and the feature had to be pushed back a sprint to deal with a number of defects?

Jeremy Dorn’s JSON-editor is a tool that takes a JSON schema and creates form out of the schema properties. It then returns the JSON data from the form as you update the form fields. We were using this tool in our Pattern Library to interact with the schema, templates, and styles, and get instant visual feedback of how changing a property affected the rendered results. We’d had great success with this editor and its ability to transform a schema into a set of editable fields, so we were pretty confident that the same approach could be applied to Drupal, or WordPress, or any other platform that wanted to use our design pattern.

Our faith in the power of the JSON schema was rewarded when our developers jumped on the opportunity to create an automated JSON schema importing process inside of Drupal. Within a few weeks, we had a system that automatically converted our components and layouts into Drupal entities. Now they were able to create a single entity for our CTA component, and reference that entity and its database fields each time that our little call-to-action button was required.

Creating Patterns

We also wanted to take advantage of the various Red Hat–branded patterns we created, which were specific recipes for putting layouts and components together. A pattern made assumptions about the grid layout, or the components used, or the values of some of the properties. This way if we needed to create a logo wall, which always contained image embeds, with five per row, we could simply use a pattern instead of setting all of those manually.

It turns out that patterns could also be written as JSON schemas. Each pattern band looked very similar to our canonical band schema, and described data that would always validate if passed through that schema. But it was more restrained than the band schema, and didn’t always offer all the options for background, layout, or component selection.

So through the power of a simple set of JSON schemas, we were now in a position where we could not only prototype the design of a new component, but we could also define the actual template file that would be used to render that design, as well as the data model that would need to feed that template, and the UI that would allow us to collect all of that data!

Our decision to embrace JSON schemas, with all of the power they brought and restraints that they required, transformed our design system into what it is today. It started as nothing more than a collection of design elements and the markup that we hoped would be used in implementation, and it resulted in a streamlined and efficient prototyping-to-rendering pipeline that removed a great deal of repetitive work. It also allowed the work we did on the frontend to completely define the entire process of collecting, storing, rendering, and styling content inside of our CMS.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.235.188