WEBVTT 00:00.000 --> 00:11.480 Oh, yeah. 00:11.480 --> 00:12.480 Hi! 00:12.480 --> 00:19.460 I kind of feel like I'm a bit of an imposter in this room today because I'm not really 00:19.460 --> 00:25.960 here to talk about brows development and I'm not necessarily here to talk about the 00:25.960 --> 00:30.760 platform from a standard perspective. 00:30.760 --> 00:34.320 But here I'm talking about web components 00:34.320 --> 00:37.000 from a JavaScript framework author's perspective. 00:37.000 --> 00:40.160 And that may be putting me at a bit of a disadvantage 00:40.160 --> 00:42.680 in this room. 00:42.680 --> 00:45.400 So yeah, hi, my name is Haley Thompson. 00:45.400 --> 00:49.600 You can find me on blue sky at haley.dev. 00:49.600 --> 00:52.880 I am on the core team for this really friendly programming 00:52.880 --> 00:55.120 language called gleam. 00:55.120 --> 00:56.480 It compiles to JavaScript. 00:56.480 --> 00:59.080 It looks like compiles to Erlang. 00:59.080 --> 01:02.240 And I'm the author of a front-hand framework 01:02.240 --> 01:04.240 in that language called luster. 01:07.600 --> 01:12.840 And like I said, I'm here to talk about web components 01:12.840 --> 01:15.200 often when we talk about web components 01:15.200 --> 01:18.680 as framework authors that's maybe a bit negative. 01:18.680 --> 01:20.480 So here, I want to talk about ways 01:20.480 --> 01:23.680 that we can make components web components work 01:23.680 --> 01:24.720 as a framework author. 01:28.720 --> 01:30.920 Often when we talk about web components 01:30.920 --> 01:33.840 and we're getting positive feedback, 01:33.840 --> 01:35.440 these are coming from people that maybe 01:35.440 --> 01:39.760 want to avoid frameworks or avoiderating lots of JavaScript 01:39.760 --> 01:42.920 or lock-in. 01:42.920 --> 01:46.400 And on the flip side, it's framework authors 01:46.400 --> 01:49.440 that are often the most critical of the technology. 01:49.440 --> 01:57.680 And I think if we want to understand how we can make 01:57.680 --> 02:00.400 web components be a really like successful foundation 02:00.400 --> 02:02.480 to build a framework on, we first need 02:02.480 --> 02:04.600 to understand what those criticisms are 02:04.600 --> 02:05.840 and why they're being made. 02:08.360 --> 02:11.200 And I think a lot of that stems from this quote 02:11.200 --> 02:14.480 from the author of solid JS. 02:14.480 --> 02:17.760 The fundamental problem with web components 02:17.760 --> 02:21.440 is that they're built on custom elements. 02:21.440 --> 02:25.520 And elements are not components. 02:25.520 --> 02:28.200 That's their observation. 02:28.200 --> 02:30.280 And then, kind of a lot of consequences 02:30.280 --> 02:32.080 for wow from this. 02:32.080 --> 02:36.400 So framework components are not tied to the DOM 02:36.400 --> 02:38.760 in the same way the custom elements are. 02:38.760 --> 02:42.360 And that means they can often do more. 02:42.360 --> 02:45.920 If you think about, say, a react application, 02:45.920 --> 02:48.920 I mean, there's going to be hundreds, maybe thousands 02:48.920 --> 02:51.520 of components in an application. 02:51.520 --> 02:53.200 And maybe only a handful of them 02:53.200 --> 02:55.600 are actually rendering something to the DOM. 02:55.600 --> 02:58.480 And the rest of them are composing in different and interesting 02:58.480 --> 02:58.880 ways. 03:02.800 --> 03:07.040 Similarly, web components affect DOM structure. 03:07.040 --> 03:08.240 They have to live in the DOM. 03:08.240 --> 03:10.640 And so that means if I have a web component 03:10.640 --> 03:14.600 that renders a list item, and then I try and render 03:14.600 --> 03:17.560 that web component inside a list. 03:17.560 --> 03:19.760 Well, I've broken the structure of the DOM. 03:19.760 --> 03:24.080 I've introduced some antique problems. 03:24.080 --> 03:25.280 And we can fix those. 03:25.280 --> 03:30.240 We can fix those with array attributes, for example. 03:30.240 --> 03:31.760 But from a framework's perspective, 03:31.760 --> 03:34.240 these are problems that never existed in the first place. 03:34.240 --> 03:36.800 And now, how do you think about ways to solve them? 03:40.480 --> 03:42.720 And then, of course, there's a performance 03:42.720 --> 03:44.800 conversation to be had here. 03:44.800 --> 03:46.320 When you're developing a framework, 03:46.320 --> 03:48.200 you really want to be tightly in control 03:48.200 --> 03:51.960 that is much of the runtime as possible. 03:51.960 --> 03:53.360 And when you go through web components, 03:53.360 --> 03:56.720 you're kind of forced into a very strict life cycle 03:56.720 --> 03:59.080 and a strict way of thinking about components that maybe 03:59.080 --> 04:02.560 doesn't map to how your framework wants to work, 04:02.560 --> 04:03.760 how your runtime wants to work. 04:08.600 --> 04:11.840 Now, none of these talking points are particularly new. 04:11.840 --> 04:16.400 I think maybe that solid JS quote comes from, 04:16.400 --> 04:19.480 I don't know, 2020. 04:19.480 --> 04:22.320 I think I've seen blog posts date back from 2015 04:22.320 --> 04:24.720 talking about web components, and the problems 04:24.720 --> 04:27.320 that they have as frameworks. 04:27.320 --> 04:29.840 So do we have any answers as a community? 04:33.280 --> 04:34.920 Well, kind of not really. 04:37.720 --> 04:41.400 As a community, we've kind of collectively agreed 04:41.400 --> 04:44.680 that web components are not framework components. 04:44.680 --> 04:46.200 And that's OK. 04:46.200 --> 04:48.680 Maybe framework components, that's, sorry, 04:48.680 --> 04:51.760 maybe frameworks don't even need to build on web components at all. 04:54.760 --> 04:58.680 And Leo Veru has this really excellent blog post 04:58.680 --> 05:01.880 kind of going into this idea. 05:01.880 --> 05:06.040 And she suggests that web components are at their best 05:06.040 --> 05:09.080 when they're these kind of generalizable elements. 05:09.080 --> 05:13.040 They extend what HTML can do. 05:13.040 --> 05:15.960 And they're used in the same way as kind of native HTML 05:15.960 --> 05:18.520 elements, meaning that they should be applicable 05:18.520 --> 05:21.080 to a wide range of diverse projects. 05:24.840 --> 05:28.360 Maybe another way to think about that is how many people 05:28.360 --> 05:32.880 have application authors do expect to use that component. 05:32.880 --> 05:36.840 And if you care about reaching as many users as possible, 05:36.840 --> 05:39.880 then you might want to reach for web components, 05:39.880 --> 05:43.960 the big story there, being, of course, interoperability. 05:43.960 --> 05:47.080 But the more projects specific you get, 05:47.080 --> 05:51.080 then the scale start tipping towards framework components. 05:51.080 --> 05:55.800 And if you're already in a React or a View application, 05:55.800 --> 06:00.440 then the cost of putting in or developing a web component 06:00.440 --> 06:03.000 is really outweighed by the convenience of just leaning 06:03.000 --> 06:05.720 onto the framework that you're already using. 06:09.320 --> 06:13.680 I mean, I think that's a fine place to be. 06:13.680 --> 06:16.440 But that kind of original promise, 06:16.440 --> 06:18.960 the allure of using the platform is still really 06:18.960 --> 06:19.800 very strong for me. 06:23.400 --> 06:27.960 And fundamentally, these are both still components. 06:27.960 --> 06:30.880 I mean, we've seen, or we've discussed maybe, 06:30.880 --> 06:33.000 the elements and framework components, 06:33.000 --> 06:34.480 they're slightly different. 06:34.480 --> 06:38.320 But fundamentally, a component is about an encapsulation 06:38.320 --> 06:42.640 of state, behavior, render logic. 06:42.640 --> 06:45.280 And web components do that just as well as framework components. 06:51.120 --> 06:54.160 So framework authors often frame this conversation 06:54.160 --> 06:59.040 as a technical, custom elements, web components. 06:59.040 --> 07:02.480 They have technical requirements or technical limitations 07:02.480 --> 07:05.520 that don't work in the context of a framework. 07:08.400 --> 07:10.760 But we've also seen maybe the difference 07:10.760 --> 07:13.480 between web components and framework components 07:13.480 --> 07:15.680 is in terms of audience or in terms of reach. 07:18.480 --> 07:21.560 But I think there's actually a more fundamental difference 07:21.560 --> 07:24.320 between the two. 07:24.320 --> 07:27.000 And it comes down for their world view. 07:27.000 --> 07:29.840 It comes down to how frameworks 07:29.840 --> 07:34.040 see the nature of applications versus web components 07:34.040 --> 07:35.400 or the platform in general. 07:38.400 --> 07:41.760 And I think that can be summed up as frameworks 07:41.760 --> 07:44.920 want to compose components. 07:44.920 --> 07:47.600 The main building block in a React application 07:47.600 --> 07:49.040 is the React component. 07:52.480 --> 07:55.800 The elements are web components are slightly different. 07:55.800 --> 07:57.920 And they want to be composing elements, right? 07:57.920 --> 08:00.960 They want to be composing real HTML elements, don't know. 08:05.680 --> 08:08.760 And to kind of exemplify that, I've taken some snippets 08:08.760 --> 08:11.600 from the React homepage. 08:11.600 --> 08:14.600 I think using these is a really good example 08:14.600 --> 08:17.400 because that homepage has to sell you 08:17.400 --> 08:19.880 on the idea of the framework and convince you 08:19.880 --> 08:23.760 about what the frameworks meant for model on world view is. 08:23.760 --> 08:27.400 And so we have a video component here. 08:27.400 --> 08:28.920 It's rendering some markup. 08:28.920 --> 08:31.800 And it's also rendering some other components 08:31.800 --> 08:33.000 that we don't know about right now. 08:36.080 --> 08:39.240 Next, they show us a video list. 08:39.240 --> 08:41.480 Here we're seeing the templating language 08:41.480 --> 08:42.800 can do looping or whatever. 08:46.960 --> 08:48.920 And then maybe the most interesting part 08:48.920 --> 08:53.360 is when we introduce this searchable video list component. 08:53.360 --> 08:57.080 And now we are starting to talk about state management. 08:57.080 --> 09:01.720 So we have this string of state search text. 09:01.720 --> 09:04.480 An interestingly, of course, this isn't rendering any dominoes 09:04.480 --> 09:04.960 directly. 09:10.480 --> 09:13.040 Now, if you're counting that's six components, 09:13.040 --> 09:14.560 just in this really small example. 09:18.720 --> 09:23.280 But if we think about what we said a component was, 09:23.280 --> 09:26.920 I mean, really, only one of these fits the bill. 09:26.920 --> 09:29.880 And that's that searchable video list. 09:29.880 --> 09:32.760 And so I think if we were working in the context of web 09:32.760 --> 09:36.680 components, we might not be thinking about that thumbnail 09:36.680 --> 09:39.720 or that like button as a web component. 09:39.720 --> 09:43.520 We might just be thinking about this as a reusable piece 09:43.520 --> 09:45.040 of the dom. 09:45.040 --> 09:52.640 Now, because framework components that 09:52.640 --> 09:57.480 these core compositional unit from the ground up frameworks 09:57.480 --> 10:00.960 have to work, not just have to embrace this idea 10:00.960 --> 10:04.320 that you might have an application with many, many hundreds 10:04.320 --> 10:08.200 of stateful components, each with their own lifetimes 10:08.200 --> 10:08.920 and render loops. 10:08.920 --> 10:15.640 But the frameworks have to work this way. 10:15.640 --> 10:18.920 I mean, we can kind of see that there is this philosophical 10:18.920 --> 10:22.360 divide between how web components want to be used, 10:22.360 --> 10:25.040 or see the world, and how frameworks want to be used, 10:25.040 --> 10:27.200 and see the world. 10:27.200 --> 10:29.800 But does it have to be like that? 10:29.800 --> 10:33.640 Well, when I think about Luster, the framework that I 10:33.640 --> 10:37.160 developed, I like to think of it like this, 10:37.160 --> 10:39.960 and that the framework really wants to be composing elements, 10:39.960 --> 10:42.440 and it really doesn't want to be composing components at all. 10:46.440 --> 10:53.160 And to maybe exemplify, I ported those react snippets over to Luster. 10:53.160 --> 10:54.920 So this is written in Gleam. 10:54.920 --> 10:57.120 I'm not going to go too deep into the language. 10:57.120 --> 10:59.680 I think if you understand a tiny bit of JavaScript, 10:59.680 --> 11:02.760 you can probably grow up this. 11:02.760 --> 11:06.040 And so again, we have this video element 11:06.040 --> 11:10.120 to rendering some marker, it's calling some other functions. 11:10.120 --> 11:14.560 And again, the video list looks pretty similar to you. 11:14.560 --> 11:17.440 Where things change or get a little bit more interesting 11:17.440 --> 11:21.560 is when we think about that searchable video list element. 11:21.560 --> 11:24.800 In React, we just introduced some state here. 11:24.800 --> 11:28.160 We wanted to handle the search input, what 11:28.160 --> 11:31.440 the user had typed, and so we just pull in use state. 11:31.440 --> 11:34.760 And now this is a stateful component. 11:34.760 --> 11:39.080 But if Luster isn't about composing components, 11:39.080 --> 11:42.320 if it's about composing elements, then we have to do something 11:42.320 --> 11:43.720 if we want reactive state. 11:46.600 --> 11:49.080 And that's achieved in what maybe appears 11:49.080 --> 11:53.880 a bit of a cop out by simply moving that state somewhere else. 11:53.880 --> 11:56.600 Now of course, you could do this in React 2. 11:56.600 --> 11:59.960 The differences you don't actually have a choice in Luster 11:59.960 --> 12:03.640 is no way to introduce component state in the same way. 12:07.560 --> 12:09.760 I was reading a blog post recently 12:09.760 --> 12:13.760 from an organization that had just ported their view 12:13.760 --> 12:17.040 application to Luster. 12:17.040 --> 12:18.600 And I pulled out this quote because I actually 12:18.600 --> 12:22.160 just think it's really perfect for what I'm talking about here. 12:22.160 --> 12:27.560 It's this idea that components that hold state in frameworks 12:27.560 --> 12:29.920 like React or View, et cetera. 12:29.920 --> 12:31.400 They make it super easy. 12:31.400 --> 12:35.800 In fact, super attractive to model your state hierarchically 12:35.800 --> 12:39.200 in the same way that you model your view. 12:39.200 --> 12:43.280 But for Luster, we can have taken this approach. 12:43.280 --> 12:45.600 That state should be modeled separately 12:45.600 --> 12:47.320 from your view code. 12:47.320 --> 12:49.840 And actually, I think you see a convergence 12:49.840 --> 12:53.640 on framework communities of this idea, too. 12:53.640 --> 12:56.120 I mean, first we had like Redux. 12:56.120 --> 12:58.640 Now we have all sorts of store libraries. 12:58.640 --> 13:02.040 Trying to move state away from our components. 13:06.240 --> 13:10.760 In Luster, that is achieved through an application architecture 13:10.760 --> 13:13.240 that all applications have to follow. 13:13.240 --> 13:16.640 And it's called the Model View Update architecture. 13:16.640 --> 13:20.360 It's also known as the NOM architecture. 13:20.360 --> 13:26.080 And that means all applications kind of follow this blueprint. 13:26.240 --> 13:31.800 We start off with a model of our entire application state. 13:31.800 --> 13:35.160 So here we have that search query string. 13:35.160 --> 13:39.360 We also have the video metadata that we've loaded. 13:39.360 --> 13:42.640 And we have an init function to produce the first version 13:42.640 --> 13:44.440 of that state when the application starts. 13:47.840 --> 13:50.480 Then we have a message type that describes all the events 13:50.480 --> 13:52.080 that we want to react to. 13:52.080 --> 13:56.040 So maybe that's user interaction or maybe that's HTTP 13:56.080 --> 13:58.760 requests, et cetera. 13:58.760 --> 14:02.760 And an update function that takes incoming event 14:02.760 --> 14:06.280 and the current state of the application 14:06.280 --> 14:07.800 and returns a new one. 14:07.800 --> 14:11.080 So here every time the user types something, 14:11.080 --> 14:13.560 we'll get a user updated search message 14:13.560 --> 14:15.160 with the new value that they've typed, 14:15.160 --> 14:17.440 and we store that in the model. 14:17.440 --> 14:22.680 And the final piece of that puzzle then is a view function, 14:22.680 --> 14:25.680 which takes the current application state 14:25.680 --> 14:27.800 to any given point in time. 14:27.800 --> 14:30.680 I'm renders that as HTML elements. 14:35.320 --> 14:39.640 And then we package these things up together in an application. 14:39.640 --> 14:42.600 And we start it. 14:42.600 --> 14:44.840 This is pretty standard of fair for a framework. 14:44.840 --> 14:48.120 So we're mounting onto some element with an ID 14:48.120 --> 14:50.000 called app and a way we go. 14:50.000 --> 14:57.280 If we start talking about state management 14:57.280 --> 15:00.680 and frameworks, though, especially declarative frameworks, 15:00.680 --> 15:04.480 which most front end frameworks are these days, 15:04.480 --> 15:08.840 then there's actually a dirty secret about we have to come front. 15:14.080 --> 15:16.280 And that frameworks like to pretend 15:16.280 --> 15:17.720 the own all of the state. 15:20.240 --> 15:25.280 So we had that search input. 15:25.280 --> 15:29.520 We own the text, the user is typed, every time they type. 15:29.520 --> 15:31.320 We get a message, we have to stay. 15:31.320 --> 15:35.360 We re-sync the DOM with whatever that string is. 15:35.360 --> 15:39.120 But of course, DOM nodes are stateful objects themselves. 15:39.120 --> 15:41.400 And there's a whole bunch of state that often exists 15:41.400 --> 15:44.640 in the DOM that is not modeled in our framework state. 15:44.640 --> 15:50.320 And the most framework's, I feel like developers, 15:50.320 --> 15:54.040 in general, are quite happy to live with this convenient 15:54.040 --> 15:56.000 lie and often it doesn't really get addressed 15:56.000 --> 15:58.680 or even spoken about. 15:58.680 --> 16:03.920 But if you kind of accept that this is what's going on, 16:03.920 --> 16:06.240 then you kind of open to the idea that web components 16:06.240 --> 16:09.480 let us play along with that lie. 16:09.480 --> 16:13.320 If we're already kind of OK with the idea that some 16:13.320 --> 16:15.520 nodes have in terms of state that we're not 16:15.520 --> 16:19.480 going to manage from our framework, then web components 16:19.480 --> 16:23.000 are a way to tap into that potential, perhaps. 16:26.120 --> 16:30.200 So I want to revisit that idea of what web components 16:30.200 --> 16:33.000 are perhaps applicable for. 16:33.000 --> 16:35.880 And instead of talking about it in terms of audience, 16:35.880 --> 16:42.320 how many users do I want my component to be accessible to? 16:42.320 --> 16:45.360 I actually think the split is a bit more specific than that. 16:45.360 --> 16:50.560 And it's about presentation logic or view state versus business 16:50.560 --> 16:53.280 logic. 16:53.280 --> 16:55.840 You know, if you have a lot of presentation logic 16:55.840 --> 16:58.600 in a component and not much business logic, 16:58.600 --> 17:00.880 then that's this idea that the component isn't really 17:00.880 --> 17:04.520 tied to your application. 17:04.520 --> 17:10.480 This might be more generalized in kind of layers framework. 17:10.480 --> 17:12.640 And as you introduce more and more business logic, 17:12.640 --> 17:14.960 then this is where we typically would feel 17:14.960 --> 17:17.280 like a framework component would come into its own. 17:22.240 --> 17:26.280 Now once you're ready to kind of participate in this idea 17:26.280 --> 17:29.440 that web components let us play along with the lie whilst 17:29.440 --> 17:32.800 also getting some benefits out of state encapsulation, 17:32.800 --> 17:35.440 then let's make that really easy. 17:35.440 --> 17:38.800 Instead of starting the application as a single page app, 17:38.800 --> 17:42.240 mounting it onto a dom node, we can just swap out 17:42.240 --> 17:43.520 what we do with that app. 17:43.520 --> 17:47.440 And so instead of starting it, we register it as a custom element. 17:51.040 --> 17:53.200 And I think this is one of the key parts 17:53.200 --> 17:55.760 about what makes component successful in Luster 17:55.760 --> 17:59.120 is that we kind of introduce the platform incrementally. 18:01.920 --> 18:05.920 I like to build on the idea that the platform is actually good. 18:05.920 --> 18:09.440 We don't really need to abstract too much away from it. 18:09.440 --> 18:12.240 But often there's a lot of upfront learning that has to be done, 18:12.240 --> 18:15.440 especially when it comes to web components. 18:15.440 --> 18:18.080 Now you have to suddenly learn three specs. 18:18.080 --> 18:20.640 You have to learn how to deal with accessibility, 18:20.640 --> 18:22.320 you have to learn about what the shadow dom is, 18:22.320 --> 18:26.240 how styling works, how slots work, et cetera, et cetera. 18:26.240 --> 18:29.520 And so making it really easy to just take an application 18:29.520 --> 18:32.240 and turn it into a web component. 18:32.320 --> 18:36.880 That's kind of this first step towards having a really tight integration. 18:36.880 --> 18:40.160 And of course, Luster isn't the first framework to have done this. 18:40.160 --> 18:42.160 Svel and view have done this in the past. 18:46.320 --> 18:48.480 But I really like this idea that the components 18:48.480 --> 18:52.000 they build on your knowledge rather than replacing it. 18:52.000 --> 18:55.360 And so you can get comfortable with Luster's approach 18:55.360 --> 18:59.360 to application architecture, which you kind of have to do 18:59.360 --> 19:03.040 if you want to buy into this kind of framework. 19:03.040 --> 19:05.360 And then once you're really comfortable with that, 19:05.360 --> 19:08.800 you can slowly start playing with components, 19:08.800 --> 19:11.280 first by just taking your application and turning it 19:11.280 --> 19:13.280 into a custom element. 19:13.280 --> 19:16.960 But then, for example, changing the application constructor. 19:16.960 --> 19:20.240 So before we were hauling Luster.application, 19:20.240 --> 19:22.640 now we're calling Luster.component. 19:22.640 --> 19:26.800 And that gives us access to some new options. 19:26.800 --> 19:30.480 So for example, we can start listening for attribute changes. 19:34.240 --> 19:37.360 Maybe we learn about form association. 19:37.360 --> 19:41.120 We want our components to participate in native HTML forms. 19:44.560 --> 19:47.120 Maybe we learn about focus delegation, particularly 19:47.120 --> 19:49.920 if we want to start distributing this component 19:49.920 --> 19:51.840 and context that we don't own. 19:51.840 --> 19:57.840 And then finally, especially if we're going to start talking 19:57.840 --> 20:01.760 about distributing this component outside of a Luster application. 20:01.760 --> 20:04.560 There's this option here called adopt styles 20:04.560 --> 20:08.000 and we can turn that off. 20:08.000 --> 20:11.600 Now the other options, these were part of the spec, right? 20:11.600 --> 20:15.200 If you know about web components, you've probably heard about listening 20:15.200 --> 20:18.720 for attribute changes or form association. 20:18.720 --> 20:22.000 But this idea of adopting styles, well, this is new. 20:22.000 --> 20:24.960 And this is one of the big watts that Luster kind of has to 20:24.960 --> 20:28.720 contend with if it wants to present components 20:28.720 --> 20:33.120 as a viable option for application authors. 20:33.120 --> 20:35.840 And that's fundamentally the styling still remains really tricky 20:35.840 --> 20:38.560 when you're working with web components, especially when you're 20:38.560 --> 20:41.760 working with the shadowed on, which is what 20:41.760 --> 20:44.800 all Luster components work with. 20:44.800 --> 20:48.400 And so what we do there is we have this kind of 20:48.400 --> 20:54.800 awful hack to crawl the document, find all of the style tags, 20:54.800 --> 20:58.640 all of the link tags, and they kind of get cloned into the component. 20:58.640 --> 21:03.840 And so if you're an application developer, 90% of the time 21:03.840 --> 21:05.680 this just works. 21:05.680 --> 21:09.040 But 10% of the time is really awful. 21:09.040 --> 21:12.880 And I'm hoping that maybe if we can start thinking about 21:12.880 --> 21:16.320 web components as viable targets for applications, 21:16.320 --> 21:18.560 that maybe we can start having a conversation 21:18.560 --> 21:23.440 about splitting the shadowed arm from style isolation. 21:27.040 --> 21:35.520 So to summarise, we kind of agree that elements are not components. 21:35.520 --> 21:40.320 But maybe frameworks don't need components as much as they think. 21:40.320 --> 21:43.600 And when they do want components, maybe work components are ready 21:43.600 --> 21:46.080 and waiting. 21:46.080 --> 21:56.080 Thank you for listening.