07 Sep 2024

feedDrupal.org aggregator

Mario Hernandez: Integrating Drupal with Storybook components

Hey you're back! 🙂 In the previous post we talked about how to build a custom Drupal theme using Storybook as the design system. We also built a simple component to demonstrate how Storybook, using custom extensions, can understand Twig. In this post, the focus will be on making Drupal aware of those components by connecting Drupal to Storybook.
If you are following along, we will continue where we left off to take advantage of all the prep work we did in the previous post. Topics we will cover in this post include:

  1. What is Drupal integration
  2. Installing and preparing Drupal for integration
  3. Building components in Storybook
  4. Building a basic front-end workflow
  5. Integrating Drupal with Storybook components

What is Drupal integration?

In the context of Drupal development using the component-driven methodology, Drupal integration means connecting Drupal presenter templates such as node.html.twig, block.html.twig, paragraph.html.twig, etc. to Storybook by mapping Drupal fields to component fields in Storybook. This in turn allows for your Drupal content to be rendered wrapped in the Storybook components.

The advantage of using a design system like Storybook is that you are in full control of the markup when building components, as a result your website is more semantic, accessible, and easier to maintain.

Building more components in Storybook

The title component we built in the previous post may not be enough to demonstrate some of the advanced techniques when integrating components. We will build a larger component to put these techniques in practice. The component we will build is called Card and it looks like this:

Palm trees in front of city buildings

When building components, I like to take inventory of the different parts that make up the components I'm building. The card image above shows three parts: An image, a title, and teaser text. Each of these parts translates into fields when I am defining the data structure for the component or building the entity in Drupal.

Building the Card component

---
modifier: ''
image: <img src="https://source.unsplash.com/cHRDevKFDBw/640x360" alt="Palm trees near city buildings" />
title:
  level: 2
  modifier: ''
  text: 'Tours & Experiences'
  url: 'https://mariohernandez.io'
teaser: 'Step inside for a tour. We offer a variety of tours and experiences to explore the building's architecture, take you backstage, and uncover the best food and drink. Tours are offered in different languages and for different levels of mobility.'
{{ attach_library('storybook/card') }}

<article class="card{{ modifier ? ' ' ~ modifier }}{{- attributes ? ' ' ~ attributes.class -}}" {{- attributes ? attributes|without(class) -}}>
  {% if image %}
    <div class="card__image">
      <figure>
        {{ image }}
      </figure>
    </div>
  {% endif %}

  <div class="card__content">
    {% if title %}
      {% include "@atoms/title/title.twig" with {
        'level': title.level,
        'modifier': title.modifier,
        'text': title.text,
        'url': title.url,
      } only %}
    {% endif %}

    {% if teaser %}
      <p class="card__teaser">{{ teaser }}</p>
    {% endif %}
  </div>
</article>

Code snippet for building card

import parse from 'html-react-parser';

import card from './card.twig';
import data from './card.yml';
import './card.css';

const component = {
  title: 'Molecules/Card',
};

export const Card = {
  render: (args) => parse(card(args)),
  args: { ...data },
};

export default component;

Let's go over a few things regarding the code above:

Before we preview the Card, some updates are needed

You may have noticed in card.twig we used the namespace @atoms when nesting the title component. This namespace does not exist, and we need to create it now. In addition, we need to move the title component into the 01-atoms directory:

/* eslint-disable */
import { defineConfig } from 'vite'
import yml from '@modyfi/vite-plugin-yaml';
import twig from 'vite-plugin-twig-drupal';
import { join } from 'node:path'
export default defineConfig({
  root: 'src',
  publicDir: 'public',
  build: {
    emptyOutDir: true,
    outDir: '../dist',
    rollupOptions: {
      input: {
        'reset': './src/css/reset.css',
        'styles': './src/css/styles.css',
        'card': './src/components/02-molecules/card/card.css',
      },
      output: {
        assetFileNames: 'css/[name].css',
      },
    },
    sourcemap: true,
  },
  plugins: [
    twig({
      namespaces: {
        atoms: join(__dirname, './src/components/01-atoms'),
        molecules: join(__dirname, './src/components/02-molecules'),
      },
    }),
    // Allows Storybook to read data from YAML files.
    yml(),
  ],
})

Let's go over some of the most noticeable updates inside vite.config.js:

Adding global styles

import '../dist/css/reset.css';
import '../dist/css/styles.css';

Previewing the Card in Storybook

Remember, you need NodeJS v20 or higher as well as NVM installed on your machine

nvm install
npm install
npm run build
npm run storybook

A quick note about the commands above:

After Storybook launches, you should see two story categories in Storybook's sidebar, Atoms and Molecules. The title component should be under Atoms and the Card under Molecules. See below:

