Search

526: Web Components, Testing, and Database Seeding

Download MP3

Dave's working on his speaker bod, how well does Find My work for a lost iPhone and Air Tags, what do you let kids have access to online, design systems for teams using different JS frameworks, web components, and testing web components.

Tags:

Guests

Chris Coyier and Dave Rupert in silly sunglasses and a sign that says Shawp Tawlkk Shough DOT COM

Chris Coyier and Dave Rupert

This episode is with just Chris & Dave, ShopTalk Show's hosts. Chris is the co-founder of CodePen and creator of CSS-Tricks, and Dave is lead developer at Paravel.

Time Jump Links

  • 00:25 Working on that speaker bod
  • 01:49 Find My
  • 06:49 Kids getting online
  • 15:49 How would you approach building a design system for teams using different js frameworks?
  • 29:17 Sponsor: Notion
  • 31:42 How do other frameworks deal with web components?
  • 39:23 Sponsor: Whiskey Web and Whatnot
  • 40:52 How do you unit test web components?

Transcript

[Banjo music]

MANTRA: Just Build Websites!

Dave Rupert: Hey there, Shop-o-maniacs. You're listening to another episode of the ShopTalk Show, a podcast all about websites. I'm Dave Rupert and with me is Chris Coyier. Hey, Chris (not in the booth).

Chris Coyier: Nah, I just felt like sitting down. I can kind of sit in the booth, but I just -- uh... yesterday, my back tightened up a little bit.

Dave: Ooh...

Chris: So, I thought, "You know what? I'm going to sit down a little bit."

Dave: Some post vacation woes there.

Chris: I've been doing really good, though. I've been yoga-ing a lot.

Dave: I saw your blog post on yoga.

Chris: Yeah?

Dave: I thought that was pretty good.

Chris: Not like heavily, but a little bit. But I'm back at cross-fit too, so I'm feeling good about that. I'm actually in a pretty good spot, you know, exercise-wise. But it still doesn't mean the back is perfect, you know.

Dave: I am not, but-- [Laughter]

We have conferences coming up, right? We'll both be at An Event Apart in Denver. Fingers crossed everything goes well there.

Chris: Yeah, October.

Dave: October-y, so back in person in conferences and stuff like that.

Chris: Mm-hmm.

Dave: Not like I'm like, "Oh, I need a beach bod for a conference," you know. But sometimes I'm just like, "Do I need to be in better shape for a conference?" [Laughter] You know what I mean?

Chris: That's the nerd's beach bod is I'm going to be up on stage presenting. I need to have better pipes.

Dave: Yeah. I've got to get my presenter bod.

Chris: Yeah.

Dave: Yeah. Yeah. Going to be up there with a bunch of skinny people. [Laughter] I better do some crunches.

[Laughter]

Dave: Uh... That's what I think about, you know, so.

00:01:46

Chris: You know what was happening this morning? Miranda drove me down to work because normally I bike down. You know we have a whole thing. I've been really into that. But it was just a special day where there's some extra crap we have to do - whatever. So, she drives me down, and she goes, "Oh, my God! I left my phone in the back of the truck before we drove out the driveway." You know?

Dave: Oh...

Chris: And I don't have my phone now, and it's not on the back of the truck now, so it's somewhere between our house and here - probably on the road.

Dave: Oh... no...

Chris: But I remember that, just the other day, I was wondering where she was because she was on her way to pick me up, and I was just poking around at my phone. I opened up that "Find My" app. It's an iPhone thing. I'm sure Android has a similar thing because how can they not.

But I was like, "Where is she?" You know? And we had, long ago, shared locations with each other for like airport pickup reasons or something.

Dave: Right. Right.

Chris: I was like, "Oh, shoot! She's right in here," and it showed me right where her phone was. It was about halfway in between work and here.

Dave: Is where it fell off the truck or whatever?

Chris: Yeah, totally. But I also called it, and some lady answered. She's like, "I found this on the road." [Laughter]

Dave: Whoa!

Chris: I was like, "Stay right there. We'll come and get it.

Dave: Nice!

Chris: Yeah, pretty useful feature. So, I just used that, that "Find My" all the time, and it was kind of top of mind because I couldn't find my keys this morning, and also opened "Find My" to just do the little -- because I put AirTags in everything.

Dave: AirTag, yeah.

Chris: Yeah, and I do this twice a week. I go through my house, and it points to me where my keys are. And they're inevitably like on my dresser right where they normally are - or something.

Dave: Yeah, of course.

Chris: But I'm such a dummy, I don't even see them.

00:03:31

Dave: I use the "Find My." We went on vacation. We went to San Diego, sunny San Diego.

Chris: Mm-hmm.

Dave: Gloomy San Diego was actually--

Chris: Whoa. That's unfortunate.

Dave: Well, it's just the way San Diego is. It blows off in the afternoon.

Chris: I see.

Dave: But I left my AirPods, Chris.

Chris: Just left them.

Dave: And then I got home, and I'm like, you know, I use my AirPods daily, just watching YouTube at night in bed or something. Bad habit, obviously, but whatever. It's what I'm doing.

And then I'm listening or I'll listen to the news, which is horrifying, but then I don't want my kids to hear the horrifying news, so I've got an AirPod in.

But I'm sitting there, and I'm like, "Wait. I haven't seen my AirPods in a couple of days. This is really abnormal," and I check everything. Then I check "Find My" and they're just in the hotel room. They must have fell on the floor or something or got - you know.

Chris: But you're in Austin and they're in San Diego?

Dave: Yeah. They're in San Diego.

Chris: Is that lost forever or do you call the hotel and be like, "Hey..."?

Dave: I guess I could, but, like, "Okay, keep walking. You're almost there." No. [Laughter]

Chris: [Laughter] Well, some guest is going to find it. It's going to be right next to the Bible in the drawer or whatever.

Dave: Yeah, and they're going to have a good day when they find it. But whatever. Hey. What can you do? You know you can't win them all. So, I'll probably replace them.

I have a friend who low-jacked his kids. [Laughter] You sneak one in their backpack or something. Are you going to do that?

Chris: I put them all over the place. Not with the kid yet because -- I don't know why I wouldn't. I guess that's kind of a decent idea. But I just am not worried about that yet because she's four years old. She's at school, dude.

Dave: She's not quite roaming. Yeah.

Chris: Yeah. [Laughter]

Dave: She's not kind of--

00:05:15

