The creator of Vue js gives an update on the new features of the technology.
Vue: Feature Updates
From:

Vue.js London 2023
Transcription
Hello UK! So it's my first time in the UK, first time in London. Super excited to be here. We had a bus tour yesterday. I didn't really have much opportunity to walk around myself yet, but it's really been a very eye-opening experience, a lot of history here. So super excited, and super excited to connect with all the developers from here, and also from all around wherever you are coming from. So today I'm going to be talking about the feature update that's happening recently in vue, and in case you don't know yet, vue 3.3 was just released yesterday. So this was the first minor release in quite a very long time. So 3.2 was actually released more than a year ago, and one of the reasons for that was because we spent a lot of time working on vite and other related projects. We invested a lot of time in the IDE side of things, making the language tools, Volar and everything, bringing up the whole development experience around the tooling ecosystem around vue Core. But now we're moving our attention back to vue Core, and you're going to see more frequent releases from vue Core after this one. So this is the start of a lot of new stuff that's coming. So let's take a look at 3.3. The focus of this release was again improving the development experience when you're using script setup in single file components and typescript. Now don't get mad at me if you don't use typescript. I think obviously when vue just started we didn't even support typescript. In fact, a lot of the initial APIs weren't even designed with typescript in mind. So over time we had to slowly rethink parts of the framework to think about, oh, how does this api work with typescript? When we introduce new APIs, how will these new APIs work with typescript? And the reason for that is because in the initial stages when vue was relatively small, our user base was mostly focused on relatively simple use cases. They were largely using a back-end framework and they just wanted some simple interactivity on the front end, but nowadays our use case has expanded wildly and people are building really complex applications with vue. And in situations like that when you have large projects, big teams, a lot of people working together with varying level of experience, having a type system and having good development experience with typescript is going to help you a lot with the cross-productivity/talks">team productivity and long-term maintainability of your applications. So we believe this is a really important thing that we want to really nail and there has historically been a few pain points in vue when you're using vue with typescript, which we believe we have largely resolved in this release. So probably one of the most wanted features since 3.2, since the introduction of script setup, is the ability to use imported types when defining props. Now, this example here actually worked in 3.2 when you import a type from another file and use it in define props with the type declaration syntax. And some background here, the reason why we need to do something special with types here is because at runtime a vue component needs to know the explicit list of props that it is expected to receive. So without that information, it won't be able to tell whether something it received should be treated as a prop versus just a fall-through attribute. This is the reason why we need to, the vue compiler needs to look at the types you provided to define props and then try to analyze what props this component expects to receive at runtime. So here, just by looking at the AST, looking at the syntax here, you'll notice that it's very easy to determine that message.msg is the only thing that this component expects. It's deterministic, so the compiler says, okay, I've already figured out that message is going to be an expected prop. So even if I'm not able to completely infer what this external type is, it doesn't prevent me from generating the correct runtime props list so that your app will just work. Because in production, we don't really do runtime props validation based on these types here. But things become a bit more complicated when you, say, use an imported interface, and then you just pass it to define props like this. Now in 3.2, the compiler was not able to really look into what is really inside this type, because up to 3.2, views component compilation is always single file context, which means the compiler only sees this file. So it doesn't have any information about other files, because it does not have the information about your file system, about your alias configuration, or the paths configuration in your ts-config. So if we want to really resolve this type and look into what properties are included in it, the compiler really does need to resolve this import specifier. It essentially needs to know which file we are in, resolve the relative paths, then we also need to take into account, say, if you are importing a type from an npm package, and how do we resolve that npm package file? And how do we, say, if the user has configured custom path mappings in ts-config, you can have random alias here that's pointing to any file in your file system, and we will need to resolve that the same way that typescript resolves the types. So that sounds like a pretty daunting undertaking, and to be honest, it really took us quite a bit of effort to make this work, but in 3.2, we actually have managed to, we believe we have managed to support most of the common cases that you can run into. For example, when you are using a relative import, it's relatively simple, because we know the current component's file path, so we can just use this relative path to look for the other file, and then we need to handle caching and file invalidation when you edit those files, and then we also need to handle the cases where you are importing from an alias or an npm package, so we actually use typescript's own api to do the module resolution to get the exact same file that typescript is resolving to, and then we parse that file to get the proper type information in order to generate the runtime code you use. So it looks like a simple feature, but in fact it's quite involved and took us quite a bit of time to figure out how to do it. But even with this, there are still some limitations that we have to be transparent about. Not just imported types, but we also added support for a limited set of complex types. For example, here we are using an imported type together with an intersection type with some additional props you might want to add to this interface. We can also do interface extends, or sometimes you may use utility types like required or optional, built-in utility types like that. So we tried to account for these types as much as possible. For example, this example here will just work, because we are able to resolve whatever is in props, we are able to resolve whatever is in here, and we are able to just merge them together. But eventually the whole analyzation process is still AST-based. We're not actually using typescript itself to do all the type inference and everything, because that would be really expensive. So it's kind of a pragmatic approach where we try to support as much as we can by just analyzing the AST. So it will not be able to cover 100% of the types, so if you have crazy complex types with conditional infer or crazy mapped types with generics and everything, some of them will not really be fully supported. But for the most common cases, this is probably going to cover most of the cases you would run into in application-level code. So the advanced type cases usually happen when you're writing a relatively advanced component library. And we're still working with component library authors to figure out what special cases they may need so that we can improve the authoring experience for them and also the consumers of those component libraries. So this is going to still be an area we're going to continue to invest in. The next big feature that we enabled is generic components. So if you are not familiar with typescript or generics, think of it as... Let's use this component as an example. So you're building a list component. This list component is going to receive a list of items, and it's also going to receive that item which is selected. So at the type level, you want to say the selected item is going to have the same type as the items in that list. So here... But the problem is I don't know what exact type it's going to be. So generics allows you to express that. So here we're just going to add the generic attribute to our script tag here. So this is going to be exactly the same as when you're using normal generics in typescript, you have this angle bracket, and you put the generic type parameters in there. So you can put anything you can put in those square brackets in here. And you're going to say we're just going to have a generic type T, and I'm going to accept a list of items that's going to be an array of T, and the selected item, which is going to be T. So as I said, you can do anything that would work with normal generics, which means you can have these extend constraints, saying this T needs to satisfy this type constraint. Or you can have multiple type parameters, and then you can use them together. So this is just a really contrived example. Don't try to make too much sense out of it. This is just to demonstrate the syntax possibility. But anything you can do with normal generics in typescript, you should be able to express it here. And we also shipped a kind of a half-baked thing about generic components with defined components. So this is more oriented towards library authors who, because if you've used vue, you know vue can actually work with JSX, and you can also use TSX with vue. And this is in some situations preferred by library authors when they write really complex and dynamic components for you to consume. So this gives them a bit more flexibility, but they also run into the situation where they need generics, and previously there's just no way to do that. Or they have to resort to really, really complex hacks, type tricks, for them to be able to express these things. So we reworked the signature of defined components to allow you to use and directly pass a function with generics to defined components. And it works kind of like react function components, with the difference being that instead of directly returning JSX or a render function or virtual nodes, you want to return a function that returns JSX. So the difference between this and react hooks, if you've used react, obviously you would know that the difference here is that Composition api code runs only once when a component is created. It does not get repeatedly called. However, this return function here, the render function, does get repeatedly called. That's the reason why I need to separate them. But this gives you a pretty nice compromise between being able to use vue's reactivity system with TSX, JSX, or manual render functions, together with very satisfactory type support with generics. So one thing missing here is that we do need to provide the same generating runtime props list based on your types kind of support. So here's what to do here. We are working on a Babel plugin that's going to be used alongside our JSX plugin. So that you can, when you use a type-only props declaration here, it's just going to automatically infer the runtime props list and inject it into this component for you. So you don't need to double declare everything. So one of the implications of this feature is we hope that some existing libraries that is using TSX to author vue components can migrate to this, and then we'll be able to just ship types only for all props between both the component library and the application. So there's no more need for you to manually write a list of runtime declarations than somehow reverse-inverse the types from it, which is a very common practice in component libraries right now. So that's one thing we want to resolve. Like eventually, we want to get to the point where if you are using typescript, no matter if you are a component author or a component consumer, you should be able to just care about the types, and without caring about the runtime props list anymore. So that is a long-term goal that we hope to achieve. So another feature in 3.3 is the more ergonomic define-emits. So if you've used define-emits previously, you probably have found the signature to be a little bit weird. And the reason is this in fact is the exact type for this emit function you are going to get back here. It's just a little bit awkward to write. So here we are doing some little internal type conversions so that you can just say we have a foo event which is going to accept this. So this syntax may look unfamiliar to some. This is in fact called labeled tuple syntax in typescript. So if you ignore this ID label here, this is just a normal number, a tuple with a single element that is a number. But typescript actually also allows you to label your tuple elements. So you can give each element inside a tuple a name. So you can say this first element is going to be ID. And the sole purpose of that is when you look at the type from somewhere else, it's going to give you better information. You're going to say, oh, the first element is the ID, the second element is that. So it's kind of like an argument list, which is the exact purpose of what you want to declare in here. So we just internally convert this type to give you the correct emit function type back. If using view2, this is in fact support view macros. In fact, this feature is inspired, we just took the feature from view macros. And the author of view macros, Kevin, he is now a core team member, and he has been actively working on a lot of experimental ideas in view macros. And if you're on the more explorative side, you're OK with trying new things that may or may not work, you should check this project out. Then there's type slots with define slots. So one thing that we've kind of have always had a bit of a problem with is that slots are not expressed as part of the components props, because everything happens in the templates. So previously there was just no way for us to express them with types. But now we provide you with the ability to do that. So this defines slots macro. The syntax here requires you to know a bit about how slots are represented internally in view. So when you pass a slot from a parent to a child component, they are internally represented as functions. So a slot passed from the parent is in fact a function that's being passed and is going to be called by the child. So here when you define slots, you're going to represent them as functions. And a slot function receives its slot props, which the child component can pass into the parent slot. So when you declare slots in a component like this, the IDE is going to essentially detect that you're declaring. If you happen to make a typo and say slot name equals a name that does not exist here, it's going to give you an error. Say, oh, this slot, you haven't declared the slot. You are probably making a mistake. And it's going to give you type checks for the slot props. If you pass message to the default slot and it's not a string, it'll yell at you. And also in the parent, when you consume this component in the parent and you use a slot and you receive the message from the scope slot, it's going to give you the string type correctly. So currently, one thing that's still missing is the required slots check. As you can see, we do have the ability to differentiate between a required slot versus an optional slot with this. Currently, the required slot check is not implemented yet, but it's planned and it will be there eventually. And again, you can use this in vue 2.7 with vue macros. The IDE support in Volar actually supports both vue 2 and vue 3. And also, define slots doesn't have really any sort of runtime implications. It doesn't do any runtime checks. This is purely for type level checks that's going to be used by Volar and vue TSC. So you do get the same checks when you are just running the type checks from the command line as well. Okay, so here we are going into some experimental territory. So reactive props destructure. How many of you have heard or tried reactivity transform? No one? Okay. So, reactivity transform was an experimental feature that we tried for quite a long time, but eventually decided to drop. And if you're not familiar with reactivity transform, then we might need to provide some more context here. So the first thing is, in vue, by default, all the reactivity magic happens by, like say when you are using a property in your template, using something inside a watcher, vue automatically tracks them. And the way vue tracks them is by tracking these dot property access. So when you say props.foo, it in fact triggers a getter or a proxy trap internally, in which vue performs the magical tracking. Oh, okay, the time is actually, I need to go faster. But the idea is, right, so by default, everything needs to happen through these dots, which is why if you currently destructure a prop, define props like this, right, you actually lose the reactivity. This foo destructure here is just going to be a constant that's never going to change. But what if we can make it reactive, and that's exactly what we're doing here. So if you opt into the experimental reactive props destructure feature here, you can destructure with default values and everything, and this foo will stay reactive, say when you use it in a computed property, when you use it inside a watch effect, or you return it from a getter, it will stay reactive. And the trick is really simple, we just compile this at compile time to something like this. So we just automatically add the dot access for you, so that you don't need to write it yourself. And it gives you, and the main reason we want to do this is because it gives you such nicer, cleaner syntax when declaring default values. If you've tried to declare default values with define props before, you know you do this with defaults, which is really awkward. This is something we really want to get rid of, and this gives us a way to do that. But again, this feature is a little bit controversial in the sense that it does introduce this new compiler magic that you need to understand a bit more about how view reactivity tracking works to understand why this works. But hopefully the DX improvements that it provides will eventually be worth it. And yeah, so this is just demonstrating that if you use this destructure prop inside a watch effect, it's going to be tracked, just like props.message. And so every time the parent props change, this console.log will log again. Now define model is another experimental feature. If you've ever authored a component, say wrapping a native input, and you want to have a custom input with more advanced features, but you still want your component consumer to be able to use vModel with it, previously it's quite verbose. This is what you need to do. You first need to declare something called declare prop called model value. Then you need to also have a corresponding event that's going to be emitted. And then you need to do your manual wiring here on the native input and eventually piece everything together. So with 3.3, this is what you need to do. You just say define a model. What it gives you back to you is a ref. This ref just works like a normal ref. You can mutate it. When you mutate it, it just emits the event back to the parent for you. If the parent value changes, it'll just update the ref for you. Because it's a ref, you can actually just bind it to an input with the native vModel. Then you can work your additional logic on top of this. It makes it a lot simpler to wrap a native input to build your custom input components on top of them. Then there's define options. When you use script setup, if you still need to declare additional options, previously you need to use a separate script block. Now you just use the macro, so you don't need two separate blocks. And then there is this little bit of advanced level thing. If you have used Composition api and you've worked with composables, anyone here have used vue.use? Great. So when you use vue.use, you know vue.use usually can, when you want to pass something into a vue.use function, it can accept a normal value or a ref. So at the type level we can think of it as something called maybe ref. You can pass a value or a ref. But the problem with that is, for example, if you have a scenario like this, you have props and you say, I want to pass props.foo into it, but I can't just say use feature props.foo because in that way I'm just passing a value that's not going to stay reactive. So what we had to do previously is you're going to either use toRef, so you say toRef props.foo, essentially you're creating a ref out of props.foo and then passing it into the function. And the problem with that is toRef, the previous signature, isn't entirely flexible. The idea here is, what if you are trying to pass in a ref for a deeply nested property? So you want to say toRef props.foo bar. Now this is already starting to look a bit weird and another downside here is it doesn't handle the case where the foo may or may not be there. So you say, okay, I can refactor this, I can use a computed property. Now I'm just going to say props.foo question mark bar. Now this will handle no matter if foo is there or not. The problem is computed, sometimes we may just use computed without thinking too much about it, but internally computing has to create a watcher that keeps track of things and invalidate values and all that. And so, yeah, the easiest way to do it is if your composables actually just support accepting a getter. So we provide a new function called toValue for you to do that, and what it really does is inside your composable, you're going to say foo is not just maybe ref, it's maybe a ref or a getter, and toValue just normalizes everything to a value, no matter if it's a value or a ref or a getter, it just normalizes it. So you can do this and you no longer need to care what foo is. It can be anything that the user wants to pass and it will just normalize it. And if you think about the normalization directions, in the middle you have maybe ref or getter. On the left we can use toRef to normalize every one of them into refs. On the right you can use toValue to normalize everyone of them into values. So it's just a two-way street. Okay, so then there's the JS import source. So I want to mention this mostly because this does have a potential implication if you're using TSX. If you don't use TSX, it doesn't really concern you, but if you use TSX with vue, you want to opt into this now so that when we remove the default global registration in 3.4, it's not going to affect you in any way. So yeah, and what's next? V-Press 1.0 beta very soon, now that we have 3.3 out. This is the static site generator. If you've read the docs of the vue.js docs, the vite docs, Rollup docs, in fact, a lot of these new projects, their documentation is built on VitePress. And if you're having a project yourself you want to document, try it out. I personally believe it's the best documentation tool out there. And next thing is we're going to do a round of vue core issue PR cleanup. We do have a bunch of P4 important issues that we want to tackle before the next minor sprint. Then in 3.4, some of the things we want to prioritize are to stabilize suspense, some ssr improvements, building safe teleport, and more efficient computed invalidations. And the goal for future minor releases is we want to keep them smaller in scope so that we release more often, release faster. In Q3 and Q4, we're going to continue working on vapor mode, and there are some additional platform features to keep an eye on if you are interested in how the future of vue will interplay with new use the platform initiatives. So one exciting thing is the native app scoped proposal that's going on in the css working group. So in short, this feature will allow us to completely reimplement vue's scoped style implementation to make it much simpler and more performant, which is a great thing. Less inside the framework and more just leveraging the native platform features. And then there's async context, which allows us to track the current component context in an async call stack. So in async setup, async methods, and async PNA actions, we're going to be able to know which component initialized it. And then there's the DOM parts, which is going to be an important compilation target for vapor mode. A lot of these are ranking from closer to materialization and to DOM parts, which is super early stage. But these are the things that we're keeping an eye on, we're excited about. We believe they'll help vue Corp to be simpler and more efficient. And that's it. Thank you. Thank you all so much. Please step into my office. Remember, I'm going to be here all day. So I hope you have a great day. And I'll see you next time. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye. Bye.