Planet Drupal
Mario Hernandez: How to build a card component
One of the most popular components on any website I've worked on is the "Card" component. Depending on the website, the card component is used to group various pieces of content into a container which can be used throughout your website.
Read the full article on how to build a card comonent.
Mario Hernandez: Flexible Headings with Twig
Proper use of headings h1-h6 in your project presents many advantages incuding semantic markup, better SEO ranking and better accesibility.
Updated April 3, 2020
Building websites using the component based approach presents all kinds of advantages over the traditional page building approach. Today I’m going to show how to create what would normally be an Atom if we use the atomic design approach for building components. We are going to take this simple component to a whole new level by providing a way to dynamically controlling how it is rendered.
The heading componentHeadings are normally used for page or section titles and are a big part of making your website SEO friendly. As simple as this may sound, headings need to be carefully planned. A typical heading would look like this:
<h1>This is a Heading 1</h1>The idea of components is that they are reusable, but how can we possibly turn what already looks like a bare bones component into one that provides options and flexibility? What if we wanted to use a h2 or h3? or what if the title field is a link to another page? Then the heading component would probably not work because we have no way of changing the heading level from h1 to any other level or add a URL. Let's improve the heading component so we make it more dynamic.
Enter Twig and JSONTwig offers many advantages over plain HTML and today we will use some logic to transform the static heading component into a more dynamic one.
Let’s start by creating a simple JSON object which we will use as data for Twig to consume. We will build some logic around this data to make the heading component more dynamic. This is typically how I build components on projects I work on.
- In your project, typically within the components/patterns directory create a new folder called heading
- Inside the heading folder create a new file called heading.json
- Inside the new file paste the code snippet below
So we created a simple JSON object with 4 keys: heading_level, modifier, title, and url.
- The heading_level is something we can use to change the headings from say, h1 to h2 or h3 if we need to.
- The modifier key allows us to pass a modifier CSS class when we make use of this component. The modifier class will make it possible for us to style the heading differently than other headings, if needed.
- The title key is the title's string of text that will become the title of a page or a component.
- ... and finally, the url key, if present, will allow us to wrap the title in an <a> tag, to make it a link.
- Inside the heading folder create a new file called heading.twig
- Inside the new file paste the code snippet below
Wow! What's all this? 😮
Let's break things down to explain what's happening here since the twig code has changed significantly:
- First we make use of heading.heading_level to complete the number part of the heading. If a value is not provided for heading_level in the JSON file, we are setting a default of 2. This will ensue that by default we will have a <h2> as the title, much better than <h1> as we saw before. This value can be changed every time the heading isused. The same approach is taken to close the heading tag at the last line of code.
- Also, in addition to adding a class of heading, we check whether there is a value for the modifier key in JSON. If there is, we pass it to the heading as a CSS class. If no value is provided nothing will be added.
- In the next line line, we check whether a URL was provided in the JSON file, and if so, we wrap the Flexible Headings with Twig variable in a <a> tag to turn the title into a link. The href value for the link is ``. If no URL is provided in the JSON file, we simply print the value of Flexible Headings with Twig as plain text.
Well, our heading component is ready but unfortunately the component on its own does not do any good. The best way to take advantage of our super smart component is to start using it within other components.
Putting the heading component to useAs previously indicated, the idea of components is so they can be reusable which eliminates code duplication. Now that we have the heading component ready, we can reuse it in other templates by taking advantage of twig’s include statements. That will look like this:
<article class="card"> {% include '@components/heading/heading.twig' with { "heading": heading } only %} </article>The example above shows how we can reuse the heading component in the card component by using a Twig’s include statement.
NOTE: For this to work, the same data structure for the heading needs to exist in the card’s JSON file. Or, you could also alter the heading's values in twig, like this:
<article class="card"> {% include '@components/heading/heading.twig' with { "heading": { "heading_level": 3, "modifier": 'card__title', "title": "This is a super flexible and smart heading", "url": "https://mariohernandez.io" } } only %} </article>You noticed the part @components? this is only an example of a namespace. If you are not familiar with the component libraries Drupal module, it allows you to create namespaces for your theme which you can use to nest or include components as we see above.
End resultThe heading component we built above would look like this when it is rendered:
<h3 class="heading card__title"> <a href="https://mariohernandez.io" class="heading__link"> This is a super flexible and smart heading </a> </h3> In closingThe main goal of this post is to bring light on how important it is to build components that are not restricted and can be used throughout the site in a way that does not feel like you are repeating yourself.
Additional Resources:Mario Hernandez: Getting started with Gatsby
As many developers, when I hear the words "static website" I immediate think of creating flat HTML pages and editing them by hand. Times have changed. As you will see, Static Site Generators (SSG), offer some of the most advanced features and make use of latest technologies available on the web.
Static Site Generators are nothing new. If you search for SSG you will find many. One of the most popular ones is Jekyll, which I have personally worked with and it's a really good one. However, this post focusing on Gatsby. Probably one of the hottest system for creating static sites.
What is Gatsby?Gatsby's primarily objective is to build static sites, but as you will learn, that's just the tip of the iceberg.
Gatsby is a blazing-fast static site generator for React.
How does Gatsby work?While other SSGs use templating languages like Mustache, Handlebars, among others, Gatsby uses React. This not only allows for building modern component-driven websites, it also provides an incredible fast page rendering. Like mind-blowing fast.
Extending GatsbyOne of the most powerful features of Gatsby is its growing number of "Plugins". Plugins are the building blocks of Gatsby. They allow you to implement new features and functionality by running a couple of commands and making some configuration changes. Anything from adding Sass to your React project, creating a blog, configuring Google Anaylitics and many many more.
Plugins are contributed code kindly provided by the generous Open Source community which totally rocks. Anyone is able to write plugins and make them available to the world to consume and use.
Check out their Plugins page for a full list of ways you can take your static site to the next level.
Editorial ProcessSo we are building static sites and you may be wondering How do I create content for my site? There are several ways in which you can create a content editign workflow for your site. Probably the easiest way is to use static Markdown files. Markdown is a lightweight markup language with plain text formatting syntax. It is designed so that it can be converted to HTML and many other formats. Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor. This blog is using markdown. Since I am the only creating content I don't need a fancy administrative interface to crate content.
Markdown is only one of the ways you can create content for your static sites. Others include more advanced methods such as plugging in Gatsby with your Content Management System (CMS) of choice. This includes Wordpress, Drupal, Netlify, ContentaCMS, Contenful, and others. This means if you currently use any of those CMSs, you can continue to use them to retain a familiar workflow while moving your front-end workflow to a simpler and easier to manage process. This method is usually referred to as decoupled or headless, as your back-end is independent of your front-end.
Quering DataAs previously mentioned, Gatsby with the power of React create the perfect system for building robust, flexible and super fast static sites. However, there is a third component that takes that power to a whole new level, and that is GraphQL.
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
Deploying GatsbyHosting for a Gatsby site can be done anywhere where React apps can run. Nowadays that's pretty much anywhere. However, before investing in an expensive and highly complicated hosting environment, take a look at some of the simpler and less expensive options on this page.
You will see that for a basic website, you can use several of the free options such as github pages, netlify and others which already include advanced continuous integration workflows. For more advanced sites where a CMS may be involved, you can also find options for deployment that will simplify your DevOps process.
My own blog is running out of a github repo that automatically get deployed when I push new updates. This is happening via Netlify which to me is probably the easiest way I have ever deployed a website.
In closingDon't worry if you are a little skeptical about static site generators. I was too. However, I gave Gatsby a try and I see myself building more gatsby sites in the future. Before Gatsby I worked with Jekyll which is also a great static site generator, but what sets Gatsby apart is its seamless integration with React and GraphQL. The combination of those 3 provides endless posibilities in your web building process. Check it out.
Mario Hernandez: Running a training workshop
Update 1-10-19
I wrote an extended version of this post at Mediacurrent's blog, check it out.
As long as I can remember I've enjoyed public speaking. This doesn't mean I am good at it, it simply means I enjoy it. School events, class president, my jobs, etc., they all taught me great lessons about public speaking. So when I started as a developer, sharing my knowledge with others at conferences or meetups came pretty natural.
I'd like to clarify, that after years of doing talks and other methods of public speaking, I am still terrified. I get nervous, my hands sweat, my legs shake, and my voice gets weird. Basically what I am trying to say is that I'm not an expert by any means, but I overcome the phobia of public speaking by doing it frequently.
For many years I have speaking at conferences, but in the past few years I started conducting longer workshops. I first started doing online workshops, which have their pros and cons. While they don't put you face to face with your audience, it also does not give you a good sense for how effective your training is because you can't see people reactions. For this reason I prefer to do face-to-face training.
As part of my job I conduct periodic Front-End training workshops for clients and recently I started conducting all-day training workshops at conferences. I really enjoy it and I'ld like to share some of the lessons learned.
Ideally you want to pick a topic you feel 100% confident about. I have learned that people attending your training or talks welcome any information you can share no matter how simple or elementary it may feel to you. Don't ever think what you know may not be of interest to others because you would be wrong.
Lately I have been challenging myself a little more when picking a topic to train about. While is good to know the topic well, it is also extremely rewarding to pick a topic you'd like to learn more about. This may feel contrary to what I said ealier but hear me out. When you decide to train on a topic, you will spend a lot of time preparing, training, testing and reharsing. This is exactly how you learn a new skill. I can't tell you how many times I come out of training I did knowing more about the topic than before and also learning from people who attended the training. If you want to learn a new skill, teaching others about it could be the best way for you to learn it.
Preparing for the trainingEveryone has their own style for teaching or doing a presentation. Some people like to use slides and screenshots, others show recordings of their project or code. My personal preference is to build a working prototype. This to me presents many advantages, but it also means you will spend more time getting ready.
My training workshops usually include very little slides because the majority of the training will be spent writing actual code and building the prototype during the training.
Here's my typical process for preparing for a training workshop:
-
Identify a prototype that serves the purpose of the training. If I am teaching a workshop about component based development I would normally pick something that involves the different aspects of component based development (attoms, nested components, reusable components, etc.)
-
Build the prototype upfront to ensure you have a working model to demo and go by.
-
Once prototype is built, create a public repo so you can share the working prototype
-
Write step-by-step instructions to building the prototype. Normally I would break the prototype down into small components, atoms.
-
Test, test and test. You want to make sure yoru audience will not run insto unexpected issues while following your instructions. For this reason you need to make sure you test your instructions. Ask a friend or colleague to go through each of your excercises to ensure thing work as expected.
-
Provide a pre-training evaluation. A quick set of questions that will give you an idea of people's skills level as well as environment (Linux, Widows, OSX). This will help you plan ahead of time.
-
Build a simple slide deck for introductions and agenda purposes. Mainly I move away from slides as soon as introduction and agenda is done. The rest of the training is all hands on.
As you will learn, one thing that can really kill a lot of the time during training is assisting people with their local environment setup. I have conducted training workshops where I've spent half the time helping people with their environment. For this reason, nowadays I communicate with the people ahead of time to ensure everyone's local environment is ready to go.
I normally make myself available once or twice in an evening through a google hangout to assit anyone who may need help. I also provide detailed instructions on how to get their local environment ready. This could save you a lot of time during training. In addition, for those who did spend the time on getting their environment ready, it's not fair that they have to be held back because someone did not make an effort to setup their environment.
I make myself available ahead of training but if someone is still having issues because of neglect, I don't hold the rest of the class back. I try to help them but at some point I move on.
If possible, get help from someone who is also well-versed with the topic so they can assist you help people who may get stuck. Nothing is more frustrating that havign to break the flow of the training to help people who get stuck. Having someone else help you with this allows you to continue with the training and not have everyone loose momentum.
FinallyEnjoy yourself. Make sure you and your audience have fun. If you show excitement in what you are doing people will get excited as well.
The Drop Times: What We Learned from DrupalJam: Open Up 2024
LakeDrops Drupal Consulting, Development and Hosting: ECA 2.0.0 has been released for Drupal 10.3 and 11
Almost 2 years ago, ECA 1.0.0 was published, and a lot happened in the 23 months in between. Today, ECA gets its first major update which comes not only with a ton of new features but also with code clean-up, performance improvements and support for the latest Drupal core releases 10.3 and soon 11.
ComputerMinds.co.uk: My text filter's placeholder content disappeared!
When I upgraded a site from Drupal 10.1 to 10.2, I discovered a particularly serious bug: the login form on our client's site vanished ... which was pretty serious for this site which hid all content behind a login!
We had a custom text format filter plugin to render the login form in place of a custom token in text that editors set, on one of the few pages that anonymous users could access. Forms can have quite different cacheability to the rest of a page, and building them can be a relatively expensive operation anyway, so we used placeholders which Drupal can replace 'lazily' outside of regular caching:
class MymoduleLoginFormFilter extends FilterBase implements TrustedCallbackInterface { public function process($text, $langcode) { $result = new FilterProcessResult($text); $needle = '[login_form]'; // No arguments needed as [login_form] is always to be replaced with the same form. $arguments = []; $replace = $result->createPlaceholder(self::class . '::renderLoginForm', $arguments); return $result->setProcessedText(str_replace($needle, $replace, $text)); } public static function renderLoginForm() { // Could be any relatively expensive operation. return \Drupal::formBuilder()->getForm(UserLoginForm::class); } public static function trustedCallbacks() { return ['renderLoginForm']; } }But our text format also had core's "Correct faulty and chopped off HTML" filter enabled - which completely removed the placeholder, and therefore the form went missing from the final output!
Debugging this to investigate was interesting - it took me down the rabbit hole of learning more about PHP 8 Fibers, as Drupal 10.2 uses them to replace placeholders. Initially, I thought the problem could be there, but it turned out that the placeholder itself was the problem. Drupal happily generated the form to go in the right place, but couldn't find the placeholder. Here's what a placeholder, created by FilterProcessResult::createPlaceholder() should look like:
<drupal-filter-placeholder callback="Drupal\mymodule\Plugin\Filter\MymoduleLoginFormFilter::renderLoginForm" arguments="" token="hqdY2kfgWm35IxkrraS4AZx6zYgR7YRVmOwvWli80V4"></drupal-filter-placeholder>Looking very carefully, I spotted that the arguments="" attribute in the actual markup was just arguments - i.e. it had been turned into a 'boolean' HTML attribute:
<drupal-filter-placeholder callback="Drupal\mymodule\Plugin\Filter\MymoduleLoginFormFilter::renderLoginForm" arguments token="hqdY2kfgWm35IxkrraS4AZx6zYgR7YRVmOwvWli80V4"></drupal-filter-placeholder>There is a limited set of these, and yet the masterminds/html5 component that Drupal 10.2 now uses to process HTML 5 requires an explicit list of the attributes that should not get converted to boolean attributes when they are set to an empty string.
At this point, I should point out that this means a simple solution could be to just pass some arguments so that the attribute isn't empty! That is a nice immediate workaround that avoids the need for any patch, so is an obvious maintainable solution:
// Insert your favourite argument; any value will do. $arguments = [42];At least that ensures our login form shows again!
But I don't see any documentation saying there must be arguments, and it would be easy for someone to write this kind of code again elsewhere, especially if we're trying to do The Right Thing by using placeholders in filters.
So I decided to contribute a fix back to Drupal core. I've worked on core before. Sometimes it's a joy to find or fix something that affects thousands of people, other times the contribution process can be soul-destroying. At least in this case, I found an existing test in core that could be easily extended to demonstrate the bug. Then I wrote a surgical fix... but I can see that it tightly couples the filter system to Drupal's HtmlSerializerRules class. That class is within the \Drupal\Component namespace, which is described as:
Drupal Components are independent libraries that do not depend on the rest of Drupal in order to function.
Components MAY depend on other Drupal Components or external libraries/packages, but MUST NOT depend on any other Drupal code.
So perhaps it needs configuration in order to be decoupled; and/or a factory service; or maybe modules should subscribe to an event to be able to inject their own rules .... and very quickly perfection feels like the enemy of good, as I can imagine the scope of a solution ballooning in size and complexity.
I'm all for high standards in core, but fulfilling them to produce solutions can still be a slow and frustrating experience. I'm already involved in enough long-running issues that just bounce around between reviewers, deprecations and changes in standards. I risk just ranting here rather than providing answers - and believe me, I'm incredibly grateful for the work that reviewers and committers have put into producing Drupal - but surely the current process must be putting so many potential contributors off. We worry about attracting talent to the Drupal ecosystem, and turning Takers into Makers, but what are they going to find when they arrive? Contributing improvements of decent size is hard and can require perseverance over years. Where can we adjust the balance to make contribution easier for anyone, even seasoned developers?
As I suggested, perhaps this particular bug needs any of a factory pattern, event subscriber, or injected configuration... but what would my next step be? I'm reluctant to put effort into writing a more complex solution when I know from experience that reviewers might just suggest doing something different anyway. At least I have that simple (if unsatisfying) workaround for the filter placeholder method: always send an argument, even if it might be ignored. I guess that reflects the contribution experience itself sometimes!
Wim Leers: XB week 5: chaos theory
We started the week off with a first MVP config entity: the component config entity. We have to start somewhere and then iterate — and iterate we can: thanks to Felix “f.mazeikis”, #3444417 landed! Most crucially this means we now have both a low-level unit test and a kernel test that ensures the validation for this config entity is thorough — of course I’m bringing config validation to Experience Builder (XB) from the very start!
It doesn’t do much: it allows “opting in” single directory components (SDCs) to XB, and now only those are available in the PoC admin UI. Next up is #3452397: expanding the admin UI and the config entity to allow defining default values for its SDC props. This will allow the UI to immediately generate a preview of the component, which otherwise may (depending on the component) not render anything visible (imagine: a <h2> without text, an <img> without an image, etc.).
But literally everything about this config entity is subject to change:
- its name (issue: #3455036)
- the whole “opting in” principle (issue comment: #3444424-7
- what it does exactly (issue: #3446083)
- whether it should handle only exposing SDCs 1:1 in XB, or whether it should also handle other component types (issue: #3454519)
Which brings me to the main subject for this week: I have a theory that explains the chaos that I think now many of you see.
We rushed to open the 0.x branch to invite contribution, because Dries announced XB at DrupalCon. This wrongly gave the impression that there was a very detailed plan, with a precise architecture in mind. That is not the case, and mea culpa for giving that impression. That branch was simply the combination of different PoCs merged together.
Some additional context: back in March, from one day to the next, I along with Ben “bnjmnm”, Ted “tedbow”, Alex “effulgentsia”, Tim and Lauri, plus Felix and Jesse (both from the Site Studio team, both very experienced) I were asked to stop everything we were doing (for me: dropping config validation) and start estimating the product requirements.
~4 weeks of non-stop grueling meetings 1 to assess and estimate the 64 requirements, with Lauri providing visual examples and additional context for the product requirements he crafted (after careful research: #3452440). We looked at multiple radical options and evaluated which of these was viable:
- building on top of one of the page building solutions that already exist in the Drupal ecosystem
- partnering with one of the existing page building systems, if they were willing to relicense under GPL-2+ and we’d be able to fit them onto Drupal’s data modeling strengths
- building something new (but reusing the parts in the Drupal ecosystem that we can build on top of, such as SDC, field types for data modeling, etc.)
The result: 60 pages worth of notes 2, with for each of the 42 “Required for MVP” product requirements (out of a total of 64), there now was an outline of how it could be implemented, a best+realistic+worst case estimate (week-level accuracy), which skillsets needed and how many people. It’s during this process that it became clear that only one choice made sense: the last one. That was the conclusion we reached because the existing solutions in the ecosystem fall short (see #3452440), Site Studio does not meet all of XB’s requirements and some of its architectural choices conflict with XB’s requirements and we did not end up finding a magical unicorn that meshed perfectly with Drupal.
It’s during this time that I made my biggest mistake yet: because the request for estimating this was coming from within Acquia, I assumed this was intended to be built by a team of Acquians. Because … why else create estimates?! If it’s built by a mix of paid full-time, paid part-time and volunteer community members, there’s little point in estimating, right?!
Well, I was wrong: turns out the intent was to get a sense of how feasible it was to achieve in roughly which timeline. But I and my fellow team members were so deep into this enormous estimation exercise based on very high-level requirements that thinking about capturing this information in a more shareable form was a thought that simply did not occur…
So, choice 3 it was, with people that have deep Layout Builder knowledge (Ted & Tim), while bringing in expertise from the Site Studio team (Jesse & Felix) and strong front-end expertise (Ben & Harumi “hooroomoo”) … because next we were asked to narrow the estimates for the highest-variance estimates, to bring it down to a higher degree of confidence. That’s where the “FTEs”, “Best Case Size (weeks)”, “Realistic Case Size (weeks)”, “Worst Case Size (weeks)”, “Most Probable Case Size (calculated, FTE weeks)”, “Variance” columns in the requirements spreadsheet come in.
For the next ~4 weeks, we built PoCs for the requirements where our estimates had either the highest variance or the highest number, to both narrow and reduce the estimates. That’s where all the branches on the experience_builder project come from! For example:
- Harumi’s poc-codemirror for editing component styles (not in 0.x)
- Felix’ research__component_export_poc to verify we could actually transport default file content as part of the regular config import/export system (not in 0.x)
- Ben’s cypress_starter
- Ben’s easyblocks_backend (not in 0.x)
- Jesse’s poc_reactrouter (not in 0.x)
- Ted’s research_sb_config for config-defined SDCs (not in 0.x)
- my research__design_versions (not in 0.x)
- …
After the ~8 chaotic weeks above, it was DrupalCon (Dries also wrote about this, just before DrupalCon, based on the above work!), and then … it was week 1.
In that first week, we took a handful of branches that seemed to contain the pieces most sensible to start iterating on an actual implementation, threw that into a witch pot, and called that 0.x!
Now that all of you know the full origin story, and many of you have experienced the subsequent ~4 equally chaotic weeks, you’re all caught up! :D
Missed a prior week? See all posts tagged Experience Builder.
Goal: make it possible to follow high-level progress by reading ~5 minutes/week. I hope this empowers more people to contribute when their unique skills can best be put to use!
For more detail, join the #experience-builder Slack channel. Check out the pinned items at the top!
Going forward: order from chaosSo, how are we going to improve things?
- Tuesday: issue queue 100% triaged — each issue that is for a later phase is now Postponed and each issue that needs input from someone has that person assigned.
- Friday: adopt Architecture Decision Records (ADRs): #3454669) — many thanks to Lullabot for leading the way
- Friday: the first high-level diagrams landed, and thanks to Kyle “ctrlADel”, they follow the C4 model
- More to come in week 6!
So besides the backstory and attempts to bring more order & overview, what happened?
The UI gained panning support (#3452623), which really makes it feel like it’s coming alive:
Try it yourself locally if you like, but there’s not much you can do yet.
Install the 0.x branch — the “Experience Builder PoC” toolbar item takes you there!
… you can also try an imperfect/limited version of the current UI on the statically hosted demo UI thanks to #3450311 by Lee “larowlan”.
We also got two eslint CI jobs running now (#3450307): one for the typical Drupal stuff, one for the XB UI (which is written in TypeScript) and reduced the overhead of PHPStan Level 8 (again thanks to Lee, in #3454501) … and a bunch more low-level things that were improved.
Various things are in progress … expect an update for those next week :)
Thanks to Lauri for reviewing this!
Nonprofit Drupal posts: June Drupal for Nonprofits Chat
Join us THURSDAY, June 20 at 1pm ET / 10am PT, for our regularly scheduled call to chat about all things Drupal and nonprofits. (Convert to your local time zone.)
We don't have anything specific on the agenda this month, so we'll have plenty of time to discuss anything that's on our minds at the intersection of Drupal and nonprofits. Got something specific you want to talk about? Feel free to share ahead of time in our collaborative Google doc: https://nten.org/drupal/notes!
All nonprofit Drupal devs and users, regardless of experience level, are always welcome on this call.
This free call is sponsored by NTEN.org and open to everyone.
-
Join the call: https://us02web.zoom.us/j/81817469653
-
Meeting ID: 818 1746 9653
Passcode: 551681 -
One tap mobile:
+16699006833,,81817469653# US (San Jose)
+13462487799,,81817469653# US (Houston) -
Dial by your location:
+1 669 900 6833 US (San Jose)
+1 346 248 7799 US (Houston)
+1 253 215 8782 US (Tacoma)
+1 929 205 6099 US (New York)
+1 301 715 8592 US (Washington DC)
+1 312 626 6799 US (Chicago) -
Find your local number: https://us02web.zoom.us/u/kpV1o65N
-
- Follow along on Google Docs: https://nten.org/drupal/notes
The Drop Times: Christina Lockhart on Elevating Drupal: The Essential Role of Women in Tech
DrupalEasy: Two very different European Drupal events in one week
I was fortunate enough to attend two very different European Drupal events recently, and wanted to take a few minutes to share my experience as well as thank the organizers for their dedication to the Drupal community.
The events couldn't have been more different - one being a first-time event in a town I've never heard of and the other celebrating their 20th anniversary in a city that can be considered the crossroads of the Netherlands.
DrupalCamp CemaesFirst off - it is pronounced KEM-ice (I didn't learn this until I actually arrived). Cemaes is a small village at the very northern tip of Wales with a population of 1,357. Luckily, one of those 1,357 people is Jennifer Tehan, an independent Drupal developer who decided to organize a Drupal event in a town that (I can only assume) has more sheep than people.
When this event was first announced a few months back, I immediately knew that this was something I wanted to attend - mostly because I've always wanted to visit Wales to do some hiking ("walking," if you're not from the United States.) I used the camp as an excuse for my wife and I to take a vacation and explore northern Wales. I could write an entire other blog post on how amazeballs the walking was…
DrupalCamp Cemaes was never going to be a large event, and it turns out there wound up being only nine of us (ten if you count Frankie, pictured below.) The advantage of such a small event was easy to see - we all got to know each other quite well. There's something really nice - less intimidating - about small Drupal events that I realized that I've really missed since the decline of Drupal meetups in many localities.
The event itself was an unconference, with different people presenting on Drupal contributions (James Shields), GitHub Codespaces (Rachel Lawson), remote work (Jennifer Tehan), the LocalGov distribution (Andy Broomfield), and Starshot (myself.)
The setting of the event couldn't have been more lovely - an idyllic seaside community where everything was within walking distance, including the village hall where the event took place. If Jennifer decides to organize DrupalCamp Cemaes 2025, I'm pretty sure Gwendolyn and I will be there.
Read a brief wrap-up of DrupalCamp Cemaes from Jennifer Tehan.
This camp was proof that anyone, anywhere can organize a successful Drupal event with a minimum of fuss.
DrupaljamFour days later, I was in Utrecht, Netherlands, at the 20th anniversary of Drupaljam, the annual main Dutch Drupal event.
I had previously attended 2011 Drupaljam and recall two things about that event: the building it took place in seemed like it was from the future, and many of the sessions were in Dutch.
This was a very different event from the one I had been at a few days earlier, in a city of almost 400,000 people with over 300 attendees (I was told this number anecdotally) in one of the coolest event venues I've ever been to. Defabrique is a renovated linseed oil and compound feed factory that is a bit over 100 years old that is (IMHO) absolutely perfect for a Drupal event. Each and every public space has character, yet is also modern enough to support open-source enthusiasts. On top of all that, the food was amazing.
Attending Drupal Jam was a bit of a last minute decision for me, but I'm really glad that I made the time for it. I was able to reconnect with members of the European Drupal community that I don't often see, and make new connections with more folks that I can count.
I spent the day the way I spend most of my time at Drupal events - alternating between networking and attending sessions that attracted me. There were a number of really interesting AI-related sessions that I took in; it's pretty obvious to me that the Drupal community is approaching AI integrations in an unsurprisingly thoughtful manner.
The last week reinforced to me how fortunate I am to be able to attend so many in-person Drupal events. The two events I was able to participate in couldn't have been more different in scale and scope, but don't ask me to choose my favorite, because I'm honestly not sure I could!
Specbee: Getting started with integrating Drupal and Tailwind CSS
Talking Drupal: Talking Drupal #455 - Top 5 uses of AI for Drupal
Today we are talking about AI Tips for Drupal Devs, AI Best Practices, and Drupal Droid with guest Mike Miles. We’ll also cover AI interpolator as our module of the week.
For show notes visit: www.talkingDrupal.com/455
Topics- Top 5 tips
- Idea Generation (Ideation)
- Code Generation
- Debugging
- Content Generation
- Technical Explanations
- How do you suggest people use AI for Ideation
- Is MIT Sloan using AI to help with Drupal Development
- Does that code get directly inserted into your sites
- What are some common pitfalls
- Is your team using AI for debugging
- Any best practices you have found helping when working with AI
- Is MIT Sloan using AI for content generation
- What is an example of how you use AI for technical explanations
- What is your view ont he future of AI in Drupal, do you think AI will replace Drupal developers
- Drupal Droid
- Talking Drupal - Drupal Droid
- Workflows of ai
- AI guidelines
- Using AI as a Development Tool
- Drupal Starshot: Delivering on a promise to Ambitious Site Builders
Michael Miles - mike-miles.com mikemiles86
HostsNic Laflin - nLighteneddevelopment.com nicxvan John Picozzi - epam.com johnpicozzi Randy Fay - rfay
MOTW CorrespondentMartin Anderson-Clutz - mandclu.com mandclu
- Brief description:
- Have you ever wanted to use AI to help populate entity fields that were left blank by Drupal content authors? There’s a module for that.
- Module name/project name:
- Brief history
- How old: created in Sep 2023 by Marcus Johansson of FreelyGive
- Versions available: 1.0.0-rc4
- Maintainership
- Actively maintained, recent release in the past month
- Security coverage - opted in, needs stable release
- Test coverage
- Documentation - User guide
- Number of open issues: 18 open issues, none of which are a bugs
- Usage stats:
- 94 sites
- Module features and usage
- In scientific fields, interpolation is the process of using known data to extrapolate or estimate unknown data points. In a similar way this module helps your Drupal site provide values for fields that didn’t receive input, based on the information that was provided.
- Fundamentally Interpolator AI provides a framework and an API, and then relies on companion modules for processing, either by leveraging third-party services like AI LLMs, or PHP-based scripting.
- There are existing integrations with a variety of AI services, including OpenAI, Dreamstudio, Hugging Face, and more.
- You can add retrievers to help extract and normalize the content you’re processing, for example photos from an external site, and other tools to help normalize and optimize content and media, and optimize any prompts you will be using with AI services.
- You can also extend the workflow capabilities of AI Interpolator, for example using the popular and powerful ECA module that we’ve talked about before on this show.
ADCI Solutions: How to add a live chat to a website using Mercure
The Drop Times: Tech Talks, Brand Strategies, and the Future of Drupal: Highlights from The DropTimes
An enterprise web solution is inherently complex. Its endless integrations with various applications that are added or loaded off as per the changing requirements, multiple front-ends that provide varied and personalised digital experiences, the cultural nuances catered to in its l10n editions, the time-zone differences that are to be taken care of, etc., etc., are the most basic parts that add to the complexity.
Continuous investment, development and deployment are part and parcel of such a solution. Even so, it becomes amiable only if the complexity is masked out in the simplicity of the consumer touch points.
Two separate sets of consumers should be considered there. One is the enterprise consumers, the content creators and marketers, who are the immediate benefactors of the solution. The second, a more prominent set, is the customers to whom the enterprises cater.
Drupal, the platform for building a digital experience, invested heavily in satisfying the end customers. That was what Drupal agencies were doing all these years. They got an exoskeleton on which they built the end-user experiences. But what about the enterprise consumers themselves? Weren’t they the ones who ultimately decided to ditch or continue using the services? How good were their experiences sans the involvement of a hired developer?
The decision to simplify the touch points of enterprise consumers is a development and design decision. These content creators and marketers will benefit the most from the Starshot Initiative. It supports the marketing team by reducing the go-to-market time and eases the content creators by providing the most valuable tools out of the box. A complex solution is now draped in a simple cassock.
The first story I’m sharing today concerns Drupal’s new brand strategy and future directions. The Starshot Initiative manifests this strategy and the direction the project is taking. An interview with Shawn Perritt, Senior Director of Brand & Creative at Acquia, shines a light on this brand refresh. Read Shawn’s conversation with Alka Elizabeth, our sub-editor.
Montreal became the hub for tech talks in the past week. We conducted one-question interviews with three featured speakers at Evolve Drupal Montreal. Kazima Abbas, our sub-editor, spoke with Josh Koenig, co-founder of Pantheon, Joe Kwan, Manager of Web Development at the University of Waterloo, and the formidable Mike Herchel, Senior Front-end Developer at Agileana. Their answers form part of the feature published here.
Kazima also conducted an exclusive interview with Baddý Sonja Breidert, CEO and Co-Founder of 1xINTERNET. The interview focused on the agency’s support for the Starshot Initiative based on its experience with ‘Try Drupal’, which implements a default Drupal deployment for enterprises. It explains why they did not wait or hesitate to offer all-out support for the new initiative.
Alka Elizabeth and Ben Peter Mathew, our community manager, had jointly covered the Starshot Session on ‘Strategic Milestones in Product Definition’ led by Dries Buytaert and Cristina Chumillas on June 7. The report can be read here.
The next two stories are our signature stories, written by our founder and lead, Anoop John, CTO of Zyxware Technologies. From the start, Anoop was vocal about ‘DrupalCollab’, an idea he had been nurturing for years. As part of the initiative, he has published a list of the largest 500+ cities in the world with a sizable population of people tagged ‘Drupal developers’ on LinkedIn. You can read the analysis here. The second story published with this list is, On Using LinkedIn to Analyze the Size of the Drupal Community, an article on the methodology for this analysis using LinkedIn, associated errors and justifications. Based on these stories, Anoop is trying to build momentum to restart local Drupal meetups in as many cities as possible. Also, read his LinkedIn article on the initiative here.
Mautic 5.1 Andromeda Edition was released last week with major updates. Read our report here. We published a quick review of the Drupal Quick Exit Module developed by Oomph Inc. Ben Peter Mathew spoke with Alyssa Varsanyi of Oomph to produce this report.
To ensure brevity, I shall list the rest of the important stories below without much ado.
That is for the week, dear readers. I wish you all a very sacred Eid Ul Adha. Let the festival of sacrifice be a time to ponder the things we are ready to give up for the better common good.
To get timely updates, follow us on LinkedIn, Twitter and Facebook. Also, join us on Drupal Slack at #thedroptimes.
Thanks and regards.
Sincerely,
Sebin A. Jacob,
Editor-in-Chief, The DropTimes
Mario Hernandez: Automating your Drupal Front-end with ViteJS
Modern web development relies heavily on automation to stay productive, validate code, and perform repetitive tasks that could slow developers down. Front-end development in particular has evolved, and it can be a daunting task to configure effective automation. In this post, I'll try to walk you through basic automation for your Drupal theme, which uses Storybook as its design system.
Recently I worked on a large Drupal project that needed to migrate its design system from Patternlab to Storybook. I knew switching design systems also meant switching front-end build tools. The obvious choice seemed to be Webpack, but as I looked deeper into build tools, I discovered ViteJS.
Vite is considered the Next Generation Frontend Tooling, and when tested, we were extremely impressed not only with how fast Vite is, but also with its plugin's ecosystem and its community support. Vite is relatively new, but it is solid and very well maintained. Learn more about Vite.
The topics covered in this post can be broken down in two categories:
-
Preparing the Front-end environment
-
Automating the environment
In a previous post, I wrote in detail how to build a front-end environment with Vite and Storybook, I am going to spare you those details here but you can reference them from the original post.
- In your command line, navigate to the directory where you wish to build your environment. If you're building a new Drupal theme, navigate to your site's web/themes/custom/
- Run the following commands (Storybook should launch at the end):
Fig. 1: The first command builds the Vite project, and the last one integrates Storybook into it.
Reviewing Vite's and Storybook's out of the box build scriptsVite and Storybook ship with a handful of useful scripts. We may find some of them already do what we want or may only need minor tweaks to make them our own.
- In your code editor, open package.json from the root of your newly built project.
- Look in the scripts section and you should see something like this:
Fig. 2: Example of default Vite and Storybook scripts out of the box.
To run any of those scripts, prefix them with npm run. For example: npm run build, npm run lint, etc. Let's review the scripts.
- dev: This is a Vite-specific command which runs the Vite app we just build for local development
- build: This is the "do it all" command. Running npm run build on a project runs every task defined in the build configuration we will do later. CI/CD runners run this command to build your app for production.
- lint: Will lint your JavaScript code inside .js or .jsx files.
- preview: This is also another Vite-specific command which runs your app in preview mode.
- storybook: This is the command you run to launch and keep Storybook running while you code.
- build-storybook: To build a static version of Storybook to package it or share it, or to run it as a static version of your project.
In front-end development, it is important everyone in your team use the same version of NodeJS while working in the same project. This ensures consistency in your project's behavior for everyone in your team. Differences in the node version your team uses can lead to inconsistencies when the project is built. One way to ensure your team is using the same node version when working in the same project, is by adding a .nvmrc file in the root of your project. This file specifies the node version your project uses. The node version is unique to each project, which means different projects can use different node versions.
- In the root of your theme, create a file called .nvmrc (mind the dot)
- Inside .nvmrc add the following: v20.14.0
- Stop Storybook by pressing Ctrl + C in your keyboard
- Build the app:
Fig. 3: Installs the node version defined in .nvmrc, then installs node packages, and finally builds the app.
NOTE: You need to have NVM installed in your system to execute nvm commands.You only need to run nvm install once per project unless the node version changes. If you switch to a project that uses a different node version, when you return to this project, run nvm use to set your environment back to the right node version.
The output in the command line should look like this:
Fig. 4: Screenshot of files compiled by the build command.
By default, Vite names the compiled files by appending a random 8-character string to the original file name. This works fine for Vite apps, but for Drupal, the libraries we'll create expect for CSS and JS file names to stay consistent and not change. Let's change this default behavior.
- First, install the glob extension. We'll use this shortly to import multiple CSS files with a single import statement.
- Then, open vite.config.js in your code editor. This is Vite's main configuration file.
- Add these two imports around line 3 or directly after the last import in the file
- Still in vite.config.js, replace the export default... with the following snippet which adds new settings for file names:
Fig. 5: Build object to modify where files are compiled as well as their name preferences.
- First we imported path and { glob }. path is part of Vite and glob was added by the extension we installed earlier.
- Then we added a build configuration object in which we defined several settings:
- emptyOutDir: When the build job runs, the dist directory will be emptied before the new compiled code is added.
- outDir: Defines the App's output directory.
- rollupOptions: This is Vite's system for bundling code and within it we can include neat configurations:
- input: The directory where we want Vite to look for CSS and JS files. Here's where the path and glob imports we added earlier are being used. By using src/**/**/*.{css,js}, we are instructing Vite to look three levels deep into the src directory and find any file that ends with .css or .js.
- output: The destination for where CSS and JS will be compiled into (dist/css and dist/js), respectively. And by setting assetFileNames: 'css/[name].css', and entryFileNames: 'css/[name].js', CSS and JS files will retain their original names.
Now if we run npm run build again, the output should be like this:
Fig. 6: Screenshot of compiled code using the original file names.
The random 8-character string is gone and notice that this time the build command is pulling more CSS files. Since we configured the input to go three levels deep, the src/stories directory was included as part of the input path.
2. Restructure the projectThe out of the box Vite project structure is a good start for us. However, we need to make some adjustments so we can adopt the Atomic Design methodology. This is today's standards and will work well with our Component-driven Development workflow. At a high level, this is the current project structure:
> .storybook/ > dist/ > public/ > src/ |- stories/ package.json vite.config.jsFig. 7: Basic structure of a Vite project listing only the most important parts.
- > .storybook is the main location for Storybook's configuration.
- > dist is where all compiled code is copied into and where the production app looks for all code.
- > public is where we can store images and other static assets we need to reference from our site. Equivalent to Drupal's /sites/default/files/.
- > src is the directory we work out of. We will update the structure of this directory next.
- package.json tracks all the different node packages we install for our app as well as the scripts we can run in our app.
- vite.config.js is Vite's main configuration file. This is probably where we will spend most of our time.
The Atomic Design methodology was first introduced by Brad Frost a little over ten years ago. Since then it has become the standard for building web projects. Our environment needs updating to reflect the structure expected by this methodology.
- First stop Storybook from running by pressing Ctrl + C in your keyboard.
- Next, inside src, create these directories: base, components, and utilities.
- Inside components, create these directories: 01-atoms, 02-molecules, 03-organisms, 04-layouts, and 05-pages.
- While we're at it, delete the stories directory inside src, since we won't be using it.
Since the project structure has changed, we need to make Storybook aware of these changes:
- Open .storybook/main.js in your code editor
- Update the stories: [] array as follows:
Fig. 8: Updating stories' path after project restructure.
The Stories array above is where we tell Storybook where to find our stories and stories docs, if any. In Storybook, stories are the components and their variations.
Add pre-built componentsAs our environment grows, we will add components inside the new directories, but for the purpose of testing our environment's automation, I have created demo components.
- Download demo components (button, title, card), from src/components/, and save them all in their content part directories in your project.
- Feel free to add any other components you may have built yourself. We'll come back to the components shortly.
Before we can see the newly added components, we need to configure Storybook to understands the Twig and YML code we are about to introduce within the demo components. To do this we need to install several node packages.
- In your command line run:
- Next, update vite.config.js with the following configuration. Add the snippet below at around line 5:
Fig. 9: TwigJS related packages and Drupal filters function.
The configuration above is critical for Storybook to understand the code in our components:
- vite-plugin-twig-drupal, is the main TwigJS extension for our project.
- Added two new imports which are used by Storybook to understand Twig:
- vite-plugin-twig-drupal handles transforming Twig files into JavaScript functions.
- @modyfi/vite-plugin-yaml let's us pass data and variables through YML to our Twig components.
- Still in vite.config.js, add the twig and yml() plugins to add Twig namespaces for Storybook.
Fig. 10: Twig namespaces reflecting project restructure.
Since we removed the react() function by using the snippet above, we can remove import react from '@vitejs/plugin-react' from the imports list as is no longer needed.
With all the configuration updates we just made, we need to rebuild the project for all the changes to take effect. Run the following commands:
npm run build npm run storybookThe components are available but as you can see, they are not styled even though each component contains a CSS stylesheet in its directory. The reason is Storybook has not been configured to find the component's CSS. We'll address this shortly.
4. Configure postCSSWhat is PostCSS? It is a JavaScript tool or transpiler that turns a special PostCSS plugin syntax into Vanilla CSS.
As we start interacting with CSS, we need to install several node packages to enable functionality we would not have otherwise. Native CSS has come a long way to the point that I no longer use Sass as a CSS preprocessor.
- Stop Storybook by pressing Ctrl + C in your keyboard
- In your command line run this command:
- At the root of your theme, create a new file called postcss.config.js, and in it, add the following:
Fig. 11: Base configuration for postCSS.
One cool thing about Vite is that it comes with postCSS functionality built in. The only requirement is that you have a postcss.config.js file in the project's root. Notice how we are not doing much configuration for those plugins except for defining them. Let's review the code above:
- postcss-import the base for importing CSS stylesheets.
- postcss-import-ext-glob to do bulk @import of all CSS content in a directory.
- postcss-nested to unwrap nested rules to make its syntax closer to Sass.
- postcss-preset-env defines the CSS browser support level we need. Stage 4 means we want the "web standards" level of support.
The goal here is to ensure that every time a new CSS stylesheet or JS file is added to the project, Storybook will automatically be aware and begin consuming their code.
NOTE: This workflow is only for Storybook. In Drupal we will use Drupal libraries in which we will include any CSS and JS required for each component.There are two types of styles to be configured in most project, global styles which apply site-wide, and components styles which are unique to each component added to the project.
Global styles- Inside src/base, add two stylesheets: reset.css and base.css.
- Copy and paste the styles for reset.css and base.css.
- Inside src/utilities create utilities.css and in it paste these styles.
- Inside src/, create a new stylesheet called styles.css.
- Inside styles.css, add the following imports:
Fig. 12: Imports to gather all global styles.
The order in which we have imported our stylesheets is important as the cascading order in which they load makes a difference. We start from reset to base, to utilities.
- reset.css: A reset stylesheet (or CSS reset) is a collection of CSS rules used to clear the browser's default formatting of HTML elements, removing potential inconsistencies between different browsers before any of our styles are applied.
- base.css: CSS Base applies a style foundation for HTML elements that is consistent for baseline styles such as typography, branding and colors, font-sizes, etc.
- utilities.css: Are a collection of pre-defined CSS rules we can apply to any HTML element. Rules such as variables for colors, font size, font color, as well as margin, sizes, z-index, animations, etc.
Before our components can be styled with their unique and individual styles, we need to make sure all our global styles are loaded so the components can inherit all the base/global styles.
- Inside src/components create a new stylesheet, components.css. This is where we are going to gather all components styles.
- Inside components.css add glob imports for each of the component's categories:
Fig. 13: Glob import for all components of all categories.
NOTE: Since we only have Atoms and Molecules to work with, we are omitting imports for 03-organisms, 04-layouts, 05-pages. Feel free to add them if you have that kind of components. Updating Storybook's PreviewThere are several ways in which we can make Storybook aware of our styles and javascript. We could import each component's stylesheet and javascript into each *.stories.js file, but this could result in some components with multiple sub-components having several CSS and JS imports. In addition, this is not an automated system which means we need to manually do imports as they become available. The approach we are going to take is importing the stylesheets we created above into Storybook's preview system. This provides a couple of advantages:
- The component's *.stories.js files are clean without any css imports as all CSS will already be available to Storybook.
- As we add new components with individual stylesheets, these stylesheets will automatically be recognized by Storybook.
Remember, the order in which we import the styles makes a difference. We want all global and base styles to be imported first, before we import component styles.
- In .storybook/preview.js add these imports at the top of the page around line 2.
Fig. 14: Importing all styles, global and components.
In addition to importing two new extensions: twig and twig-drupal-filters, we setup a setupFilters function for Storybook to read Drupal filters we may use in our components. We are also importing two of the stylesheets we created earlier:
- styles.css contains all the CSS code from reset.css, base.css, and utilities.css (in that order)
- components.css contains all the CSS from all components. As new components are added and they have their own stylesheets, they will automatically be included in this import.
On a typical project, you will find that the majority of your components don't use JavaScript, and for this reason, we don't need such an elaborate system for JS code. Importing the JS files in the component's *.stories.js should work just fine. Since the demo components dont use JS, I have commented near the top of card.stories.js how the component's JS file would be imported if JS was needed.
If the need for a more automated JavaScript processing workflow arose, we could easily repeat the same CSS workflow but for JS.
Build the project againNow that our system for CSS and JS is in place, let's build the project to ensure everything is working as we expect it.
npm run build npm run storybookYou may notice that now the components in Storybook look styled. This tells us our new system is working as expected. However, the Card component, if you used the demo components, is missing an image. We will address this issue in the next section.
This concludes the preparation part of this post. The remaining part will focus on creating automation tasks for compiling, minifying and linting code, copying static assets such as images, and finally, watching for code changes as we code. 6. Copying images and other assetsCopying static assets like images, icons, JS, and other files from src into dist is a common practice in front-end projects. Vite comes with built-in functionality to do this. Your assets need to be placed in the public directory and Vite will automatically copy them on build. However, sometimes we may have those assets alongside our components or other directories within our project.
In Vite, there are many ways to accomplish any task, in this case, we will be using a nice plugin called vite-plugin-static-copy. Let's set it up.
- If Storybook is running, kill it with Ctrl + C in your keyboard
- Next, install the extension by running:
- Next, right after all the existing imports in vite.config.js, import one more extension:
- Lastly, still in vite.config.js, add the viteStaticCopy function configuration inside the plugins:[] array:
Fig. 15: Adds tasks for copying JavaScript and Images from src to dist.
The viteStaticCopy function we added allows us to copy any type of static assets anywhere within your project. We added a target array in which we included src and dest for the images we want copied. Every time we run npm run build, any images inside any of the components, will be copied into dist/images.
If you need to copy other static assets, simply create new targets for each.
- Build the project again:
The missing image for the Card component should now be visible. Pretty sweet! 🍰
7. The Watch taskA watch task makes it possible for developers to see the changes they are making as they code, and without being interrupted by running commands. Depending on your configuration, a watch task watches for any changes you make to CSS, JavaScript and other file types, and upon saving those changes, code is automatically compiled, and a Hard Module Reload (HMR) is evoked, making the changes visible in Storybook.
Although there are extensions to create watch tasks, we will stick with Storybook's out of the box watch functionality because it does everything we need. In fact, I have used this very approach on a project that supports over one hundred sites.
I actually learned this the hard way, I originally was importing the key stylesheets in .storybook/preview.js using the files from dist. This works to an extend because the code is compiled upon changes, but Storybook is not aware of the changes unless we restart Storybook. I spent hours debugging this issue and tried so many other options, but at the end, the simple solution was to import CSS and JS into Storybook's preview using the source files. For example, if you look in .storybook/preview.js, you will see we are importing two CSS files which contain all of the CSS code our project needs:
import '../src/styles.css'; import '../src/components/components.css';Fig. 16: Importing source assets into Storybook's preview.
Importing source CSS or JS files into Storybook's preview allows Storybook to become aware immediately of any code changes.
The same, or kind of the same works for JavaScript. However, the difference is that for JS, we import the JS file in the component's *.stories.js, which in turn has the same effect as what we've done above for CSS. The reason for this is that typically not every component we build needs JS.
A real watch taskCurrently we are running npm run storybook as a watch task. Nothing wrong with this. However, to keep up with standards and best practices, we could rename the storybook command, watch, so we can run npm run watch. Something to consider.
You could also make a copy of the storybook command and name it watch and add additional commands you wish to run with watch, while leaving the original storybook command intact. Choices, choices.
8. Linting CSS and JavaScriptOur workflow is coming along nicely. There are many other things we can do but for now, we will end with one last task: CSS and JS linting.
- Install the required packages. There are several of them.
- Next, after the last import in vite.config.js, add one more:
- Then, let's add one more plugin in the plugins:[] array:
Fig. 17: Checks for linting CSS and JavaScript.
So we can execute the above checks on demand, we can add them as commands to our app.
- In package.json, within the scripts section, add the following commands:
Fig. 18: Two new npm commands to lint CSS and JavaScript.
- We installed a series of packages related to ESLint and Stylelint.
- vite-plugin-checker is a plugin that can run TypeScript, VLS, vue-tsc, ESLint, and Stylelint in worker thread.
- We imported vite-plugin-checker and created a new plugin with two checks, one for ESLint and the other for Stylelint.
- By default, the new checks will run when we execute npm run build, but we also added them as individual commands so we can run them on demand.
Both ESLint and Stylelint use configuration files where we can configure the various rules we want to enforce when writing code. The files they use are eslint.config.js and .stylelintrc.yml respectively. For the purpose of this post, we are only going to add the .stylelintrc.yml in which we have defined basic CSS linting rules.
- In the root of your theme, create a new file called .stylelintrc.yml (mind the dot)
- Inside .stylelintrc.yml, add the following code:
Fig. 19: Basic CSS Stylelint rules.
The CSS rules above are only a starting point, but should be able to check for the most common CSS errors.
Test the rules we've defined by running either npm run build or npm run stylelint. Either command will alert you of a couple of errors our current code contains. This tells us the linting process is working as expected. You could test JS linting by creating a dummy JS file inside a component and writing bad JS in it.
9. One last thingIt goes without saying that we need to add storybook.info.yml and storybook.libraries.yml files for this to be a true Drupal theme. In addition, we need to create the templates directory somewhere within our theme.
storybook.info.ymlThe same way we did for Storybook, we need to create namespaces for Drupal. This requires the Components module and storybook.info.yml configuration is like this:
components: namespaces: atoms: - src/components/01-atoms molecules: - src/components/02-molecules organisms: - src/components/03-organisms layouts: - src/components/04-layouts pages: - src/components/05-pages templates: - src/templatesFig. 20: Drupal namespaces for nesting components.
storybook.libraries.ymlThe recommended method for adding CSS and JS to components or a theme in Drupal is by using Drupal libraries. In our project we would create a library for each component in which we will include any CSS or JS the component needs. In addition, we need to create a global library which includes all the global and utilities styles. Here are examples of libraries we can add in storybook.libraries.yml.
global: version: VERSION css: base: dist/css/reset.css: {} dist/css/base.css: {} dist/css/utilities.css: {} button: css: component: dist/css/button.css: {} card: css: component: dist/css/card.css: {} title: css: component: dist/css/title.css: {}Fig. 21: Drupal libraries for global styles and component's styles.
/templatesDrupal's templates' directory can be created anywhere within the theme. I typically like to create it inside the src directory. Go ahead and create it now.
- Inside storybook.info.yml, add a new Twig namespace for the templates directory. See example above. Update your path accordingly based on where you created your templates directory.
P.S: When the Vite project was originally created at the begining of the post, Vite created files such as App.css, App.js, main.js, and index.html. All these files are in the root of the project and can be deleted. It won't affect any of the work we've done, but Vite will no longer run on its own, which we don't need it to anyway.
In closingI realize this is a very long post, but there is really no way around it when covering these many topics in a single post. I hope you found the content useful and can apply it to your next Drupal project. There are different ways to do what I've covered in this post, and I challenge you to find better and more efficient ways. For now, thanks for visiting.
Download the themeA full version of the Drupal theme built with this post can be downloaded.
Make sure you are using the theme branch from the repo.
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:
- What is Drupal integration
- Installing and preparing Drupal for integration
- Building components in Storybook
- Building a basic front-end workflow
- Integrating Drupal with Storybook components
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 StorybookThe 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:
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- Open the Drupal site in your code editor and within your code editor navigate to the storybook theme (web/themes/custom/storybook)
- Create two new directories inside components called 01-atoms and 02-molecules
- Inside 02-molecules create a new directory called card
- Inside the card directory add the following four files:
- card.css: component's styles
- card.twig: component's markup and logic
- card.stories.jsx: Storybook's story
- card.yml: component's demo data
- Add the following code snippet to card.yml:
- Add the following to card.twig to provide the markup and logic for the card:
Code snippet for building card
-
Copy and paste these styles into card.css.
-
Finally, let's create the Storybook card story by adding the following to card.stories.jsx:
Let's go over a few things regarding the code above:
- The data structure in card.yml reflects the data structure and type we will use in Drupal.
- The image field uses the entire <img> element rather than just using the image src and alt attributes. The reason for this is so when we get to Drupal, we can use Drupal's full image entity. This is a good practice for caching purposes.
- card.twig reuses the title component we created in the previous post. Rather than build a title from scratch for the Card and repeat the code we already wrote, reusing the existing components keeps us DRY.
- card.stories.jsx in the Storybook story for the Card, notice how the code in this file is very similar to the code in the title.stories.jsx. Even with complex components, when we port them into Storybook as stories, most times the code will be similar as what you see above because Storybook is simply parsing whatever is in .twig and .yml files. There are exceptions when the React code may have extra parameters or logic which typically happens when we're building stories variations. Maybe a topic for a different blog post. 😉
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:
- In your code editor or command line (whichever is easier), move the title directory into the 01-atoms directory
- In your editor, open title.stories.jsx and change the line
title: 'Components/Title' to title: 'Atoms/Title'. This will display the title component within the Atoms category in Storybook's sidebar. - Rather than have you make individual changes to vite.config.js, let's replace/overwrite all its content with the following:
Let's go over some of the most noticeable updates inside vite.config.js:
-
We have defined a few things to improve the functionality of our Vite project, starting with using src as our app root directory and public for publicDir. This helps the app understand the project structure in a relative manner.
-
Next, we defined a Build task which provides the app with defaults for things like where should it compiled code to (i.e. /dist), and rollupOptions for instructing the app which stylesheets to compile and what to call them.
-
As part of the rollupOptions we also defined two stylesheets for global styles (reset.css and styles.css). We'll create these next.
Important This is as basic as it gets for a build workflow and in no way would I recommend this be your front-end build workflow. When working on bigger projects with more components, it is best to define a more robust and dynamic workflow that provides automation for all the repetitive tasks performed on a typical front-end project. -
Under the Plugins section, we have defined two new namespaces, @atoms and @molecules, each of which points to specific path within our components directory. These are the namespaces Storybook understands when nesting components. You can have as many namespaces as needed.
- Inside storybook/src, create a new directory called css
- Inside the css directory, add two new files, reset.css and styles.css
- Here are the styles for reset.css and styles.css. Please copy them and paste them into each of the stylesheets.
- Now for Storybook to use reset.css and styles.css, we need to update /.storybook/preview.js by adding these two imports directly after the current imports, around line 4.
- In your command line, navigate to the storybook directory and run:
A quick note about the commands above:
- nvm install and npm install are typically only done once in your app. These commands will first install and use the node version specified in .nvmrc, and will install all the required node packages found in package.json. If you happen to be workign on another project that may use a different version of node, when you comeback to the Storybook project you will need to run nvm use in order to resume using the right node version.
- npm run build is usually only ran when you have made configuration changes to the project or are introducing new files.
- npm run storybook is the command you will use all the time when you want to run Storybook.
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:
Installing Drupal and setting up the Storybook themeWe 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.
- Build a basic Drupal 10 website (I recommend using DDEV).
- 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.
- No need to enable the theme just yet, we'll come back to the theme shortly.
- Finally, create a new Article post that includes a title, body content and an image. We'll use this article later in the process.
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.
- Install and enable the Components module
- Add the following namespaces at the end of storybook.info.yml (mind your indentation):
- Replace all content in storybook.libraries.yml with the following:
-
Let's go over the changes to both, storybook.info.yml and storybook.libraries.yml files:
- Using the Components module we created two namespaces: @atoms and @molecules. Each namespace is associated with a specific path to the corresponding components. This is important because Drupal by default only looks for Twig templates inside the /templates directory and without the Components module and the namespaces it would not know to look for our component's Twig templates inside the components directory.
- Then we created two Drupal libraries: global and card. The Global library includes two CSS stylesheets (reset.css and styles.css), which handle base styles in our theme. the Card library includes the styles we wrote for the Card component. If you noticed, when we created the Card component, the first line inside card.twig is a Twig attach library statement. Basically card.twig is expecting a Drupal library called card.
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.
-
The first thing we need to do to begin the integration process is to determine which Twig template Drupal uses to render article nodes in teaser view mode. One easy way to do this is by turning Twig debugging on. This used to be a complex configuration but starting with Drupal 10.1 you can now do it directly in Drupal's UI:
- While logged in with admin access, navigate to /admin/config/development/settings on your browser. This will bring up the Development settings page.
- Check all the boxes on this page and click Save settings. This will enable Twig debugging and disable caching.
- Now navigate to /admin/config/development/performance so we can turn CSS and JS aggregation off.
- Under Bandwidth optimization cleared the two boxes for CSS and Javascript aggregation then click on Save configuration.
- Lastly, click the Clear all caches button. This will ensure any CSS or JS we write will be available without having to clear caches.
-
With Twig debugging on, go to the homepage where the Article we created should be displayed in teaser mode. If you right-click on any part of the article and select inspect from the context menu, you will see in detail all the templates Drupal is using to render the content on the current page. See example below.
Note I am using a new basic Drupal site with Olivero as the default theme. If your homepage does not display Article nodes in teaser view mode, you could create a simple Drupal view to list Article nodes in teaser view mode to follow along.
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 suggestionBy 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.
- Copy /core/themes/olivero/templates/content/node--teaser.html.twig into your theme's /storybook/templates/content/. Create the directory if it does not exist.
- Now rename the newly copied template to node--article--teaser.html.twig.
- Clear Drupal's cache since we are introducing a new Twig template.
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- Delete everything from the newly copied template except the comments and the classes array variable
- At the bottom of what is left in the template add the following code snippet:
- We set a variable with content|render as its value. The only purpose for this variable is to make Drupal aware of the entire content array for caching purposes. More info here.
- Next, we setup a variable called article_title which we structured the same way as data inside card.yml. Having similar data structures between Drupal and our components provides many advantages during the integration process.
- Notice how for the text and url properties we are using Drupal specific variables (label and url), accordingly. If you look in the comments in node--article--teaser.html.twig you will see these two variables.
- We are using a Twig include statement with the @molecules namespace to nest the Card component into the node template. The same way we nested the Title component into the Card.
- We mapped Drupal's attributes into the component's attributes placeholder so Drupal can inject any attributes such as CSS classes, IDs, Data attributes, etc. into the component.
- Finally, we mapped the image, title and teaser fields from Drupal to the component's equivalent fields.
- Save the changes to the template and clear Drupal's cache.
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 CardIntegration 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:
- If you right-click on the article and select Inspect, you will notice the following:
- Drupal is now using node--article--teaser.html.twig. This is the template we created.
- The template path is now themes/custom/storybook/src/templates/content/.
- You will also notice that the article is using the custom markup we wrote for the Card component which is more semantic, accessible, but in addition to this, the <article> tag is also inheriting several other attributes that were provided by Drupal through its Attributes variable. See below:
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:
- Going to the Manage display tab of the Article's Teaser view mode (/admin/structure/types/manage/article/display/teaser).
- Changing the image style for the Image field for one that may work better for your image.
- Preview the article again on the homepage to see if this looks better.
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.Mario Hernandez: Migrating from Patternlab to Storybook
Building a custom Drupal theme nowadays is a more complex process than it used to be. Most themes require some kind of build tool such as Gulp, Grunt, Webpack or others to automate many of the repeatitive tasks we perform when working on the front-end. Tasks like compiling and minifying code, compressing images, linting code, and many more. As Atomic Web Design became a thing, things got more complicated because now if you are building components you need a styleguide or Design System to showcase and maintain those components. One of those design systems for me has been Patternlab. I started using Patternlab in all my Drupal projects almost ten years ago with great success. In addition, Patternlab has been the design system of choice at my place of work but one of my immediate tasks was to work on migrating to a different design system. We have a small team but were very excited about the challenge of finding and using a more modern and robust design system for our large multi-site Drupal environment.
Enter StorybookAfter looking a various options for a design system, Storybook seemed to be the right choice for us for a couple of reasons: one, it has been around for about 10 years and during this time it has matured significantly, and two, it has become a very popular option in the Drupal ecosystem. In some ways, Storybook follows the same model as Drupal, it has a pretty active community and a very healthy ecosystem of plugins to extend its core functionality.
Storybook looks very promising as a design system for Drupal projects and with the recent release of Single Directory Components or SDC, and the new Storybook module, we think things can only get better for Drupal front-end development. Unfortunately for us, technical limitations in combination with our specific requirements, prevented us from using SDC or the Storybook module. Instead, we built our environment from scratch with a stand-alone integration of Storybook 8.
INFO: At the time of our implementation, TwigJS did not have the capability to resolve SDC's namespace. It appears this has been addressed and using SDC should now be possible with this custom setup. I haven't personally tried it and therefore I can't confirm. Our process and requirementsIn choosing Storybook, we went through a rigorous research and testing process to ensure it will not only solve our immediate problems with our current environment, but it will be around as a long term solution. As part of this process, we also tested several available options like Emulsify and Gesso which would be great options for anyone looking for a ready-to-go system out of the box. Some of our requirements included:
1. No components refactoringThe first and non-negotiable requirement was to be able to migrate components from Patternlab to a new design system with the least amount of refactoring as possible. We have a decent amount of components which have been built within the last year and the last thing we wanted was to have to rebuild them again because we are switching design system.
2. A new Front-end build workflowI personally have been faithful to Gulp as a front-end build tool for as long as I can remember because it did everything I needed done in a very efficient manner. The Drupal project we maintain also used Gulp, but as part of this migration, we wanted to see what other options were out there that could improve our workflow. The obvious choice seemed to be Webpack, but as we looked closer into this we learned about ViteJS, "The Next Genration Frontend Tooling". Vite delivers on its promise of being "blazing fast", and its ecosystem is great and growing, so we went with it.
3. No more Sass in favor of PostCSSCSS has drastically improved in recent years. It is now possible with plain CSS, to do many of the things you used to be able to only do with Sass or similar CSS Preprocessor. Eliminating Sass from our workflow meant we would also be able to get rid of many other node dependencies related to Sass. The goal for this project was to use plain CSS in combination with PostCSS and one bonus of using Vite is that Vite offers PostCSS processing out of the box without additional plugins or dependencies. Ofcourse if you want to do more advance PostCSS processing you will probably need some external dependencies.
Building a new Drupal theme with StorybookLet's go over the steps to building the base of your new Drupal theme with ViteJS and Storybook. This will be at a high-level to callout only the most important and Drupal-related parts. This process will create a brand new theme. If you already have a theme you would like to use, make the appropriate changes to the instructions.
1. Setup Storybook with ViteJS ViteJS- In your Drupal project, navigate to the theme's directory (i.e. /web/themes/custom/)
- Run the following command:
- When prompted, select the framework of your choice, for us the framework is React.
- When prompted, select the variant for your project, for us this is JavaScript
After the setup finishes you will have a basic Vite project running.
Storybook- Be sure your system is running NodeJS version 18 or higher
- Inside the newly created theme, run this command:
- After installation completes, you will have a new Storybook instance running
- If Storybook didn't start on its own, start it by running:
Twig templates are server-side templates which are normally rendered with TwigPHP to HTML by Drupal, but Storybook is a JS tool. TwigJS is the JS-equivalent of TwigPHP so that Storybook understands Twig. Let's install all dependencies needed for Storybook to work with Twig.
- If Storybook is still running, press Ctrl + C to stop it
- Then run the following command:
- vite-plugin-twig-drupal: If you are using Vite like we are, this is a Vite plugin that handles transforming twig files into a Javascript function that can be used with Storybook. This plugin includes the following:
- Twig or TwigJS: This is the JavaScript implementation of the Twig PHP templating language. This allows Storybook to understand Twig.
Note: TwigJS may not always be in sync with the version of Twig PHP in Drupal and you may run into issues when using certain Twig functions or filters, however, we are adding other extensions that may help with the incompatability issues. - drupal attribute: Adds the ability to work with Drupal attributes.
- Twig or TwigJS: This is the JavaScript implementation of the Twig PHP templating language. This allows Storybook to understand Twig.
- twig-drupal-filters: TwigJS implementation of Twig functions and filters.
- html-react-parser: This extension is key for Storybook to parse HTML code into react elements.
- @modifi/vite-plugin-yaml: Transforms a YAML file into a JS object. This is useful for passing the component's data to React as args.
Update your vite.config.js so it makes use of the new extensions we just installed as well as configuring the namesapces for our components.
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({ plugins: [ twig({ namespaces: { components: join(__dirname, "./src/components"), // Other namespaces maybe be added. }, }), // Allows Storybook to read data from YAML files. yml(), ], }) Storybook configurationOut of the box, Storybook comes with main.js and preview.js inside the .storybook directory. These two files is where a lot of Storybook's configuration is done. We are going to define the location of our components, same location as we did in vite.config.js above (we'll create this directory shortly). We are also going to do a quick config inside preview.js for handling drupal filters.
- Inside .storybook/main.js file, update the stories array as follows:
- Inside .storybook/preview.js, update it as follows:
- If Storybook is still running, press Ctrl + C to stop it
- Inside the src directory, create the components directory. Alternatively, you could rename the existing stories directory to components.
With the current system in place we can start building components. We'll start with a very simple component to try things out first.
- Inside src/components, create a new directory called title
- Inside the title directory, create the following files: title.yml and title.twig
- Inside title.yml, add the following:
- Inside title.twig, add the following:
We have a simple title component that will print a title of anything you want. The level key allows us to change the heading level of the title (i.e. h1, h2, h3, etc.), and the modifier key allows us to pass a modifier class to the component, and the url will be helpful when our title needs to be a link to another page or component.
Currently the title component is not available in storybook. Storybook uses a special file to display each component as a story, the file name is component-name.stories.jsx.
- Inside title create a file called title.stories.jsx
- Inside the stories file, add the following:
- If Storybook is running you should see the title story. See example below:
- Otherwise start Storybook by running:
With Storybook running, the title component should look like the image below:
The controls highlighted at the bottom of the title allow you to change the values of each of the fields for the title.
I wanted to start with the simplest of components, the title, to show how Storybook, with help from the extensions we installed, understands Twig. The good news is that the same approach we took with the title component works on even more complex components. Even the React code we wrote does not change much on large components.
In the next blog post, we will build more components that nest smaller components, and we will also add Drupal related parts and configuration to our theme so we can begin using the theme in a Drupal site. Finally, we will integrate the components we built in Storybook with Drupal so our content can be rendered using the component we're building. Stay tuned. For now, if you want to grab a copy of all the code in this post, you can do so below.
Resources In closingGetting to this point was a team effort and I'd like to thank Chaz Chumley, a Senior Software Engineer, who did a lot of the configuration discussed in this post. In addition, I am thankful to the Emulsify and Gesso teams for letting us pick their brains during our research. Their help was critical in this process.
I hope this was helpful and if there is anything I can help you with in your journey of a Storybook-friendly Drupal theme, feel free to reach out.
Mario Hernandez: Managing image embeds with Drupal media
Allowing your content creators to embed images in text fields is a big risk if you don't have the right measures in place to get properly rendered images without jeopardizing your site's performance. We faced this issue first-hand with embedded images due to not using the right configuration and this lead to extremely large images being rendered. In this post I'll go over the techniques I took for addressing those issues and set up a system for image embeds that is solid and performant.
I started by writing a seven-part guide on how to setup responsive images. In this post I'll focus on image embeds. If you followed or read the responsive images guide, you should be able to take advantage of some of the work we did there in this post. The guidelines covered here include:
- Defining requirements
- Image styles
- Media view modes
- Text format configuration
Before you write the first line of code or set the first drupal configuration for this issue, you need to have a clear understanding of your requirements. Here is a summary of my requirements:
-
Only certain user roles can embed images
This means we need to figure out if the text formats used in our site will allow us to set the restrictions we need. Otherwise we may need to create or edit a text format for our target user roles.
-
Users need to be able to choose the image size and aspect ratio when embedding images
We defined the image sizes and aspect ratios and assigned names that were user-friendly for non-technical users. We came up with name options we think our users will find easy to work with such as:
- Small square, Small portrait, Small rectangular
- Medium square, Medium portrait, Medium rectangular, Medium rectangular wide
- Large square, Large rectangular, Large rectangular wide
- Extra large square, Extra large rectangular, Extra large rectangular wide
-
If no option is selected by users, set a default image size
For the default option when no selection is made by the user, we decided to use the Medium rectangular option. This has an aspect ratio of 3:2 and it measures about 720x480.
-
Existing Media items need to be available for embedding
This was a tricky one because my original inclination was to create a new Media type so we can isolate all configuration for its view modes and not overpopulate our default Media type. However, this ended up not working for us because when you limit your image embeds to only use a new Media type, you don't get access to any of the media items (images), that have already been uploaded to the Media library using other media types. Ultimately we ended up using Drupal core's Media type, Image, and our dev team had to compromise on having a very busy list of view modes for this media type.
-
Images need the ability to be cropped wihin the Media page
Since most of our images already provide the ability to be cropped at different aspect ratios, using the core Media type in the previous bullet point made this an easy solution.
It all starts with image styles. I'm not going to go over how to create image styles, you can read my post Image styles in Drupal. The one thing I am going to repeat however is the importance of creating reusable image styles. Reusable image styles can help you reduce the number of image styles you create while providing the flexibility you need with each use case.
Image styles are key as each of the size options we defined above translate into image styles. So Small square for example, is an image style that is defined as 1:1 (250px). Medium rectangular would be something like 3:2 (720x480), etc. You may be wondering, how do you plan on using fiendly names for your content editors when your image styles names are not very friendly? Great question. Since we are using Drupal's Media, content editors do not interact directly with image styles, they do with Media view modes and this is where we will use more friendly names.
Media view modesView modes are one of Drupal's powerful features. Being able to display content is different ways with little effort can turn a simple website into a dynamic content hub. The example I always give when someone asks me what view modes are or how do they work is the Amazon website. When you are viewing a product in amazon.com, or most retail websites for that matter, you will notice that the same product or similar ones appear all over the page but in slightly different ways, with different fields or styles. See the page below for an example.
The image above shows many ways in which a product can be displayed. I've numbered each display.
In Drupal, every entity such as content types, media types, blocks, etc., offer the ability to create view modes. For the purpose of image embeds, we will create a Media type view mode for each image style we plan on using. The view modes is what content editors will interact with when choosing an image size or aspect ratio during the image embed process. This is where we will use the user-friendly names we defined earlier. Let's go over how this relationship between view modes and image styles works for image embeds.
Configure view modes for the Image media type-
In your Drupal site, create an image style for each image size option you wish to provide to users when embedding images.
-
Next, create a Media view mode for each image style (/admin/structure/display-modes/view). Very iimportant: Remember the view mode's label (name) is where we are going to use the friendly name (i.e. Medium rectangular (720x480)). I like to keep the machine name similar to the label so it's easier to debug or identify in code (i.e. medium_rectangular_720x480).
-
Now, let's tie 1 & 2 together:
- Go to the media type you plan on using for media embeds (/admin/structure/media/manage/image/display). I am using Drupal core's Image media type.
- Scroll down and expand the Custom display settings fieldset.
- Check each of the view modes you created in step 2 and click Save.
-
Now click each of the view modes and update the image field to use the respective/matching image style.
View modes and image styles are all configured. Now let's configure the Text format that authorized users will use to embed images.
- Go to the Text formats and editors page (/admin/config/content/formats)
- Click Configure next to the text format you plan on using (i.e. Full HTML)
- Ensure the right user roles are selected
- Within the Toolbar configuration section, drag the Drupal media button from the Available buttons options to the Active toolbar section. You could probably remove the original insert image button since you won't be using it.
- Scroll to the Enabled filters section and check the Embed media checkbox
- Scroll to the Filter settings section and set the following:
-
Default view mode: This is the default display that will be used if content editors don't pick an option when embedding images. Select any of the view modes that represents the image size you want to use as default.
-
Media types selectable in the Media Library: Select the Media type you plan on using. In my case is Image.
-
View modes selectable in the 'Edit media' dialog: Finally, select each of the view modes you created in the previous section. FYI: View modes will be sorted in alpha order by their machine name. In my case I had to prefix some of the machine names with either "a" or "b" so the list of options for the users to choose from would be nicely organized by their label name. See screnshot below.
-
Click Save configuration
-
Now that we've completed all the configuration we should be able to take it for test drive.
- Go to any page where there is a text field with a WYSIWYG editor
- Make sure you are using the right text format by selecting it at the bottom of the text field where you want to embed an image
- Click the Insert media button from the editor's toolbar
- Select or upload the image you'd like to embed
- When the image has been inserted, click on it and you should see several options of actions you can do with the image. Things like align the image, add a caption, link it, and you should also see a selection box listing all the view modes you created.
- After making your selection you should immediately see the image size/aspect ratio change to the one you selected. When you are happy with your selection, click the Save button to save your page.
Important: Depending on your site's configuration, the options for changing your image size may look different than mine. In my case, I am only using Drupal's core modules and this is how the options look for me:
In closingPutting a system like this for your image embeds will give you the piece of mind that content editors have options to choose how big or small they would like images to be displayed, and from a performance point of view, if your image styles are done properly, you can rest assurred that bloated images will never be rendered because you have put the guard rails in place to avoid this from happening.
I hope you found this article useful and can put these techniques to use in your own Drupal project. Happy New Year! 🎉 🎊 🎆 👋