Palm trees near city buildings

Installing Drupal and setting up the Storybook theme

We have completed all the prep work in Storybook and our attention now will be all in Drupal. In the previous post all the work we did was in a standalone project which did not require Drupal to run. In this post, we need a Drupal site to be able to do the integration with Storybook. If you are following along and already have a Drupal 10 site ready, you can skip the first step below.

  1. Build a basic Drupal 10 website (I recommend using DDEV).
  2. Add the storybook theme to your website. If you completed the excercise in the previous post, you can copy the theme you built into your site's /themes/custom/ directory, Otherwise, you can clone the previous post repo into the same location so it becomes your theme. After this your theme's path should be themes/custom/storybook.
  3. No need to enable the theme just yet, we'll come back to the theme shortly.
  4. Finally, create a new Article post that includes a title, body content and an image. We'll use this article later in the process.

Creating Drupal namespaces and adding Libraries

Earlier we created namespaces for Storybook, now we will do the same but this time for Drupal. It is best if the namesapces' names between Storybook and Drupal match for consistency. In addition, we will create Drupal libraries to allow Drupal to use the CSS we've written.

components:
  namespaces:
    atoms: src/components/01-atoms
    molecules: src/components/02-molecules
global:
  version: VERSION
  css:
    base:
      dist/css/reset.css: {}
      dist/css/styles.css: {}

card:
  css:
    component:
      dist/css/card.css: {}

Turn Twig debugging on

All the pieces are in place to Integrate the Card component so Drupal can use it to render article nodes when viewed in teaser view mode.

Code inspector showing Drupal debugging

In the example above, we see a list of templates that start with node...*. These are called template suggestions and are the names Drupal is suggesting we can assign our custom templates. The higher the template appears on the list, the more specific it is to the piece of content being rendered. For example, changes made to node.html.twig would affect ALL nodes throughout the site, whereas changes made to node--1--teaser.html.twig will only affect the first node created on the site but only when it's viewed in teaser view mode.

Notice I marked the template name Drupal is using to render the Article node. We know this is the template because it has an X before the template name.

In addition, I also marked the template path. As you can see the current template is located in core/themes/olivero/templates/content/node--teaser.html.twig.

And finally, I marked examples of attributes Drupal is injecting in the markup. These attributes may not always be useful but it is a good practice to ensure they are available even when we are writing custom markup for our components.

Create a template suggestion

By looking at the path of the template in the code inspector, we can see that the original template being used is located inside the Olivero core theme. The debugging screenshot above shows a pretty extensive list of templates suggestions, and based on our requirements, copying the file node--teaser.html.twig makes sense since we are going to be working with a node in teaser view mode.

As you can see, by renaming the template node--article--teaser (one of the names listed as a suggestion), we are indicating that any changes we make to this template will only affect nodes of type Article which are displayed in Teaser view mode. So whenever an Article node is displayed, if it is in teaser view mode, it will use the Card component to render it.

The template has a lot of information that may or may not be needed when integrating it with Storybook. If you recall, the Card component we built was made up of three parts: an image, a title, and teaser text. Each of those are Drupal fields and these are the only fields we care about when integrating. Whenever when I copy a template from Drupal core or a module into my theme, I like to keep the comments on the template untouched. This is helpful in case I need to reference any variables or elements of the template.

The actual integration ...Finally

  1. Delete everything from the newly copied template except the comments and the classes array variable
  2. At the bottom of what is left in the template add the following code snippet:
{% set render_content = content|render %}

{% set article_title = {
    'level': 2,
    'modifier': 'card__title',
    'text': label,
    'url': url,
  }
%}

{% include '@molecules/card/card.twig' with {
  'attributes': attributes.addClass(classes),
  'image': content.field_image,
  'title': article_title,
  'teaser': content.body,
} only %}

Enable the Storybook theme

Before we forget, let's enable the Storybook theme an also make it your default theme, otherwise all the work we are doing will not be visible since we are currently using Olivero as the default theme. Clear caches after this is done.

Previewing the Article node as a Card

Integration is done and we switched our default theme to Storybook. After clearing caches if you reload the homepage you should be able to see the Article node you wrote but this time displayed as a card. See below:
Example of twig debugging

Drupal template suggestions in code inspector

If your card's image size or aspect ratio does not look as the one in Storybook, this is probably due to the image style being used in the Article Teaser view mode. You can address this by:

In closing

This is only a small example of how to build a simple component in Storybook using Twig and then integrate it with Drupal, so content is rendered in a more semantic and accessible manner. There are many more advantages of implementing a system like this. I hope this was helpful and see the potential of a component-driven environment using Storybook. Thanks for visiting.

Download the code