Chris: Then we had this problem, not that this is the-- I didn't make this the AirTag show, but I really like it in my wallet. I bought a special wallet just for that. But I bought the four-pack, so I'm walking around being like, "What else can I put this in?" You know?

Dave: [Laughter]

Chris: I just tossed one in the console of the truck because I was like, I don't know. Airport, get out, you know, "Hey, where did I park?"

Dave: Where's my car? Where did I park? Dude!

Chris: Yeah, and/or if it gets ripped off - any of these things.

Dave: Yeah.

Chris: I think that's kind of cool to be able to track it down. It's not yet, I don't think, maybe, super common knowledge that that is possible. I don't know. I don't know.

One there. One in the luggage and stuff. But we sold--

We had a Chevy Tahoe, too, and we sold it because we're getting a Tesla X.

Dave: Ooh...

Chris: Pretty exciting, so we sold the other car well ahead of time. It's been so nice here. A little hot, obviously, but we've been just biking everywhere, so we really didn't need two cars, anyway, and we got a decent offer on the Tahoe, so we sell it. Down to one car.

But then when we do need a car, Miranda needs it more than I do, so she's been driving it around. But it's obnoxious to her because, every 30 minutes on her phone, it's like, "There's an AirTag following you!"

Dave: Ah...

Chris: Which is that safety feature of AirTags because if somebody wants to do harm upon you or stalk you, they slip one in your--

Dave: Put one in your purse. Yeah.

Chris: Exactly, so that's what it looks like to Apple. I just haven't done the work to share it between two accounts or something. I don't even know if it's possible or what. I just haven't looked into it yet.

Dave: Like a family AirTag. Yeah.

Chris: A family AirTag. Yeah.

Dave: Yeah. No, I don't -- the family stuff is getting me. Yeah, what is this? This is oldmenyellatcloud.episode.

Chris: Mm-hmm.

00:06:59

Dave: But I'm in the situation where my kids are getting online this summer.

Chris: Yeah. Yeah, I saw the blog post.

Dave: Yeah.

Chris: Yep.

Dave: Yeah, so I'll talk about the blog post, but we've been doing a parent-initiated Zooms and Facetimes. You know, go call your cousins. Okay, yeah, you want to get on Roblox. Go ahead. You know?

Chris: Yeah.

Dave: But it's a lot of work to do that because it's like text parents, the parents say okay, and then you get the kids going, and they can't do it. So, you're involved in everything. You know?

Chris: Hmm...

Dave: But we've kind of just said, "You know what? It's your problem, kids." Like, you need to figure...

Chris: Yeah. Here's your iPad. bye-bye. Yeah.

Dave: Here's your iPad, so we kind of need--

Chris: But you can't quite do that, right? Because then--

Dave: You can't unfettered access, or I don't really believe in that, so there's still some stuff. But it's like they have a Messenger kid's account, and they can just kind of fire it up just like a phone and call a kid or text somebody whenever they want. You know?

Chris: Yeah. That's the Facebook thing?

Dave: Yeah, that's the Facebook Messenger.

Chris: Yeah. I have a nephew on that, and all I get is his little scores from video games and stuff, or he'll draw a picture and be like, "What is this?" And I'll be like, "It's a tree." And he'll be like, "No! It's a big tree."

Dave: Oh, busted.

[Laughter]

Dave: Yeah, yeah. Yeah. "Got 'em."

[Laughter]

Chris: Yeah.

Dave: But yeah. Yeah, I mean, and that's kind of all it is, them sending selfies and stuff and whatever or memes, doges, and whatever. Anyway, but it's kind of been interesting because I like to be really consistent when I parent. I try to be, you know, so I literally write down the rules I make up for my kids.

I made four Internet rules, and one is never send a photo to a stranger. I feel like that's a pretty good one. You may need to break that rule eventually, but that's a generally pretty good rule to stand by.

If you're mean online, that's like being mean in person. Just the idea of just teaching my kids, "Hey, a lot of people are going to be mean online, and that does mean they are mean in person."

We have this whole other thing about trying on mean. People try it out, what it feels like to be mean.

Chris: I like that one because I think that holds for adults too. I really don't like that, like, "Oh, that's just their little personality on Twitter. They're not really like that."

I'm like, "No, you are like that. Your fingers typed those words, so that's what you are like."

Dave: Yeah. Yeah, it's honestly, when I get a sniff of that, that's the easiest way to get blocked and muted from me.

Chris: Yeah.

Dave: Is just like, "You're kind of just like a bad dude."

Chris: [Laughter]

Dave: So, like, "Later. I don't need you, that in my life." You know? But even just with kids, it's just saying something mean or dogging your sister is kind of like a thing, you know. Just don't do that.

Chris: Ah, there you go. Yeah.

Dave: Then, like--

Chris: That's two rules? You're onto three?

Dave: That's two. Three -- I forget. I should pull this up, but my third one, I think, was just like-- What was my third one? I know the fourth one. I should just go to the website. Here we go.

Okay. My third one was no spamming because the first time they got the thing, my kids sent 40 photos to their friends that were of terrible quality. I just was like, "Hey, no one wants to see that. That's called spam. You're just sending people stuff they don't want." So, I'm preparing them for a career in email marketing. [Laughter]

Just the idea of, like, "Hey, you're there to have a conversation, and you go back and forth sometimes." You know?

Then number four is it's okay to leave a chatroom or a game or whatever if you're uncomfortable. Just the idea of, like, "Hey, you can just ghost, sign out, log out, take a break. Don't do it." That's just from getting pressured or something to do something they don't want to do - a situation. You know.

Chris: Good ones. There's not 15 where they'll just roll their eyes at you.

Dave: Yeah.

Chris: Because they're like, "That's too many rules. I can't remember all these rules, dad."

Dave: Yeah. Somebody on the Internet, the Twitter, was like, maybe a fifth one would be: remember the Internet never forgets. That's true and important, but I also was like, "That gets really dark. That's really heavy for an eight-year-old to absorb." The Internet will never forget anything you post.

Anyway, I just was like, I'm going to leave that out for now, but that will be a lesson they'll have to learn eventually.

Chris: Yeah. There's no rules about don't go to pornography websites because A) they're too young for that and B) aren't those locked down, anyway? That's not even entering their brain because they can't.

00:11:53

Dave: I don't think it's entering their brain. I do have devices as locked down as a I can. Apple doesn't really give you any kind of parent filter thing.

I try to point them to safer places, so like their school Chromebooks are locked down. You can only do certain things.

Chris: Yeah.

