Using Bento Components in Gutenberg Blocks
I have spent the weekend of March 19th to 21st at the CloudFest Hackathon in Rust, where I had submitted a project together with Pascal Birchler (@swissspidy). It was lots of fun and producing many interesting insights for me personally as well as for the team, so I’d like to share a few of my impressions here.
What is the Cloudfest Hackathon?
The global cloud computing industry comes together each year in the Europa-Park Resort in Rust to celebrate the Cloudfest (formerly World Hosting Days). This event is happening in an amusement park that is still closed off to the public, with all of the attractions being reserved for the attendees! 😁
Leading up to the Cloudfest on the weekend immediately prior, there is traditionally the Cloudfest Hackathon, where open source enthusiasts join forces with industry partners to work on exciting projects.
The last two planned editions of this hackathon had to be canceled due to the pandemic, so I was even more eager this time to get together with the other participants and have a few days of social fun in a creative setting.
What is Bento?
Bento is a relatively new high-performance web components library that makes it easy to optimize your web properties for a great page experience. It is officially governed through the OpenJS Foundation and was initially conceived to make the performance insights and engineering expertise of the AMP project available to more people and with fewer strings attached. Bento components are self-contained and can be used in a gradual mix-and-match approach, as opposed to the all-or-nothing proposition that the AMP runtime tries to enforce.
Bento components are packaged as React or Preact components for a seamless integration into any framework using such a stack. However, they are also provided as standardized Web Components so that they can be used anywhere else where (P)React might not be available.
Why would Bento be a good fit for Gutenberg Blocks?
The WordPress block-based editor, or Gutenberg, lets site owners assemble a page layout via “blocks”. These blocks are designed with two different contexts in mind: an interactive editing representation that lets you manipulate the block to your liking within the admin backend, as well as a rendered frontend representation that gets persisted into the database and is shown to visitors.
This duality, in its simplest form, is represented by the two main functions that each block needs to provide to properly exist within Gutenberg:
import { useBlockProps } from '@wordpress/block-editor';
const blockSettings = {
apiVersion: 2,
// The edit function renders the block in its editable representation.
edit: () => {
const blockProps = useBlockProps();
return <div { ...blockProps }>Your block in the editor.</div>;
},
// The save function renders the block in its final frontend form.
save: () => {
const blockProps = useBlockProps.save();
return <div { ...blockProps }> Your block on the frontend. </div>;
};
};
The “Edit” context is what the block editor shows within the WordPress admin backend. It is a fully interactive editing environment powered by React. This allows us to use the React version of the Bento web components within the editor. These React components can be intertwined with the Gutenberg React components in seemingly endless and sometimes quite surprising ways.
The “Save” context is regular frontend markup. This is the snippet of HTML that will be stored in the content as it is persisted to the database. This, on the other hand, is a perfect fit for the web components that Bento can be compiled into. Instead of the markup being a very complex hierarchy of nested divs and other elements to power such a component, it will be the simplest possible representation in the form of a custom HTML element, like <bento-carousel ...>
. This has the advantage that fewer internals bleed into the database, making upgrades to the actual components smoother and less expensive.
Goal of the Hackathon
Pascal & I did not have a very well-defined goal when introducing the project, with the following excerpt as the stated goal leading up to the event:
The goal would be to create a starter template that makes it easy to quickly set up a WordPress website that has excellent Page Experience scores right out of the gate, by powering Gutenberg blocks via a solid Bento components foundation.
https://www.cloudfest.com/project/bento-wordpress
Once our group was assembled and started discussing the project, though, the team was quick to agree on an approach and a deliverable to target: a landing page for a fictitious hackathon powered by a Gutenberg full-site editing (FSE) theme and custom blocks using Bento components.
This would force us to have a practical application that we were aiming for, and it allowed us to split up the tasks in a very pragmatic way, letting us work in parallel for most of the hackathon.
Bento Components that were Implemented
Most Bento components were a 1-to-1 mapping into Gutenberg blocks. However, some components required more than one block to make sense in terms of their UX. The following table summarizes what we ended up with during the course of the hackathon.
HTML Element(s) | React Component(s) | Block(s) |
<bento-accordion> | BentoAccordion, BentoAccordionSection, BentoAccordionHeader, BentoAccordionContent | bento/accordion, bento/accordion-section |
<bento-base-carousel> | BentoBaseCarousel | bento/carousel |
<bento-date-countdown> | BentoDateCountdown | bento/date-countdown, bento/countdown-days, bento/countdown-hours, bento/countdown-minutes, bento/countdown-seconds |
<bento-fit-text> | BentoFitText | bento/fit-text |
<bento-lightbox-gallery> | – | (filter) |
<bento-sidebar> | (template part) | bento/sidebar |
<bento-social-share> | BentoSocialShare | bento/social-share |
7 Bento components | 11 Gutenberg blocks |
All of these 11 blocks combine to power the landing page that we created for this fictitious hackathon. Note that we started with a completed empty theme that only had a theme.json
file to start with. This theme was extracted from the end-to-end tests of the Gutenberg source repository.
You can see this page for yourself and experiment with the scrolling, the image lazy loading, etc. by heading to the following URL:
👉 bit.ly/bento-wp 👈
Example Component: Bento Fit-Text
I’ll use one of the simpler components to demonstrate the interactions between Bento and Gutenberg: the <bento-fit-text>
component.
The Fit-Text component provides a way to render arbitrary text into a block container in such a way that it always properly fills its container. It does so by adapting the font size dynamically, within parametrizable minimum and maximum limits.
To provide a useful user experience, this component did not only require inspector controls to set the minimum and maximum font size and a way to provide the text to show – it also needed a way to configure the height of the container in an intuitive way.
To achieve this, we combined the Bento component with a <RichText>
component to make it typable, and wrapped it in a <ResizableBox>
to add a draggable height control.
<div {...blockProps}>
<ResizableBox
minHeight={50}
enable={{ bottom: true, right: false }}
onResizeStop={(event, direction, elt, delta) => {
setLocalHeight((prevHeight) => prevHeight + delta.height);
}}
>
<BentoFitText
className="bento-fit-text"
style={{ height: localHeight }}
minFontSize={parseInt(minFontSize.replace('px', ''), 10)}
maxFontSize={parseInt(maxFontSize.replace('px', ''), 10)}
>
<RichText
identifier="content"
value={content}
onChange={setContent}
aria-label={__('Fitted Text', 'gutenberg-bento')}
placeholder={__('Write text…', 'gutenberg-bento')}
/>
</BentoFitText>
</ResizableBox>
</div>
What were the Findings?
Both Gutenberg and Bento are flexible enough where it matters
We ended up with some pretty wild configurations, nesting Gutenberg blocks within Bento components that are nested in Gutenberg blocks…
We hit a single issue where something was not working as expected for Gutenberg, and a quick patch resolved that issue promptly.
Gutenberg had no trouble at all keeping parts or all of the interactivity of the Bento components alive within the live editor.
Bento on the other hand was happily accepting any Gutenberg blocks within containers like the accordion section.
Gutenberg DX is coming together
Maybe this was less of a surprise for some of the other members of the team, but for me personally, the developer experience of integrating these Components into new custom blocks was surprisingly good.
I still have trouble with finding the information I need, given the lack of up-to-date documentation for all of the new and advanced stuff that Gutenberg provides, but in this specific context, that was a non-issue – given we had members of the core Gutenberg team sitting at our table.
In the end, it was mostly a matter of finding the right React components for each of the expected user experiences and fiddling with the attributes to get them into the right spots. This made it very quick to get a first prototype going for most of these components.
Bento interactivity performance is impressive
The Bento components are based on years of engineering efforts to optimize for raw performance and hardware acceleration.
Components that have animated parts are rendered with surprising smoothness, even in the Gutenberg live editor.
The page we ended up with was not only completely unoptimized, it was also using images we dragged directly from Unsplash into the editor, with the entire thing hosted on a very slow VPS. Given that what you see on the sample page is probably the worst case scenario, we can confidently say that the performance of the Bento components is as good or even better as we hoped.
What’s next?
The team already discussed next steps and will proceed to add more polish to the current implementations. We’re also discussing what an appropriate structure of the plugin could look like. Do we want to have all blocks in one single plugin? Multiple plugins with individual blocks? How does this tie into the public block library?
You can keep up-to-date with our latest development efforts for this project at the following URL:
Thanks again to the entire team that worked on this project and invested time and effort into producing a workable solution in such a short time frame! 🎉
Pascal Birchler • Alain Schlesser
Jessica Lyschik • Héctor Prieto • Marcel Schmitz • Adam Zieliński • Greg Ziółkowski