 All right. Hey, everyone. My name is Daniel Vesa and my talk today is about theming with bundle classes layout builder and twig I've been involved in a very large-scale Drupal 9 build for the last year and Building the site this way has been a really good experience and one that I thought was worth sharing After this talk after I submitted this talk I thought about the more clickbait name of goodbye managed display But I thought about it far too late. So we get the more boring title Like I said, my name is Daniel Vesa. I work at previous next as a developer and I've been using Drupal since 2014 These slides and the demo site that I've built for this talk will be on my GitHub later So, you know, if you want to refresh yourself or come back with any questions. Let me know I Know what usually happens with me is that I think about about a hundred questions As soon as the conference finishes and I can't ask them anymore So if that happens to you, feel free to reach out. My name is Daniel Vesa on the Drupal Slag And I'm mainly in the Australia New Zealand channel the first thing I wanted to go over was bundle classes and I put a very Hyperbole statement of one of the best new additions to core and I actually think that's true Bundle classes let you define a PHP class per entity on your site or per entity type on your site So you can have a bundle class for things like node types vocabularies media block content Even into types like user You can have a bundle class per one What this means is that you can keep your business logic in one single place per entity type This means that developers don't need to dig around theme or a hundred modules to figure out We are a particular bit of functionality comes from they should just be able to go straight to the bundle class Saves a lot of time and it keeps everything quite clean and compact Since they're just standard PHP classes Bundle classes have a lot of wins around code sharing so you can define base classes or interfaces or traits Any of that stuff that let you share code and stop duplicating code across your site and part of this Leads to a like I said better code, but it also leads to better testability This is a really big win in my book. I'm a big fan of testing code and bundle classes makes that really clean and the last part is about twig so Bundle classes have built-in integration with twig any function in your bundle class that starts with get Or with has they can be used automatically inside twig templates So you don't need to have things like preprocesses in your code Having less PHP code and the theme is a big win for me And let your theme just focus on the things that it should be focusing on like the CSS and the JavaScript for your site Bundle classes are also opt-in So it's not something you have to commit to and spend, you know, a hundred hours building out bundle classes for everything on your site They can be done piece by piece when a new piece of functionality comes in or when a bug comes in You can definitely keep things going the way they were before Unfortunately, there isn't any other talks on bundle classes at this conference But I highly recommend you look into them more. They're awesome. I can't imagine not using them The second part is later builder. So layout builders being stable now for a really long time It's probably not quite the hot new thing anymore, but it is a really powerful site building tool With our builder either you can set a base layout for your content types and give editors Control over how the page looks on a per node basis So users can add or remove blocks fields media or with a pretty simple drag-and-drop interface In earlier versions of Drupal, this was possible with things like the panels module or People would leverage paragraphs to give something quite similar to this But now we've got it in core with a stable core maintained API And with it now being in core There's a suite of contributed modules that we've used on this project to really help the layout builder and editorial experience One of the drawbacks to layout builder out of the box is that every single block Becomes available for your editors to use right away This means pointless blocks like powered by Drupal or every single field could be given as a block Which means that your editors need to know how format is work, which is just it's not necessary They don't need to know that stuff. That's that's more our kind of role So the first thing oh and for example on the site that we have built we have built about 20 or so Blocks that belong to a program content type And out of the box it means there's 20 program blocks would be available for something like an article which Why do they need to be there? It just clogs the whole editorial screen To fix this we use layout builder restrictions Which allows you to configure a list of blocks that the editor can see and embed on a per-section basis It really cleans up that screen. So they only see what they want to use what they can use and What they won't get in trouble for using There's also a layout builder lock which prevents editors from editing or deleting certain sections for example Yeah, you can configure a layout builder lock So that you can't delete the hero of a page because you probably want to have that in and you can use layout builder Restrictions to stop the editor from being able to place the hero in the right column of a two-column layout Common sense common sense says, you know, let's not do these things But you know, let's protect against it just in case The last one I wanted to talk about was layout builder claro Oops. Oh geez is loud but a claro which Improves the drag and drop and the general feel of layout builder was cause claro theme There's a talk tomorrow at 11 from Joshua Graham, which is an overview and introduction to layout builder I'm sure they'll go over some of the finer concepts that might not be touched about in this talk So I recommend you go to that and the last point was twig So I'm sure having built Drupal 8 9 and 10 sites that everyone's pretty familiar with twig But for this project, we've been using it extensively with a twig based design system that we've built with KSS Our extensive use of twig allows us to match the components in our design system from Drupal and have real control over our markup This cleaner markup has a lot of benefits It's easier to style. It's got performance improvements because you're not loading the DOM with lots of unnecessary Elements like all of the wrappers that come out of the box with Drupal And I've also I've loved and I've used display suite heavily in the past but with this approach we've found that it's just not needed anymore and Not needing to click around the managed display with all of the different buttons and then just not hitting save right at the very end It's something that I've done many times and something that I'm glad to not need to think about anymore So for a quick little micro demo I'll set up a you mommy side If you're as bad as cooking as I am and you don't want to cook the same four things for the rest of your life You'll probably go on a lot of recipe sites that look just like this But in this example, you'll probably notice that something is missing that every single recipe site has to have You don't want to have any ideas and what that might be You're exactly right and just to point out how good that is I've called it the life story block Because what we really want is to let our readers, you know Read a thousand characters from us before they get to the recipe that they actually just wanted to look at So I've added this perfectly named life story block and it's got three fields That's got a sad scale field which measures how sad the story is It's got a read time field in minutes for the length of the story and then a field for the story itself Which is just a regular text formatted field Because we have layout builder and a default layout. We can now place this block anywhere on the page Because of course the life story is the most important part of the page I've added it to the very top here under the whole big story section But I could also place it under the recipe if I wanted to be nice Or I could remove the sidebar section and place place it there. Maybe We'd probably look a bit odd to have it there and something that we would probably stop with layout builder restrictions Probably shouldn't be there So here's the markup that's rendered for that one block. It's pretty long It's pretty div soupy and it feels like it's got about a hundred classes So out of the box each field has got three rapid three divs and there's an overall wrapping div So for an extremely simple block We're left with 10 divs and 27 lines of HTML and all that really does is display the content of three fields Two of them, which are a number This adds up pretty quickly. Let's say if you've got ten of these on a page or ten similar blocks on a page Starts to add up pretty quick So and your requirement is in Extensive user research says that users find it far too hard just to read the time in minutes We now need it to say short medium or long So under five minutes would be short six to ten medium 11 or plus minutes Would be long So let's use this opportunity to convert our block to a twig base layer And this is the first step and we are my original click baby name of goodbye managed display came from When we move our block over to a twig base layer, we just don't need any of this stuff anymore We're handling the rendering elsewhere So let's take some control over that markup and add a template for the block This is a very simple bundle life story block template that has this markup in it It's pretty clean. It's pretty easy and it wipes out all of those Drupal wrappers that were bloating our DOM We've got a couple of variables in here and for this first example that come from a preprocess While we have the full markup control We could also change the sad scale and the read time fields to be probably a more semantically correct definition list This would be a pain to do without having it inside twig So here is where we add those variables to the template. We're currently doing this by this preprocess The sad store are the sad scale and the life story Fields are just grabbing the values exactly from the field and the read time is being converted to a label Just with a very simple match function Spoiler, this is the code that will soon be converting over to our bundle class. So let's compare For the people with poor memory, this is the old markup the pretty long pretty old pretty gross stuff and This is the new markup So the new markup that's significantly clean. It's significantly smaller Adding the twig template has removed all of those Drupal wrappers It's taken us from 10 divs to one just the wrapping one and from 27 lines of HTML to six all While retaining the same look and the same feel of the block At the same time we've added a life story class at the very top Which is just for better CSS targeting What this means is that if you've got good code splitting set up in your theme You can use this approach to load only the life story CSS and JavaScript When this block is rendered but rather than having that included globally across everything you could easily attach a library to the top of this So where are we now? We've met the new requirement and at the same time we have reduced our markup to be a lot cleaner. So that's a big win The code right now is in a pre-process in the theme which makes code sharing messy and hard to find and Testing also becomes difficult for this Something like our read time conversion should probably be unit or current got kernel tested With this code, that's not really possible or that clean So testing this would most likely be a functional test Which means we would need to create probably a test user create a test node Create a block place the block of layout builder render the page and then assert that right label Is being rendered on the page that adds a lot of time both in development and in CI minutes waiting for the tests to run I And adds a lot of code especially if you've got 20 of these So let's fix that issue and convert our code over to a bundle class So I've removed the pre-process and I've added this instead It's a pretty easy bundle class since there is only the three fields our sad scale and our Hopefully that's readable our sad scale and story field are still just pulled directly from the field content And our get read time label Is just using that exact same match code that we had in the pre-process which we've now removed Our twig still looks pretty similar Except now I'm using all of the good stuff that comes from bundle classes At the top there you can see that we've got the block content entity available to us So rather than having those variables from the pre-process Instead we're just using the get sad scale get read time label and get story Methods from a bundle class directly I've talked a lot about testing without really adding any examples So now that we have our bundle class, let's just write a very quick code So a very quick test Now this test checks our read time label function And it checks that it returns short for one minute medium for six minutes And long for a really long number like a thousand minutes Across 10 runs of this test locally It averaged out to taking about 550 milliseconds per test run I can pretty much absolutely guarantee the other tests would have taken significantly longer to run okay, so Our example was for the most part pretty basic So I thought I'd cover some of the other I guess advanced things we've been doing for this project And I'd include some real world content So the first is adding a base class per entity type that should say not per entity Traits and code sharing And then design systems and some redacted real world examples Now when setting up bundle classes the cleanest option is To make a base class per entity type And have all of your custom bundle classes extend that rather than extending node or extending block content In this case, I've set up an abstract class called umami node base Which just has two very simple functions It's got the content type label And like it's human readable form not machine name and it's got the readable publish date Which just does what it says. It's a readable version of the publish date. So By extending this class rather than extending node All of your Node types on the site would have these functions available to them along with any other Kind of generic functions you'd want across all of your nodes And this would be the same for blocks or media or any of those other kind of base entity types Another thing that we've found useful is having a dedicated module per bundle class So rather than having Umami bundles and having all you know media and recipe and blocks all in one big monster module We would do something like create Umami node where we would have this here And then we would have umami recipe where we would have the umami recipe bundle class It just means that You know us devs know where to go and we know where to look for something And if we wanted to do something like remove the recipes We would be able to delete that module without Needing to worry about pulling out code from a bunch of different places because it's all just in one spot Another one is that bundle classes can easily be moved between projects So if these functions you find that you're writing these You know on every site you build You could just grab this exact thing Rename it from Umami to the new site name and you would have your new Base class with all of your common stuff that you've already used Um, they're the same if you had for example an event That is very similar to another event. You can just grab your bundle class and grab all the code So traits and code sharing Because they're PHP classes We can now use traits to share code For example, if you've got fields that are on more than one blog You could just create a trait that does something like get read time And then every time you add that field To a new blog you add that trait and you've already got that functionality available to you In this case, I've removed the read time functionality from The original block I created and I've moved that into a train Which means that the next block I create that needs that same read time label I can just add this trait to it and it will already have it Without us needing to do anything else So Like I mentioned earlier, we built all of our components as twig components outside of Drupal In a design system What this means is that rather than having all of that twig in the block template Like we did in the demo earlier We have all of our twig inside of our design system and then Drupal just imports those twig files But what that means is that we need to attach the library which we're attaching at the top here And then we just include the twig file from the design system The problem being is that this feature Dot twig It has a lot of variables in it So we needed a way to pass all of the variables to the template What we could do is say Include the template with and then just list 10, you know with variables But we decided to try do it in a bit more reusable way We do have over 20 of these type of blocks in our site So what we did is We made a simple template or we interface We attach this template this interface to all of our bundle classes Where we pass variables to our templates Which looks like this So this is our feature class for the template we looked at earlier It extends our block content base and it implements the new interface I was talking about That interface contains a get template variables function And what that does is it collates all of the variables that the twig template needs And returns them into in a simple keyed array And this preprocess is the secret source that links the bundle class and the template together It checks if the bundle class implements our new interface And if it does it attaches all of the variables to the template With our get template variables function I know I kind of bagged a bit on preprocesses earlier But having this glue ended up being the best way to manage this At first we didn't even have this And we just used get template variables straight inside the template But what we found is that there were situations where we couldn't have The right level of cache control that we needed by doing that So we added this here to kind of get around that and include the right levels of cacheability that we needed So what this means because this preprocess is the generic block template All we need to do when a new block is created Is implement our get templates variables function in the bundle class And all of our variables are automatically passed to our template with no extra work from us This glue just automatically does it If you would like to read more of the bundle classes this talk so far has been very heavy on that side of things And the history there's the Drupal change record on the slide And there's an excellent three-part series from Derek Wright at 10 7 Where he talks about their history with it and why they contributed helped contributed to Drupal These are the layout but allowed builder some modules that are sorry contra modules That we've used on our project I've talked about most of these already the top three are the the big ones because I feel like those top three modules Are really valuable for every single layout builder site you use. I don't I think they should just be always used But there are many more these other two modules I have been really valuable for the site, but They're more the modules you use on a case by case basis rather than Generic, you know should be used everywhere modules like the other three So this has been a close to a year-long project And I do really feel like I've only scratched the surface with how far you can go on this approach But time is limited. So I've tried to compress everything down into something that Hopefully is interesting and something you can try and Learn about or take away and and apply Um, but yes time is very limited Uh, thank you so much for listening. Are there any questions? So I have a question Simple solution Yeah, it ends up being kind of Up to how you want to implement it I suppose for example, if you only even if you only had one entity on the site You can still turn that into a bundle class. It's it's really valuable that you can Do it slowly over time So if your entire site's, you know working then maybe you don't need to touch certain parts But if you've got a new feature to add At the same time while you're doing that you could add a bundle class for that one specific Node type for example while leaving the rest of your site the way it already existed Um, and it'll work exactly how it did before. It's a Optin thing that you can do slowly over time Yeah You said you've You created like a base class that extends, you know, and then from there you extended with your bundle classes that base class Do you ever add more functions of that Rather than but have you come times where Your bundle classes you've got functions that are in there that you're doing sort of over and over again Anything you might just add this to the base class. Do you ever do that? Yeah, I saw it. That's a thing No, absolutely. Yeah, we um as time goes on we started this project with very few Methods in the the node base and yeah over time has gone on. We've definitely been adding shared functionality to it. There's been times I suppose we're You need to decide to think about things as when to add it to the base class or when to use it as a trade for example, if it's a very Node oriented thing that's going to be used across all of node the node types They're not added to the base class if it's something like we have one that's called uh Entity with tidal field and we can add that trait to blocks or media or nodes So that's where I use it as a trait Um, but yeah, definitely don't be afraid to add stuff to the the node base class when it's used in multiple places an example of The story block that was being put into the sort of layout builder That's like a kind of node layout builder So it is yeah, so what happened? Is that like is that just like a full-fledged block or is it kind of just strange just that node? Like is it just just just like short cuts creating a block and attaching it to that layout or so you can make The block but if you did want the block to be across multiple nodes you could Attach it, you know anywhere you want essentially Was it as though you're asking sorry just so it's basically still just creating a normal dribble block with via that interface So it has me in that spot Yes, yeah, exactly. It is it is still like a standard dribble block not like a special Extreme to hear anything. Yeah every everything's just the the dribble level Um, what would be the best way to handle like optional fields so that we don't end up um So that would probably be best done at the template level So and that with the twig templates you can have Yeah, just a if thing to say if um, yeah in this case if the story is empty Then just don't render any of that content Um, I would probably do that. Yeah at the template level Otherwise you might get this situation where like you said you've got The life story wrappers but nothing actually in it Um, so you can do it at that level But what if there's like heaps of eels in a block so we would have like Individual eels condition for eels then. Yeah, it's always needed. Yeah, like for example, I don't know If I just go back Um, where is my So for example in this case I should probably have wrapped it if around This sad scale and this read time label Field, I mean in my case tell mandatory, but if they weren't Then in this case, yeah, I would get sad scale and then an empty field there So that's where I'll wrap it if around that and this one here is getting the markup directly from the field So I don't need it if around it because it'll just return empty if nothing's in it. It won't return any markup But these two here would be a good example when I would Wrap that in like a an if to make sure that it's Rendering correctly when nothing's there Oh looks like I'm out of time