Dave: I feel pretty safe about that. But YouTube is kind of a dangerous situation, but I try to push them to YouTube Kids, which is usually vetted for kids.

Chris: Yeah.

Dave: Yeah. No, I mean it's kind of wild because they'll just ask Siri any question they want, and Siri will tell them what Siri finds on the Internet. It could get a little--

Chris: There is absolutely no parent lock or kid version of Siri?

Dave: Yeah. Yeah. I know for a fact my friend was telling me his son, who ... his friend was like, "Where do babies come from? You have to tell me. No one will tell me the answer." [Laughter] You know?

Chris: [Laughter]

Dave: And so, his parents were like, "Look, man. If you really want--" they just basically - I don't know - talk shit, get hit. They're just basically, "Okay, man. You are asking way too much. Here's the answer."

He was like, "No!" [Laughter]

Chris: [Laughter]

Dave: Anyway, I'm sure that next chapter of our life is happening pretty soon, as kids, young boys get curious about what's going on.

Chris: Yeah.

Dave: Young boys and girls. I know some girls his age who are also asking that same question. Anyway, the whole point of that was I'm now in this situation where it's like, "Okay," because as good as iOS is, it's a personal device almost, right? The kid, they want their Roblox account. They want their Minecraft account hooked up. They don't want to log out and then log in and enter a password and all that junk.

And so it's this really interesting world where now it's like, "Oh, man. I have to buy another device." Then I have to set it up and get them communicating. Then I have to get it locked down. Then I have to put the screen times on there. You know?

It's work. It's turning into a whole other job. It's another job that you have to do to successfully, safely parent your kids through being online.

You know some people do nothing, and a lot of people did nothing, and their kids are kind of fine. No, their kids are fine. I'm just kind of like, "Is this all worth it?" But I don't know. I'm going to try, I guess, is sort of my hope.

Chris: Yeah. Hmm.

Dave: You're not quite there yet with a four-year-old.

Chris: No. No, and I don't know what. I'm going to definitely overthink it when it arrives. You know? I don't know what's going to happen. Maybe I'll throw up my hands and just be like, "Just be a good kid! Here's your phone."

Dave: Here's your phone. You're five years old. Here's your phone. Just be a good kid.

Chris: Yeah.

Dave: Well, that's the thing, too. It's not a big deal, but one of my -- my brother-in-law and sister-in-law, they gave their youngest, who is Emmy's age (like six), gave her one of the hand-me-down phones. It's basically just like an iPad, but it's just like, "Here's your phone." You know?

And so, it's always like, "Oh, but she has a phone." You know? And I'm just like, "Yeah, but it's not connected to anything." [Laughter] It's just a phone. It's just on the family plan, but it doesn't get any minutes - or whatever.

Chris: Yeah.

Dave: Anyway. It's hard to explain that stuff. You know?

00:15:47

Chris: Well, should we do some questions here from the world?

Dave: Yeah, we can go talk about websites. Let's talk about websites.

Chris: [Laughter] One from Raphael Ferrand is curious about design systems and micro front ends, which is already -- I hate to say it. But just for radio's sake here, every time I heard that word, just the hair stands up on the back of my fricken' neck.

Dave: Yeah.

Chris: Dude, I fricken' hate these things. [Laughter] But, "Practically, how do you approach building a design system for teams using different JS frameworks?"

That sentence is worth rereading. "How would you approach building a design system for teams using different JS frameworks?"

Now, at a certain scale, I will just be like, "Fine. Fine. Okay. You have to do it." But I already don't like it. You know? I already think, like, "Okay, you need to build the tabs component, but it needs to work in React, and it needs to work in Vue, and it needs to work in Angular."

I'm like, "Where do you work? What is happening that you need to do that?" You've already said, "Oh, there's a bunch of people that work here that don't have cross-platform experience with these, don't have cross-framework experience." You're saying, "Nobody needs to deeply understand these frameworks. Just pick one, do whatever you want, and we'll make the design system team deal with all three."

I already hate it for that reason - limiting expertise.

Dave: You've upstreamed a lot of problems.

Chris: Yeah. [Laughter]

Dave: Just by being like, "Everyone, do what you want," it's like -- I don't know. It's like Europe.

[Laughter]

Dave: It's like, "Everyone, do what you want." [Laughter]

Chris: We'll share the same money, but even that's a fricken' miracle.

Dave: We'll have the same money. It'll be fine. It'll be fine.

[Laughter]

Chris: It's like Europe.

[Laughter]

00:17:40

Chris: Maybe you work at IBM or something and the Web is a big place. You have to do it.

He says -- Raphael keeps talking here. "Web components, but there might be some imperative." I'm going to skip that sentence. "Building them already adapted for each JS. Do you build them for each JavaScript framework? Do you build them--? Do you build React?"

You're not reaching outside of anything else. You've built these things just for React. Then you stop, make a new repo or something, like a clean slate, and build them that work and look identical but don't really share anything. But to you, one works great for React and one works great for Vue. That's that.

Or do you make them work with Web components? You've built them once and then you wire them up however you need to wire them up to make sure that they work okay in React.

For today, let's assume that React supports Web components just fine (even though historically that's not super true).

Dave: Yeah. Maybe the next-ish version. Who knows?

Chris: He wants to know about you because you have more thought behind Web components. You've got the whole course on Frondend Masters and stuff, you know.

Dave: Yeah, well, you can start by buying my course on Frontend Masters. [Laughter]

But yeah. This is Dave Rupert talking. Maybe you can have some success.

one situation would be your design system is based on something that already does this: Ionic, Chakra UI, some of these tools that kind of already--

Chris: I thought of Ionic. Tell me about that for a second, though. Wasn't that their foundation? I went to their website just now to be like, what's their sell on, "Make a component in this and use it across platforms"? Is that not really what they do anymore, or is it?

Dave: Yeah. I mean it was kind of like -- from my understanding, Ionic, it was kind of an iOS.css in its first iteration of life. You'd actually write Web components and stitch them together and be like, "This is tab-bar," and then you just chuck it up. Then it renders out as tab-bar. Then they did a whole--

Chris: So, it is mobile-focused. Yeah.

Dave: It's mobile-focused. But I think it's all responsive now. But some of those metaphors maybe don't work. You don't generally have tab bars on websites.

But then they added this thing where it would be like android.css, so the same components but it could look like an Android app.

Chris: Okay.