For a full copy of the code base which includes the work in this and the previous post, clone or download the repo and switch to the card branch. The main branch only includes the previous post code.

Download the code

07 Sep 2024 8:14pm GMT

06 Sep 2024

feedDrupal.org aggregator

Wim Leers: XB week 14: early christmas tree

How does cta1href sound to you? Gibberish, right? :D
Jesse "jessebaker" Baker pointed out that Experience Builder (XB) in its current state was subjecting its users to such nonsense! Fortunately, thanks to every Single Directory Component (SDC) specifying a title for each prop, we were able to automatically generate the much more readable CTA 1 link - thanks to foundations Ben "bnjmnm" Mullins did in #3461422 from 3 weeks prior.

Utkarsh "utkarsh_33" and Omkar "omkar-pd" Deshpande eliminated an extraneous "preview" request from the client, hence improving performance (as well as sanity).

Ben & Jesse made XB's Cypress end-to-end tests leap massively ahead (especially compared to Drupal core's use of Nightwatch), by introducing cypress-terminal-report. The resulting test failure output on GitLab CI makes it far easier to figure out where something is going wrong: a big productivity boost!

Two weeks ago I alluded to it, and now it finally happened: after months of getting basic infrastructure off the ground, we now finally were able to Kyle "ctrladel" Einecker's set of representative SDCs that Lauri approved, Ivan "finnsky" Berdinsky and I reviewed, and Ted "tedbow" Bowman pushed across the finish line.
(Not everything Kyle proposed landed, because XB and the SDC subsystem do not yet have all the capabilities needed for some of the SDCs he wrote - see the follow-up if you're interested.)

The very first component tree rendered in Experience Builder: both slots of this 'two column' SDC contain other SDCs!
The very first component tree rendered in Experience Builder: both slots of this 'two column' SDC contain other SDCs!
Issue #3446722, image by me.

Some of the people working full-time on XB are doing so using DDEV. And running end-to-end tests that use WebDriver with both the test runner and the system under test living in a Docker container turns out to be quite challenging! Besides us, we know that many (most?) in the community use a DDEV-based development environment, and we'd love to welcome as many contributors as possible. Not being able to run the most important tests of all then is of course quite a problem.
That's why Travis "traviscarden" Carden had been diligently (he actually joined a few weeks ago!) working on making that painless. The result: the ddev-drupal-xb-dev DDEV add-on - once installed, running (and seeing!) the XB end-to-end tests requires only ddev xb-cypress-open :)

Feliksas "f.mazeikis" Mazeikis is back full-time on XB, and he's started working on the super important #3463999: Auto-create/update Component config entities for all discovered SDCs that meet XB's minimum criteria. It's a critical piece in making Lauri's product vision come to life: it will ensure that any SDC that we're confident will work in XB becomes available automatically.1 While working on that, he discovered that there was a pretty big oversight in the StorablePropShape work I landed two weeks ago: in some places Drupal core does not distinguish between "instance settings" and "storage settings" at all (and nor did the code I landed then), but in some places it actually does. Felix was running into that now, thanks to config validation, and so he fixed that.

Many of the things that happened this week were on the "enablement" side of things. Nonetheless, the XB UI also made progress:

  1. Harumi "hooroomoo" Jang and Jesse landed the updated "insert" UX and hierarchy view, based on the updated design.
  2. But, most importantly, nicely rounding out this post by combining "enablement" and improved UX: Bálint "balintbrews" Kléri implemented error boundaries. In principle, you should never see these, but we all occasionally have internet connection issues. At those times, Bálint's "Try again" addition works beautifully :)
    (And as a bonus it accelerates debugging failing server responses!)
Error boundaries with a friendly 'Try again'.
Error boundaries with a friendly 'Try again'.
Issue #3461431, image by Bálint.

Thanks to Travis for reviewing this!

Week 14 was August 12-18, 2024.


  1. At this early stage, it'll happen indiscriminately. Later, we'll ensure that on production environments no new SDCs will be made available to the Content Creator without explicit approval by the Site Builder. See the first steps towards auditability of the available XB components if you're interested in this aspect. ↩︎

06 Sep 2024 4:20pm GMT

The Drop Times: Vincenzo Gambino: A Drupal Architect from Palermo

In an exclusive interview with The DropTimes, Vincenzo Gambino, a Drupal Architect and Senior React Developer, shares his journey from Palermo to London, exploring how Drupal shaped his career. From working on high-traffic projects for Cambridge University to co-authoring "Jumpstart Jamstack Development", Vincenzo discusses Drupal's evolution, its role in headless architectures, and the future of open-source technologies. With over 12 years of experience, he offers insights into Drupal's flexibility, security, and performance, as well as its adaptability to modern web development trends.

06 Sep 2024 3:31pm GMT