Dave: It's just the skin. Then I think there's this whole other level where, "Oh, you use tab-bar? Well, actually, use the native iOS tab bar when we--" We can compile this out to an app project, and we can use the native tab bar inside of there.

Chris: Like a straight-up X code swift, ready for the app store, rock-n-roll.

Dave: Sort of like React native sort of does this, right?

Chris: Wow. Okay.

Dave: You use that component. We can actually use the native component based on that.

Chris: You don't write your own components in Ionic anymore, or you kind of can, but nobody does that?

Dave: You totally can. Yeah, I mean it's just an HTML page, so you can just spit out whatever you want. But this would just be an idea of maybe something that is designed to work in different things, so they now have React versions. They have Vue versions, I think, like that. There are different--

I guess that's what Ionic is doing. They're building this framework to build mobile apps. You could use a framework that works in Angular, Vue, React, and stuff like that.

Chris: But if you're going to make something really unique, like I'm going to make a little Sudoku component, and the Sudoku component is a 9x9 grid, and you can click into each tab and use the numbers 1 through 9 in each little thing, that's really specific. Nobody is going to have that prebuilt for you.

Dave: No one is going to have that prebuilt for you.

Chris: But you want to use it because your newspaper website is in Vue and you want to offer it in the fun and games section. But you also have a React native app, and you want to use it there, too. What the hell do you do? Do you write it as a Web component and hope to wire it up into both of those places or do you write it twice? What do you do?

00:22:07

Dave: That's where I think Web components would come in. And so, the thing about Web components, which is not really advertised -- it's not on the box, the sticker on the box -- you can write Web components in some of the major frameworks. Svelte, Vue, PReact allow you to use those, use Web components, in the application.

You can both export. You can export. I can write Svelte and export a Web component that can be used by a Vue project. It's a little weird, but it's svelte:options tag Sudoku puzzles, sudoku-puzzle, or whatever.

Chris: Yep.

Dave: And so, then in Vue, you basically write your Sudoku in your Vue, but to spit it out as a Web component, you'd be like, you need import, define custom element from Vue, and then you import whatever, Sudoku puzzle from sudoku-puzzle.vue.

Then custom elements, which is the web-thing.define sudoku-puzzle, that's HTML or that's JavaScript.

Chris: Okay.

Dave: Then you use that define custom element sudoku-puzzle, so it's just a little transpiler, I guess.

Chris: A wrapper thing. Yeah.

Dave: A little wrapper that converts your big--

Chris: That's nice.

Dave: --Vue object into a custom element.

Chris: Okay, so despite the fact that it's being used in React and is being used in Vue, you really don't use any of the APIs provided by those platforms. You're not using use state in React. You're not using whatever else in Vue. You're using fricken' DOM APIs. You know?

Dave: You're using DOM APIs. Yeah, there may be a way to--

In Vue 3, you could. It would actually bundle and export out with all that Reactivity stuff. It's really 3K to 7K. It's probably okay for your component, the cost of Vue 3 inside of there.

Chris: Oh, I've heard that, too. That's a little mind-blowing to me, isn't it, that you're like, "I'm going to use a Web component. It's actually going to use a little Vue inside," but it doesn't mean that it can't be used in another framework's world because it's a little isolated inside there.

Dave: Yeah. Yeah, it's just a normal import.

Chris: It's so little that it's like, "Fine."

Dave: Yeah. React is kind of another ball of wax, which is why you maybe actually want to start authoring in Web components if you know the whole thing is going to go out. That's its whole job is to go places. Web components are part of the Web platform and they go places.

They can go to the old Ruby on Rails app. We're just talking JavaScript, but Web components can go to the old Rails app, the old Java app.

Chris: Anywhere, yeah.

Dave: They don't have to just-- It can kind of go anywhere, so I would maybe start in Web components, and I would maybe use something like Lit. The reason you're going to want to use Lit is because it has some extra, I guess, framework goodness or community-supported adventures. [Laughter]

Chris: Right.

Dave: There's a Lit Labs project to wrap your custom element so that it works in React projects.

Chris: Oh, really?

Dave: Yeah.

Chris: That's nice.

Dave: And so, what it's going to look like is basically you have your Web components and then every little-- You maybe have a mirror - whatever - Sudoku puzzle React component that just imports sudoku-puzzle, the Web component, but it's just a little wrapper. Then you only import the React-y stuff in your application.

Chris: Yeah. Yeah. I kind of am coming around on Lit. I've never hated on it, but I've always been like, "Why would I--?"

Every time I reach for it to make a little demo Web component, I'm like, "I don't really need this. It's not doing that much for me."

But if you look at the whole package, you're like, "All right. Well, it does make doing styles a little bit more ergonomic," and it adds the lifecycle hooks. So, if you need those, which you probably will at some point, you've got those. It makes dealing with events a little more ergonomic and stuff.

You're like, all together, it's like this is kind of a lot. Then to know that it's -- What's is it? -- an absolutely tiny amount of code.

Dave: 7K, I think, 7K to 10K.

Chris: Yeah, that you're like, "I'm just going to use it."

Dave: Yeah.

Chris: That's shared across them all (if you need to).

Dave: Yeah, and the more you use it, the more that cost is amortized.

Chris: Right.

00:26:44

Dave: Cory LaViska, who makes shoelace.style, he had a really good post about this, and he just was like, "You know I used -- I wrote Shoelace, the first version of Shoelace, with this all vanilla and really homegrown."

Chris: Hmm.

Dave: But he had written enough helpers, enough functions, enough, you know, "Oh, let's do this," enough shared parts that he basically rewrote Lit with no community support.

Chris: Oh, no. Yeah.

Dave: It was basically just like homegrown Lit. I think we used to call it where you made Sass in PHP. You wrote enough PHP where you made Sass, eventually, and so you might as well just use Sass. You know?

It's kind of one of those situations. I think you're going to find--

Yeah, I think it's called On Web, using Web component libraries. I'll put a link in the show notes. I think you're going to find a lot of really good reasons why you might want to just use a library. Again, they're really small, especially compared to something like React. But I think it's going to help you build out fast and scalable.

Then you're going to just benefit from some community stuff. Then these React wrappers are a great way to get stuff into your React project.

What's React on, 18 right now? There's talk that React 19 might include Web component support. It's on an experimental branch, React supports Web components, and I've heard the support right now is pretty good. I haven't heard any showstoppers. It is a breaking change in React, possibly, potentially.

Chris: It's the version release.

Dave: Yeah, that's why it's in kind of an experimental thing. Yeah, that's something to think about.

It all comes down to HTML, like attributes are attributes. In react, props and attributes are kind of the same thing.

Chris: Yeah, it gets a little conflict-y.

Dave: Yeah. Props are different in HTML land, in JavaScript. Props are a JavaScript thing, kind of, and attributes are an HTML kind of thing.

Chris: Yeah. Tricky, right?

Dave: It all comes down to that.

00:29:20

[Banjo music starts]

Chris: This episode of ShopTalk Show is brought to you in part by Notion. Learn more and get started for free at notion.com/codepen. That's notion.com/codepen to help you take the first step towards an organized, happier team today.

That again is notion.com/codepen. I know this is ShopTalk Show and not CodePen Radio, but that's the URL we got just to keep all them clicks all consolidated for this overall sponsorship.

Notion is the best. As you know, I have done videos about how we use Notion. We've talked about Notion a ton on CodePen Radio and ShopTalk Show. It's a phenomenal software product. In my opinion, it really changed the game for and kind of invented a new category of knowledge management app, which is kind of how I think of it. But it's an app that's really at the core of running any kind of business, but probably mostly software technology businesses because that's where my brain is at.

It helps you plan projects and have shared calendars and have shared meeting notes. What you can do with it is really open-ended in the best possible way. Everything you make is like a database or documents, and it's all nested and has good permissions levels and stuff.

I know I'm speaking very abstractly here, but once you get into using it, you're going to find it very natural and comfortable to use, especially in a team setting. It just really brings people together. I have no doubt that it's made us a better place to work. At the places, the businesses, I've incorporated Notion into there, it's like Notion is where the work happens a lot of times, and I really love that.

I also want to say one thing about how I appreciate how they get the details right at Notion as a company. For example, for a long time, anybody was asking, "Where's the API? Where's the API?" for years and years and years. Finally, they're like, "Here's the API," and it's super well-done.

It's well documented. It has good default integrations. It's just a super well-done API to the point where people were just like, "Um... Thanks." [Laughter] "That's perfect, actually. Great." You know?

Then they took a bunch of time to get even a little detail about how text is selected across blocks in the document editor. It just underwent this great improvement of how you can select text across them. It feels just like you're selecting text in a natural way that you'd expect in any text editor, which was different before because of the block nature of editing.

A little hard to describe, but if you don't notice it, well, that's what they wanted. They didn't want you to be like, "Ew... Why is text selection weird in here?" which it kind of used to be a little bit and now it's just better.

And I appreciate that, like, "We're going to spend time on that detail. Not on necessarily some big flashy thing, but just on getting the experience of using the app good." Thanks for the support, Notion.

[Banjo music stops]

00:32:16

Chris: How do other frameworks think about--? How have they avoided the trouble? Every other framework, as far as I know -- and I'm thinking of like Astro components and how Vue handles it and how Svelte does it and stuff. They just don't have that problem.

You can even just look at the class attribute to think about it, right? Which ones support class?

Dave: Can use class, yeah.

Chris: All of them, except React. Then you have to use class name. It kind of made sense in the early days. You were like, "I don't care. I just have to rewire my brain for one little thing when I'm in React.

But honestly, it's not one little thing because every stupid attribute, if it's DOM-bound, is like, "Did you mean--?" because it's always like, "Oh--"

Dave: "Did you mean--?" Yeah.

Chris: Yeah. All those iFrame attributes that you have to get camel cased just correctly to work. That's unfortunate that you have to do that math in your head of which one of these are going to get gobbled up because they look like props, and which ones are going to make it to the HTML? Oops, I forgot to spread my props because I passed them but I didn't use them.

Dave: Yeah. Well, and so Lit has a workaround for this. Again, why you want to use it a component or a library. It's like .posts=mypost, or something. It's kind of like Vue's colon syntax where you're like, "Hey, this is actually JavaScript."

Chris: Right.

Dave: You'd say, like, you'd put a period in front of the thing.

Chris: I like that. Draw a line in the sand and be like, "If we're inventing this thing, it needs to be an HTML incompatible syntax."

Dave: Yeah. Then that's, I mean, you can pass a JSON array. [Laughter] I mean you could. Then just string parse and un-parse. But that's just going to blow your JavaScript.

You really just want it to hang out in memory. You don't want to put it in template all the time.

Or, hey, guess what. It's just JavaScript. [Laughter] You could just have a global - whatever - window.posts and that's what you loop through. You know what I mean? There are no rules!

Chris: Yeah. That's what I think is a little unfortunate about this is if you're bound for Vue or React or Svelte or anything. They have so many niceties in there that you don't get to have -- [laughter] because your thing supports all these things, so you're just limited to DOM APIs and Lit helpers or something (if you go with that).

There's really nice stuff baked into the framework, and you just don't get to have those things.

Dave: You know I think, too, that's where maybe keeping your design system small in this situation is probably your best insurance policy. Looping through things, you never loop through things.

Our code does not loop through things. Our code is interactions and buttons and stuff like that. You know?

Chris: Yeah. I see. It's a design system that's pretty atomic, low down the atomic scale.

Dave: Very atomic. Yeah, and it's like, okay, if you're going to loop stuff, you just have to loop stuff. Here are some helper classes how we do that, some instructional stuff.

That's where it gets tricky. If somebody is like, "I want to use Redux to manage my posts, and I want to use Vue X, and I want to use some new library, like Pinia or whatever," you're just going to die under supporting all those things.

Chris: Yeah.

Dave: Maybe your design system, you say, "Cool. We are only doing atoms and molecules," like if you are assembling these together, that's your job.

00:35:49

Chris: I love that. We have two. At CodePen, the way it splits up, we have a thing called CP Library. CP just being CodePen, and library means dumb. None of these components have the ability to do a query at all.

Dave: [Laughter] Yeah.

Chris: They can't. There's no -- I can't say none of them have a loop in them. I don't know that to be sure, but probably not. They're mostly just -- they're just visual things.

But then we have another thing that feels a little bit like a design system but it really is just us step up the atomic scale. We used to call it CP Components, I think. But now we have this - whatever - internal organizational stuff. We call it CP Client, which is where we keep all our browser-based stuff, and there's a components folder in that.

They're still shared. It's still pretty high up in the mono repo setup, so different parts of the app can still reach for these things, but they can have queries and stuff. They have more responsibilities, so it limits where they're usable. Not really in our case because we are good like that. But yeah, there's a distinction between a library component and then a global component.

Dave: Yeah. In that situation, your library could be Web components (if you're an all React stack). But in theory, that's how it could work. You know?

Chris: Right. Right. Right. Just because it's a Sudoku puzzle doesn't mean that it needs to hit the Sudoku table of the database to get its own data. You can draw a line and say--

You pass in data to it, and it knows what to do with that data. But it doesn't get its own data. It's fed data.

Dave: Then you could even -- what we're finding, we're writing a big Nuxt Vue app, but what I'm finding is I do a lot of event emitting, like, "Hey, this thing is just going to chuck up and say, 'Hey, I won. Success on the Sudoku puzzle.'"

Chris: Mm-hmm.

Dave: Here's my solve. Post whatever data you want. Emitting events up and then just anything because it's a DOM event. It can be like, "Oh, I know what to do. I heard that event. Let me react to that."

Hopefully, that works. I guess that's maybe React might treat that way differently. But it's just emitting events. Try doing that. That's a thing we can do.

Maybe you have a little wrapper that, okay, if I hear this event then, in my React app, then fire this actual event, this React style event - or something.

Chris: Um... Yeah, I love that. Although, you can see how that gets in the weeds so quick. You're like, "Oh, I'm going to emit events." Well, frick. Everybody has got to agree on how to listen for those then.

Dave: Right. Yeah, scope them and then listen to them. Yeah, but you know -- yeah. I think, though, you know what's funny is everyone can do their own thing.

The major tradeoff there is now you are in wrapper town. A one-way ticket to wrapper town USA.

Chris: Hmm.

Dave: I hope you like it there. [Laughter] That's something to consider.

Chris: Yeah.

Dave: Maybe you limit the number of frameworks you're dealing with would probably suit you really well.

Chris: Yeah. Even two sucks, but man, three is going to be nuts. Anyway--

Well, let's stay in Web component town for a minute because, even if you don't care about Web components, dear listeners of ShopTalk Show. I don't know how that's possible, but you know.

Dave: Ah, yeah.

Chris: It is possible. We end up talking about lots of other subjects, so I think they're a gateway drug, really.

00:39:25

[Banjo music starts]

Chris: This episode of ShopTalk Show is brought to you by another podcast. Whiskey Web and Whatnot, a fantastic podcast from Robbie and Chuck over there. They run their own agency as well, so come at it from a long career in tech and lots of different technologies that they use.

You know I found that Dave and I, on this show, have a certain set of technologies we have the most experience with and then end up talking about that. Being a guest over on Whiskey Web and Whatnot was so fresh because they definitely have a totally different set. I mean we're all Web people, but they have a different perspective on things because of the different technology choices they've made and had experience with throughout their career.

They obviously talk about whiskey. That's great. The beginning small chunk of the show, everybody enjoys a little whiskey. They talk about it, so there's that aspect to it.

But it's largely about Web stuff, but lighthearted, not super-duper heavy on tech all the time. They have guests from all kinds of different areas of Web development. Lots of variety there, which is kind of fun. Web3 and NTF are fairly frequent topics and stuff.

I think it's just a worth a subscribe. If you like this show, there's a good chance you'll like that show. I certainly enjoy it. They do a good job over there. Can't have enough Web podcasts, right?

Go check out whiskeywebandwhatnot.fm. If you just spell it all out .fm, you'll get there, and it'll be in the show notes, of course, too. Thanks. Bye.

[Banjo music stops]

00:41:07

Chris: Ryan Filler, though, asks about unit testing Web components. Interesting question.

"Is it something like Cypress or Playwright, those being - you know - DOM, you know, like fire up Chrome, simulate clicks, more end-to-end-y, I'd say. I don't love this because it means I have to have a live version of every scenario that my component could be in. I'd much rather use something like Vitest or Jest to virtually create them with different attributes. Do you have any experience with testing like this?"

I can say that I don't, but I sympathize with what you're saying here.

Dave: No, I think it's a good question and one that's not super obvious from the outside.

There is a thing called Web Test Runner, @webtestrunner, that's kind of like the de facto Web component. I think you can get it going in Jest. I think you can get it going in Vitest, even. But all it is, is an import thing, foo from var, and then describe, and it expects something.

I think it's kind of a Chai-based is sort of what I've understood as I've dug into it.

Chris: Hmm...

Dave: One thing you may want to look at is there's a--

Chris: Does it still boot up a browser to do it? Or just get it in Node?

Dave: No, no. It's all just Node-based.

Chris: Oh, that's good.

Dave: I don't know what the JS DOM that it uses is, but it's all Node-based, I believe. I don't think I've seen it do something different.

One thing I would say, one thing to check out, is this thing called--

Let me get the actual thing because I see the test stuff.

Well, anyway, Open WC, which is a Web components kind of group, they have this thing called Chai A11y aXe.

Chris: [Laughter]

Dave: You basically can--

Chris: It's sad that I know exactly what all three of those things are, but I could see somebody listening in on this being like, "Oh, yeah, Chai A11y aXe."

Dave: Chai A11y aXe? What the devil?!

Chris: [Laughter] What the devil.

Chris: What kind of garbage is that? But what's cool is you can say, "Expect element to be accessible," and it will run the--

Chris: What?!

Dave: It'll run an accessibility test on the element. I just was like, I have never seen this, and that is very stinkin' cool.

Chris: You expect it when you start to see .2.b. You expect to see .2.b.truthe.

Dave: Yeah.

Chris: Or two equaled some text, or whatever. Not to be accessible, parentheses, parentheses. You know? It means it runs the little aXe suite right over this little DOM fragment, I guess. Right? It's looking for input label pairs. It's looking for probably color contrast and stuff (if it's booting it right up into the DOM). It might not be super accurate about that because I guess it wouldn't know about global CSS and stuff.

00:44:17

Dave: Yeah. This is all part of Open WC. Why I mentioned that was because they have a little CLI that's going to be, like, that is basically Create React App for a Web component project. When you run it, it gives you a Web component. Then it gives you a whole app, like a demo app that you can kind of start messing with.

This is great. I didn't know this really existed until I started doing my course. It's actually pretty great, and it will give you some ideas on how to test it and what it looks like if you test it.

I don't see a reason. I know Vite supports Lit and stuff like first-party. Vitest may be able to do Web components. I haven't seen anyone do that, but I would reckon it could. That's going to be my--

Chris: Yeah, there's also a question, like, what are you testing? Are you making sure that it looks right? Are you making sure that it doesn't throw an error? Are you testing the output of an internal function inside of a Web component? I have questions about what it is you're exactly trying to test. You know?

Dave: Yeah. I usually just, like, I found myself getting into if I change a prop and I'm doing an if statement in the component, make sure whatever div class - whatever - has button is true, or whatever. That's kind of what I've been doing.

Chris: I see. Whatever. If there's any logic in there, test the logic.

Dave: Just test the logic of the component, and you can do that in unit tests really fine. You know if you're doing some E2E thing, like when I click this button and it fires this modal, you may need Cypress to make sure that the modal flew up.

Chris: Yeah.

Dave: Or maybe, in your Web test runner, or your Chair or whatever, you just say, like, set up a spy and just say, "Oh, did it fire this event? Did it try to click save?"

Chris: Yeah. Yeah, that could do it, maybe.

Dave: That's something to think about. I do that a lot.

Yeah, I've been getting into testing lately. I drug my feet for decades, and now I'm finally in the, like, "You know what? I can do it." [Laughter]

00:46:48

Chris: [Laughter] Yeah, we've been in that phase a little bit at CodePen, too, because we have this goal to move over some of the site to this API that is the Go version of our API, which, whatever.

It's hard to give enough context to this really quickly on a podcast, but imagine that we have, like, we're building a second API that needs to be compatible with the other API. But it's one thing to do that, and one way to make sure that you're actually doing it is to release a small part of your website that does use that API that's less mission-critical, and be like, "Hey, there'll be bugs on this page if we did it wrong."

Then the fact that you've gotten it to production means that you've dotted a million t's or dotted a million i's and crossed a million t's, you know, as far as deployment and all that. And so, a part of that story (much like refactoring CSS or anything else), it needs to be tested.

Dave: Yeah.

Chris: You can't just wing it on creating, testing, you know. There are unit tests, which are the really low-level little ones, right? Like, "I'm writing a function that calls a database." Does it work? Does it return what I think?" Little baby tests that tend to run really fast and you don't need to--

They don't have a lot of dependencies, usually, the unit tests. Then what gets complicated, always, forever -- I don't know why this is just the longest conversation ever -- then there's integration tests, and they have the most nebulous definition of all tests. But in our case, it helped me think of them in an interesting way. We have integration tests for the API that fake like it's a browser, kind of. It's not Cypress, but it boots up.

We use this thing called Apollo client-end server, which is kind of like helps manage, maintain state and stuff, generally, across stuff, and will write a query against the API, but run it through Apollo. There are more gears, whereas a unit test isn't really combining gears. An integration test is combining gears, like metaphorical gears.

Dave: Yeah. Yeah.

Chris: They have to turn together. So, integration means there are services that are working together that still do what you expect them to do. But they're short of literally running a browser.

Then as soon as you're actually in browser land, I guess that becomes an end-to-end test. You know? Usually, I think of those end-to-end tests as ones that might traverse multiple pages or something.

Dave: Yeah. yeah.

Chris: So, in the nebulous definition. But I'm going to call it and say our end-to-end tests are Cypress.

Dave: Right.

Chris: That's just for our internal reasons that those are a different category. So, three really different layers of tests and, when you're on a small team like we are, you just can't write--

Because you could write one function, really low-level, that's like, "This thing is a query that gets Pens." You can imagine that. Of course, there's a query that does that.

Dave: Yeah.

Chris: You can write unit tests for that and, definitely, integration tests for that, and definitely end-to-end tests for that. That one is so important; they probably do all three exist. But for the 500 APIs we have, we probably can't write 1,500 tests.

Dave: Right. Right.

Chris: You know, one for each. So, you've kind of got to pick.

Dave: Sass with the letter A, does that compile?

Chris: [Laughter] Yeah.

Dave: You know, like Sass with the letter AB, does that compile? Yeah. It's just not going to--

Chris: Yeah. That's not -- those are even a whole separate bucket.

Dave: Right.

Chris: Processor tests are a whole different thing.

Dave: Yeah. Well, and it's tough, too. If you don't understand, and I put myself in the same, just generally, if you as a developer don't understand what the framework does and what you do and what even the Web platform does, you know, you can be writing your tests over and over. But if you just put a details element on your page, you probably don't need to write a test for, like, "On click, does it show stuff?" because that's the browser. [Laughter] Unless you did something weird, guess what, that works 99.999999% of the time. I wouldn't even worry about it, but that's just me. Maybe I'm way out there.

Or if it's a framework, it's like, "I really just care that the save function got called." You know? I'm going to just assume the database writing machine knows how to write to the database. I'm going to just assume the event emitting from the framework knows how to emit an event. That's probably all I need to do is just make sure save got called.

Chris: There you go.

00:51:41

Dave: It's fun. It's interesting to figure out where to draw the lines.

Chris: It is, isn't it? It's easy to underdo it. I would underdo it before I overdid it only because I've overdid it before.

We used to have a huge suite of Cypress tests that are now long dead because they became so problematic for us. But the idea being they were too much. They tried to mock too much data. They were too long of flows. They were too flakey and stuff, and it become a problem, so we aborted on them.

Now the new ones are, like, "Go to this page." [Laughter] Done.

Dave: Done. Yeah.

Chris: Some of them, like even that one, I'm a fan of using that as an example because, "Go to this page and make sure there are no JavaScript errors," is kind of a pretty sweet test, I think. It means your app booted up.

Dave: Oh, man.

Chris: Now you have a test that proves that your app booted up. That's very possible to break. But they tend to be at least test your mission-critical flows, like can somebody sign up for this Web app? That's pretty crucial, and it's probably at least a two-page test then.

Dave: Right. Yeah. Can somebody log in? Can somebody sign up? Can somebody--?

Chris: Yeah, test login and signup. Upgrade is a big one for the ones that take credit cards.

Dave: Can somebody upgrade? Yeah. How do you--? Well, maybe that a whole other show. We're running out of time.

Because you would be testing production, right? Are you testing--?

Chris: No, you don't have to do.

Dave: Or do you just send it the fake credit card, the 4111111111111109?

Chris: Yeah.

Dave: And then it--

Chris: 424242, in the case of Stripe.

Dave: 42424242.

Chris: Yeah, exactly.

Dave: And so, you send 4242, and then it just says, like, "Cool. Yeah. You did it." That's only on tests, right? You wouldn't-- That doesn't work in production, on the production keys, right?

00:53:25

Chris: Nah, you probably wouldn't run that particular test in production.

Dave: Right. Yeah, yeah.

Chris: You'd just run it on either staging or local.

Dave: Yeah.

Chris: Or like CI because I think a good setup is running all these tests in CI that mimic production.

Dave: Right.

Chris: Not staging because stating is useful, but it's too long to wait to get it all to staging and then run the tests. Depending on what your setup is like, staging might happen after the merge.

Dave: Yeah, yeah.

Chris: I guess it kind of depends on how you set it up.

Dave: Right. Yeah.

Chris: I'd prefer, because these days, something like GitHub Actions can spin up your site, even a pretty complicated site, so we have a little version of the site that runs in GitHub Actions that will run all three kinds of these tests, even Cypress.

Dave: See, that stuff is incredible. Yeah. We had bdougie and Rizel on a while back, and there's so much I'm not doing with GitHub actions, so I feel like I should. But this getting a baby site running or deploy previews.

Chris: That's a blog post I have to get done. There are literally 15, 20 things that--

Dave: You've got to do to get a baby site running.

Chris: No, that you could do in CI that when you look at each one of them individually, you're like, "Yeah, we should do that. That's a great idea." But nobody does all 18 of them - or whatever. People do seven, maybe, or two or zero.

Dave: Oh, my gosh. I want this blog post, and I just want sample functions. But that's too much on you. But that's what I want. I just want a recipe book. You know what I mean? Like, "Here's the recipe to not get caught with your pants down."

Chris: That's what I thought that Netlify's plugins would turn into, that their little ecosystem around build plugins--

Dave: Yeah.

Chris: --would turn into that, but I haven't seen it quite play out that way.

00:55:23

Dave: Yeah. I don't know. I tried to do, you know, it's a cool app called Renderer. Have you seen that? It's a hosting service. Maybe it was render.com. It's like a Node host app, a Digital Ocean competitor, you know, something like that.

It's basically just like, send your app here and we'll run -- try to build it, and then we'll do deploy previews and stuff like that. It has databases and stuff, too. It seems pretty cool.

Chris: That's complicated, isn't it? That's always what gets me because, when you have a production, at some point soon in the app's existence, you'll be like, "Well, we can't boot the whole database, obviously." Somebody has got to do a seeding situation.

Dave: Yeah. Well, let me tell you about my new best friend. I haven't quite implemented it, but I saw it.

Chris: SeedMaster Pro.

Dave: SeedMaster 5000.

Chris: [Laughter]

Dave: [Laughter] There's this app called Snaplet came to my attention. It's by the people who make Redwood.

Chris: What?!

Dave: Redwood.js, you know, did all this cool stuff. And then this thing is Snaplet, and it's basically say goodbye to seed scripts is their catchphrase.

Chris: What?!

Dave: It's like, okay, I'm sold. Where do I put my credit card? [Laughter] But I think Tom Preston-Warner, who has been on the show, he's kind of like an investor or he's an investor in Redwood or whatever, and so I think it's the same company - or whatever.

But it's basically almost like WP DB Migrate Pro for your just normal PostgreSQL database. Then it has this thing, like a reducer, basically, where it's like, "I'm going to pull this data, but just take out the last 10,000 users, or whatever. I don't need all that." You know?

You can even be like, "Okay. Then make sure every user has an anonymous email like [email protected]," so it's just -- I don't know. It's very cool. I just saw it, and I was like, "I am super compelled by this," because even this week, we're working.

We use a shared dev database, but that can go bad if you're all doing migrations and stuff, like changes and stuff. But this idea, like, "Hey, we can all just have-- You just update your database seed, and it'll figure it out."

00:57:54

Chris: Wow! This is blowing my mind a little bit. What's the thing about--? Some of the reason that you seed isn't just because you have to because you have this little baby empty database. But it's because I'm seeding it with data that I intended, want to test against.

Dave: Right. Right.

Chris: I'm being really specific against what I'm putting in there.

Dave: Yeah, so you can be really specific. You can be as specific as you want. Like if you have one user, user whatever, davetron5001 is your fake user who does all the bad stuff - or whatever - that you want to test against, you could just pull my user, my user ID.

Chris: Yeah.

Dave: And that's in the database. Then you're like, "Go through all of his Pens, and make sure they all work."

Chris: But if you're sitting down to write a test, you'd be like, "Okay. I want to test that I'm going to seed three things in the database and run a count against them. It better come back as three," because if it comes back as two or four, that's an incorrect test.

My test that would write that would normally seed the database and then test against it altogether. But with this, I'd have to stop, go, put something in the database that I know is--

There's almost a disconnect between what I'm testing and--

Dave: Oh, yeah. Well, but I think you could maybe--

Yeah. I don't know, actually. Yeah.

Chris: If you started from scratch with that, I think it would be better. But to sprinkle it in now--

Dave: But now you have it -- yeah. The way you have it now, you're like, every test inserts what it needs to test. Right? So, then you test the--

Chris: Yeah.

Dave: Yeah.

Chris: But it's not like I love that, though. It's just the way it is. So, I'd have to just readjust my brain a little bit.

Dave: Yeah. Well, and I definitely think this thing is pretty new, obviously, so you're going to spend an innovation token on it.

Chris: [Laughter]

Dave: But it is very cool in what it's attempting to do. The idea you can have a shared database or the production database, and then you just say, "Give me that," but then nerf it down to only a reasonable amount, and then let's--

Chris: Yeah.

Dave: It'll run nightly snapshots or whatever, and then you're just like, "Okay. Load the snapshot up on my computer." If somebody needed test data or whatever, maybe you figure out a way to emulate that test data in the best way possible. You're kind of working with live data at that point. That's kind of wild.

Chris: Cool.

Dave: But you know.

Chris: Yeah. Right on.

Dave: Anyway, all right. Well, cool. We should wrap it up here. Thank you, dear listener, for downloading this in your podcatcher of choice. Be sure to star, heart, favorite it up. That's how people find out about the show.

Follow us on Twitter for eight tweets a month. Join us in the D-d-d-d-discord because -- don't we have D-d-d-d-doug in the Discord now? [Laughter] Shout out.

Chris: Yeah.

Dave: Shout out.

Chris: High-five, Doug.

Dave: Shout out to D-d-d-d-doug in the Discord. Yeah.

Chris: Yeah.

Dave: It's good times in the Discord, so thank you all who have already joined. But yeah, feel free to join. Then that's patreon.com/shoptalkshow.

Chris, do you got anything else you'd like to say?

Chris: ShopTalkShow.com.